mehdime / DbContextScope

A simple and flexible way to manage your Entity Framework DbContext instances
http://mehdi.me/ambient-dbcontext-in-ef6/
MIT License
633 stars 271 forks source link

DbContext Custom Connection String #27

Open MohamedAtia-B opened 9 years ago

MohamedAtia-B commented 9 years ago

Hello Mehdi, I would like to thank/congratulate you for this nice dll and post.

I am using in a business application and it works like charm, however I have a requirement that I cannot store connectionStrings in the app/web.config. So I need to set it programmatically.

The GetContext() method initializes the context object with the correct connectionString however in the Repository.Insert() the ambientLocator returns a context object without a connection string and throws InvalidOperationException "No connection string named 'BDAL_DemoEntities' could be found in the application config file."

It seems like it instantiates a new context object, is there a way to solve the issue?

 // Calling method
 using (var scope = ContextFactory.ScopeFactory.Create())
        {
            using (var context = GetContext()) // Here the context.Database.Connection is correct
            {
                EFType myObject = this.ToEF();//Get an EF Object 
                myObject = Repository.Insert(myObject);
            }
        }

    public BDAL_DemoEntities GetContext()
    {
        string connStr = "metadata=res://*/Model.BDAL_DemoModel.csdl|res://*/Model.BDAL_DemoModel.ssdl|res://*/Model.BDAL_DemoModel.msl;provider=System.Data.SqlClient;provider connection string=\"data source=.;initial catalog=BDAL_Demo;integrated security=True;MultipleActiveResultSets=True;App=EntityFramework\"";

       BDAL_DemoEntities cntxt = new BDAL_DemoEntities(connStr);
       return cntxt;

        // To use the default connection string from app/web.config 
        return new BDAL_DemoEntities();
    }

    // The issue is in the Repository.Insert(). The ambientContextLocator
    // returns a context that throws an exception as there is no connection String in the app.config
    var context = _ambientDbContextLocator.Get<context>();
BloodBaz commented 8 years ago

I have a similar problem. My connection string is a function of a named app.config connection string AND another parameter which I have my own interface/implementation that provides this (IConnectionStringResolver). I want to inject it as follows but AmbientDbContextLocator requires a parameterless constructor for my DbContext-derived class:

public class TaskManagementDbContext : DbContext
{
    public TaskManagementDbContext(IConnectionStringResolver csr) :
        base(csr.GetConnectionString("Default"))
    {
    }
}

This is fine when I use NInject to instantiate the DbContext directly but when trying to use with DbContextScope, it is AmbientDbContextLocator that is doing the instantiation and it throws a MissingMethodException because it requires my DBContext-derived class to have a parameterless constructor:

public class TaskRepository : ITaskRepository
{
    private readonly IAmbientDbContextLocator _ambientDbContextLocator;

    private TaskManagementDbContext DbContext
    {
        get
        {
            // MissingMethodException thrown "No parameterless constructor defined for this object"
            var dbContext = _ambientDbContextLocator.Get<TaskManagementDbContext>();
            ...
        }
    }

Is there a work-around for this? Thanks!

ellern commented 8 years ago

@BloodBaz the reason it's not working, is probably because you didn't register a IDbContextFactory. Inside DbContextCollection.cs it checks if the factory is registered with the following code:

 var dbContext = _dbContextFactory != null 
   ? _dbContextFactory.CreateDbContext<TDbContext>()
   : Activator.CreateInstance<TDbContext>();

If it's not registered it will use the Activator.CreateInstance, but instead you want it to use your DI.

Whenever I need to use one of my DbContext I do the following:

public async Task<Foo> GetFooAsync(Guid id)
{
    using (var dbContextScope = _dbContextScopeFactory.CreateReadOnly())
    {
        return await dbContextScope.DbContexts.Get<FooDbContext>()
            .Foo
            .FirstOrDefaultAsync(q => q.Id == id);
    }
}

I'm using Simple Injector for my DI, here is the setup.

container.Register<IDbContextScopeFactory>(() => new DbContextScopeFactory(new SimpleInjectorDbContextFactory(container)), Lifestyle.Scoped);
internal class SimpleInjectorDbContextFactory : IDbContextFactory
{
    readonly Container _container;

    public SimpleInjectorDbContextFactory(Container container)
    {
        _container = container;
    }

    public TDbContext CreateDbContext<TDbContext>() where TDbContext : DbContext
    {
        return _container.GetInstance<TDbContext>();
    }
}
mitsbits commented 8 years ago

Here's a DbContextFactory that works wiith DbContextScope just fine. Connection string form config. https://github.com/mitsbits/Ubik/blob/master/Ubik.EF/DbContextFactory.cs

BloodBaz commented 8 years ago

@ellern Thanks for the pointers. This is very useful. @mitsbits Thanks for the example.