在同一个项目中同时使用 AddDbContextFactory() 和 AddDbContext() 扩展方法
Posted
技术标签:
【中文标题】在同一个项目中同时使用 AddDbContextFactory() 和 AddDbContext() 扩展方法【英文标题】:Use both AddDbContextFactory() and AddDbContext() extension methods in the same project 【发布时间】:2021-03-09 08:58:46 【问题描述】:我正在尝试使用the DbContext configuration section of the EF Core docs 中讨论的新DbContextFactory
模式。
我已在我的 Blazor 应用程序中成功启动并运行 DbContextFactory
,但我想保留直接注入 DbContext
实例的选项,以保持现有代码正常工作。
但是,当我尝试这样做时,我遇到了如下错误:
System.AggregateException:某些服务无法 构造(验证服务描述符时出错 '服务类型: Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': 无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'。)---> System.InvalidOperationException:验证服务时出错 描述符'服务类型: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': 无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'。 ---> System.InvalidOperationException:无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'。
我在实验时也曾遇到过这个错误:
无法解析范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' 来自 根提供者。
理论上可以同时使用AddDbContext
和AddDbContextFactory
吗?
【问题讨论】:
【参考方案1】:是的,这完全是为了理解各种元素的生命周期并正确设置它们。
默认情况下,由AddDbContextFactory()
扩展方法创建的DbContextFactory
具有单例生命周期。如果您使用具有默认设置的AddDbContext()
扩展方法,它将创建具有Scoped
寿命(see the source-code here)的DbContextOptions
,并且Singleton
不能使用具有较短Scoped
寿命的东西,抛出错误。
为了解决这个问题,我们需要将DbContextOptions
的生命周期更改为“Singleton”。这可以通过显式设置AddDbContext()
的DbContextOptions
参数的范围来完成
services.AddDbContext<FusionContext>(options =>
options.UseSqlServer(YourSqlConnection),
contextLifetime: ServiceLifetime.Transient,
optionsLifetime: ServiceLifetime.Singleton);
关于on the EF core GitHub repository starting here 的讨论非常好。还值得一看 DbContextFactory
here 的源代码。
或者,您还可以通过setting the ServiceLifetime parameter in the constructor 更改DbContextFactory
的生命周期:
services.AddDbContextFactory<FusionContext>(options =>
options.UseSqlServer(YourSqlConnection),
ServiceLifetime.Scoped);
这些选项应该完全配置为as you would for a normal DbContext,因为这些选项将在工厂创建的 DbContext 上设置。
【讨论】:
这个答案对于升级到 .NET 5 并希望在同一个项目中使用 Blazor 和 MVC Core 的人非常有用,尽管我不知道 ApplyOurOptions 是什么。如果这不是我不知道的 EF 的一部分,也许考虑将其转换为使用普通选项? 嗨@BrianMacKay,这正是我们的情况。抱歉,ApplyOurOptions
是我们代码中的辅助函数,我已经更改了代码并添加了一些说明。
@tomRedux 我敢打赌,在 .NET 5 之前,您必须和我做同样的事情并弄清楚如何实现自己的 AddDbContextFactory 扩展。美好时光。 :)
一个警告:如果您单独使用options.AddInterceptors(...)
和AddDbContext
,您将为每个新的Scoped 上下文获得每个拦截器的新实例(因为拦截器是与选项一起创建的)。所以通常这意味着对于每个 Web 请求,您都会获得干净的拦截器。但是,如果您切换到使用AddDbContextFactory
,您现在将能够创建多个独立的上下文,但它们都将共享拦截器(通过共享选项)。如果您的拦截器是完全线程安全的,这很好,但如果不是,请小心!
...这是因为对于AddDbContextFactory
,您无法指定optionsLifetime
与contextLifetime
不同(至少如果有让我知道!)。池化也有类似的情况。【参考方案2】:
重点:
AddDbContextFactory
和 AddDbContext
在内部使用 TryAdd
在共享私有方法 AddCoreServices
中注册 DbContextOptions<T>
。 (source)
这实际上意味着代码中的任何一个第一个都是被使用的。
因此,您实际上可以这样做以获得更简洁的设置:
services.AddDbContext<RRStoreContext>(options =>
// apply options
);
services.AddDbContextFactory<RRStoreContext>(lifetime: ServiceLifetime.Scoped);
我正在使用以下内容向自己证明它确实具有这样的功能:
services.AddDbContextFactory<RRStoreContext>(options =>
throw new Exception("Oops!"); // this should never be reached
, ServiceLifetime.Scoped);
不幸的是,我有一些不是线程安全的查询拦截器(这就是我想用工厂创建多个实例的全部原因),所以我认为我需要创建自己的上下文工厂,因为我有单独的初始化用于 Context 与 ContextFactory。
编辑:我结束了你制作自己的上下文工厂,以便能够为创建的每个新上下文创建新选项。唯一的原因是允许非线程安全的拦截器,但如果你需要它或类似的东西,那么这应该可以工作。
受以下影响:DbContextFactory
public class SmartRRStoreContextFactory : IDbContextFactory<RRStoreContext>
private readonly IServiceProvider _serviceProvider;
public SmartRRStoreContextFactory(IServiceProvider serviceProvider)
_serviceProvider = serviceProvider;
public virtual RRStoreContext CreateDbContext()
// need a new options object for each 'factory generated' context
// because of thread safety isuess with Interceptors
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();
);
// this is not thread safe
options.AddInterceptors(new RRSaveChangesInterceptor());
, optionsLifetime: ServiceLifetime.Transient);
// add context factory, this uses the same options builder that was just defined
// but with a custom factory to force new options every time
services.AddDbContextFactory<RRStoreContext, SmartRRStoreContextFactory>();
我将以警告结束。如果您在“正常”注入的 DbContext 之外使用工厂 (CreateDbContext
),请特别确保不要混合实体。例如,如果您在错误的上下文中调用 SaveChanges,那么您的实体将不会被保存。
【讨论】:
这真的很有趣,谢谢。我会看看那个。我想我会做同样的事情,除了帮助提醒我发生了什么事! 基本上花了一整天的时间来探索这个和异步的一些周边问题。设法从需要 1.5 秒的操作中节省 1 秒。它每天运行一次,所以我每年节省大约 6 分钟 :-) 哇,谢谢。我很惊讶地看到这种行为。感觉 .NET 应该跟踪哪些选项用于不同的“请求者”;我很高兴,就我而言,无论是使用 ContextPool 还是 ContextFactory,选项都是相同的。 @Jarvis 它只是通过使用泛型类型名称DbContextOptions<MyContextName>
的依赖注入进行跟踪 - 因此无论工厂还是非工厂请求它,您都会获得相同的选项(并且范围决定是否不是共享副本)
嗯,当使用具有瞬态生命周期的 AddDbContext 时,它可能会泄漏类型。通常,作用域生命周期用于 DbContext,因为它在作用域结束后调用 dispose。但是当一个人使用瞬态时,用户必须调用我认为的处置,它可能会泄漏?以上是关于在同一个项目中同时使用 AddDbContextFactory() 和 AddDbContext() 扩展方法的主要内容,如果未能解决你的问题,请参考以下文章
在同一个项目中同时使用 Realm Swift 和 Realm Objective-C
我们可以在一个项目中同时使用 Java Spring mvc 和 Spring Boot 吗?
在项目中同时使用 bootstrap 和 material-UI