DbContext 和作用域依赖
DbContext and scoped dependency
我有一个简单的 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>
工厂接线,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);
}
但显然,DbContext
无法实现这种简单的情况,因为这条消息:
The target context 'MyDbContext' is not constructible. Add a default
constructor or provide an implementation of 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);
虽然不优雅但它可以工作,但是有另一种 "better" 方法来做我需要的吗?我喜欢明确地依赖于上下文,但似乎不可能。
更新
错误来自:
'System.Data.Entity.Migrations.Infrastructure.MigrationsException'
occurred in EntityFramework.dll but was not handled in user code
在此代码中,查询方法的 return 语句位于此处:
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
}
添加第二个(默认)构造函数。这样 EF 迁移可以在命令行 运行 时使用此构造函数,而您可以让您的应用程序使用第二个构造函数。
当您添加第二个构造函数时,您在 DbContext
上失去了 Simple Injector 的自动装配功能,但这应该不是问题;你可以简单地连接你的上下文如下:
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();
我有一个简单的 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>
工厂接线,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);
}
但显然,DbContext
无法实现这种简单的情况,因为这条消息:
The target context 'MyDbContext' is not constructible. Add a default constructor or provide an implementation of 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);
虽然不优雅但它可以工作,但是有另一种 "better" 方法来做我需要的吗?我喜欢明确地依赖于上下文,但似乎不可能。
更新
错误来自:
'System.Data.Entity.Migrations.Infrastructure.MigrationsException' occurred in EntityFramework.dll but was not handled in user code
在此代码中,查询方法的 return 语句位于此处:
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
}
添加第二个(默认)构造函数。这样 EF 迁移可以在命令行 运行 时使用此构造函数,而您可以让您的应用程序使用第二个构造函数。
当您添加第二个构造函数时,您在 DbContext
上失去了 Simple Injector 的自动装配功能,但这应该不是问题;你可以简单地连接你的上下文如下:
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();