DbContext和作用域依赖

本文关键字:依赖 作用域 DbContext | 更新日期: 2023-09-27 18:02:11

我有一个简单的DbContext看起来像:

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);
    }
}

这个DbContext是使用Func<T>工厂连接的,使用简单注入器文档中的指导方针: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);
}

但显然,这样简单的情况是不可能的DbContext,因为这条消息:

目标上下文'MyDbContext'是不可构造的。添加默认值构造函数或提供IDbContextFactory的实现。

我真的不喜欢IDbContextFactory的想法,所以我能想出的唯一解决方案是删除对MyDbContext的依赖,将其设置为属性,修改RegisterFuncFactory方法并手动初始化上下文:

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);

虽然不优雅,但是否有另一种"更好"的方法来做我需要的?我喜欢对上下文的显式依赖,但似乎不可能。

错误来自:

"System.Data.Entity.Migrations.Infrastructure.MigrationsException"发生在EntityFramework.dll中,但未在用户代码中处理

在这段代码中,Query方法的返回语句如下:
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
}

DbContext和作用域依赖

添加第二个(默认)构造函数。这样,EF迁移可以在从命令行运行时使用这个构造函数,而您可以让应用程序使用第二个构造函数。

当你添加第二个构造函数时,你在DbContext上失去了简单注入器的自动连接能力,但这应该不是问题;您可以简单地将上下文连接如下:

IUserContext userContext = new AspNetUserContext();
container.RegisterSingleton<IUserContext>(userContext);
var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(
    () => new MyDbContext(userContext),
    container);
container.RegisterSingleton<Func<DbContext>>(contextProducer.GetInstance);

这个答案只是为了向更多的用户显示我最终得到的结果。@Steven的答案是正确的。

为了能够在支持迁移的同时将依赖项注入DbContext,我们必须使用两个构造函数。一个用于迁移,一个用于应用程序。

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);
    }
}

然后在组合根中连接,如:

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();