【问题标题】:Use both AddDbContextFactory() and AddDbContext() extension methods in the same project在同一个项目中同时使用 AddDbContextFactory() 和 AddDbContext() 扩展方法
【发布时间】:2021-03-09 08:58:46
【问题描述】:

我正在尝试使用the DbContext configuration section of the EF Core docs 中讨论的新DbContextFactory 模式。

我已在我的 Blazor 应用程序中成功启动并运行 DbContextFactory,但我想保留直接注入 DbContext 实例的选项,以保持现有代码正常工作。

但是,当我尝试这样做时,我遇到了如下错误:

System.AggregateException:某些服务无法 构造(验证服务描述符时出错 '服务类型: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': 无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'。)---> System.InvalidOperationException:验证服务时出错 描述符'服务类型: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': 无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'。 ---> System.InvalidOperationException:无法使用范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'。

我在实验时也曾遇到过这个错误:

无法解析范围服务 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' 来自 根提供者。

理论上可以同时使用AddDbContextAddDbContextFactory吗?

【问题讨论】:

    标签: c# entity-framework dependency-injection ef-core-3.1 ef-core-5.0


    【解决方案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,您无法指定optionsLifetimecontextLifetime 不同(至少如果有让我知道!)。池化也有类似的情况。
    【解决方案2】:

    重点:

    AddDbContextFactoryAddDbContext 在内部使用 TryAdd 在共享私有方法 AddCoreServices 中注册 DbContextOptions&lt;T&gt;。 (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&lt;MyContextName&gt; 的依赖注入进行跟踪 - 因此无论工厂还是非工厂请求它,您都会获得相同的选项(并且范围决定是否不是共享副本)
    • 嗯,当使用具有瞬态生命周期的 AddDbContext 时,它可能会泄漏类型。通常,作用域生命周期用于 DbContext,因为它在作用域结束后调用 dispose。但是当一个人使用瞬态时,用户必须调用我认为的处置,它可能会泄漏?
    猜你喜欢
    • 2022-01-11
    • 1970-01-01
    • 2013-02-21
    • 1970-01-01
    • 2015-03-21
    • 2014-05-25
    • 1970-01-01
    • 2019-05-04
    • 1970-01-01
    相关资源
    最近更新 更多