janhartmann
janhartmann

Reputation: 15003

DbContext and scoped dependency

I have a simple DbContext looking like:

public class MyDbContext : DbContext
{
    private readonly IUserContext _userContext;

    public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
    {
        _userContext = userContext;

        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // ... Here I need to creates some filters using the IUserContext dependency

        base.OnModelCreating(modelBuilder);
    }

}

This DbContext is wired using Func<T> factory, using the guidelines in the Simple Injector documentation: container.RegisterFuncFactory<DbContext, MyDbContext>(Lifestyle.Scoped);

public static void RegisterFuncFactory<TService, TImpl>(
    this Container container, Lifestyle lifestyle = null)
    where TService : class
    where TImpl : class, TService
{
    lifestyle = lifestyle ?? Lifestyle.Transient;
    var producer = lifestyle.CreateProducer<TService, TImpl>(container);
    container.RegisterSingleton<Func<TService>>(producer.GetInstance);
}

But apperently, such simple case is not possible with the DbContext because of this message:

The target context 'MyDbContext' is not constructible. Add a default constructor or provide an implementation of IDbContextFactory.

I dont really like the idea of the IDbContextFactory, so the only solution I can come up with is to remove the dependency on the MyDbContext, set it as a property, modify the RegisterFuncFactory method and manually initialize the context:

internal static void RegisterFuncFactory<TService, TImpl>(this Container container, Func<TImpl> instanceProducer, Lifestyle lifestyle = null) where TService : class where TImpl : class, TService
{
    lifestyle = lifestyle ?? Lifestyle.Transient;
    var producer = lifestyle.CreateProducer<TService>(instanceProducer, container);
    container.Register<Func<TService>>(() => producer.GetInstance, Lifestyle.Singleton);
}

container.RegisterFuncFactory<DbContext, MyDbContext>(() => new MyDbContext
{
    UserContext = container.GetInstance<IUserContext>()
}, Lifestyle.Scoped);

While not elegant it works, but is there another and "better" way of doing what I need? I like the explicitly of the dependency on the context, but seem not possible.

UPDATE

The error is coming from:

'System.Data.Entity.Migrations.Infrastructure.MigrationsException' occurred in EntityFramework.dll but was not handled in user code

On this code the return statement of the Query method here:

internal sealed class EntityFrameworkRepository<TEntity> : IEntityWriter<TEntity>, IEntityReader<TEntity> where TEntity : Entity
{
    private readonly Func<DbContext> _contextProvider;

    public EntityFrameworkRepository(Func<DbContext> contextProvider)
    {
        _contextProvider = contextProvider;
    }

    public IQueryable<TEntity> Query()
    {
        var context = _contextProvider();
        return context.Set<TEntity>().AsNoTracking();
    }

    // Methods removed for brevity

}

Upvotes: 2

Views: 657

Answers (2)

janhartmann
janhartmann

Reputation: 15003

This answer is only to display for further users what I ended up with. @Steven answer is the right answer.

In order to be able to inject dependencies into the the DbContext while supporting migrations, we have to use two constructors. One for the migrations and one for the application.

public class MyDbContext : DbContext
{
    private readonly IUserContext _userContext;

    // For migrations
    public MyDbContext() : base("DefaultConnectionString")
    {
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
    }

    // For applications
    public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
    {
        _userContext = userContext; 
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // ... Code removed for brevity

        base.OnModelCreating(modelBuilder);
    }
}

This is then wired in the composition root like:

public static void RegisterEntityFramework<TContext>(this Container container, Func<TContext> context) where TContext : DbContext
{
    if (container == null) throw new ArgumentNullException(nameof(container));

    var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(context, container);
    container.RegisterSingleton<Func<DbContext>>(() => contextProducer.GetInstance);
}

var userContext = new AspNetHttpUserContext();
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.RegisterSingleton<IUserContext>(userContext);
container.RegisterEntityFramework(() => new WayFinderDbContext(userContext));
container.Verify();

Upvotes: 0

Steven
Steven

Reputation: 172835

Add a second (default) constructor. This way EF migrations can use this constructor when run from the command line, while you can let your application use the second constructor.

You loose Simple Injector's auto-wiring capabilities on your DbContext when you add this second constructor, but this shouldn't be a problem; you can simply wire your context as follows:

IUserContext userContext = new AspNetUserContext();

container.RegisterSingleton<IUserContext>(userContext);

var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(
    () => new MyDbContext(userContext),
    container);

container.RegisterSingleton<Func<DbContext>>(contextProducer.GetInstance);

Upvotes: 2

Related Questions