重要点:
AddDbContextFactory
和AddDbContext
都会在内部使用TryAdd
来将DbContextOptions<T>
注册到共享的私有方法AddCoreServices
中。(来源)
这实际上意味着你代码中先调用哪个函数,就会使用哪一个。
因此,你实际上可以采用以下方式进行更简洁的设置:
services.AddDbContext<RRStoreContext>(options => {
});
services.AddDbContextFactory<RRStoreContext>(lifetime: ServiceLifetime.Scoped);
我正在使用以下内容来证明它确实像那样工作:
services.AddDbContextFactory<RRStoreContext>(options =>
{
throw new Exception("Oops!");
}, ServiceLifetime.Scoped);
很不幸,我的一些查询拦截器不是线程安全的(这也是我想用工厂创建多个实例的原因),所以我认为我需要制作自己的上下文工厂,因为我有单独的初始化上下文与上下文工厂。
编辑:最终我制作了自己的上下文工厂,以便为创建的每个新上下文创建新选项。唯一的原因是允许非线程安全的拦截器,但如果您需要类似的东西,则应该可以使用此方法。
受以下项目启发:DbContextFactory
public class SmartRRStoreContextFactory : IDbContextFactory<RRStoreContext>
{
private readonly IServiceProvider _serviceProvider;
public SmartRRStoreContextFactory(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public virtual RRStoreContext CreateDbContext()
{
var options = (DbContextOptions<RRStoreContext>) _serviceProvider.GetService(typeof(DbContextOptions<RRStoreContext>));
return new RRStoreContext(options);
}
}
注意:我只有一个需要这个的上下文,所以我在我的CreateDbContext
方法中硬编码了新的上下文。另一种替代方法是使用反射-类似于DbContextFactorySource。
然后在我的Startup.cs中:
services.AddDbContext<RRStoreContext>(options =>
{
var connection = CONNECTION_STRING;
options.UseSqlServer(connection, sqlOptions =>
{
sqlOptions.EnableRetryOnFailure();
});
options.AddInterceptors(new RRSaveChangesInterceptor());
}, optionsLifetime: ServiceLifetime.Transient);
services.AddDbContextFactory<RRStoreContext, SmartRRStoreContextFactory>();
最后我来发出一个警告。如果你在注入DbContext的同时使用工厂(CreateDbContext
),请特别注意不要混合实体。例如,如果你在错误的上下文中调用SaveChanges,则你的实体将无法保存。
ApplyOurOptions
是我们代码中的一个辅助函数,我已经更改了代码并添加了一些说明。 - tomRedoxAddDbContextFactory
,你无法指定optionsLifetime
与contextLifetime
不同(至少如果有的话请告诉我!)。连接池也存在类似的情况。 - Simon_WeaverAddDbContext
示例代码中有contextLifetime: ServiceLifetime.Transient
。这并没有什么问题,但它与默认值不同,即作用域为 scoped。由于重点是选项生命周期,你可能希望在示例中将服务生命周期保留为默认值以避免混淆(即删除具有contextLifetime
的行)。 - Edward Brey