在同一项目中同时使用 AddDbContextFactory() 和 AddDbContext() 扩展方法

tom*_*dox 11 c# entity-framework dependency-injection ef-core-3.1 ef-core-5.0

我正在尝试使用EF Core 文档的 DbContext 配置部分中DbContextFactory讨论的新模式。

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

但是,当我尝试这样做时,出现以下错误:

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

在试验时,我还设法在某一时刻得到了这个错误:

无法从根提供程序解析范围服务“Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]”。

它是理论上可以同时使用AddDbContextAddDbContextFactory在一起?

tom*_*dox 14

是的,这完全是关于了解游戏中各种元素的生命周期并正确设置它们。

默认情况下,DbContextFactoryAddDbContextFactory()扩展方法创建的具有单例生命周期。如果您使用AddDbContext()带有默认设置的扩展方法,它将创建一个DbContextOptions具有Scoped生命周期的对象(请参阅此处的源代码),并且由于Singleton不能使用Scoped生命周期较短的东西,因此会引发错误。

为了解决这个问题,我们需要将 的生命周期DbContextOptions也改为“单例”。这可以通过显式设置DbContextOptions参数的范围来完成AddDbContext()

services.AddDbContext<FusionContext>(options =>
    options.UseSqlServer(YourSqlConnection),
    contextLifetime: ServiceLifetime.Transient, 
    optionsLifetime: ServiceLifetime.Singleton);
Run Code Online (Sandbox Code Playgroud)

从这里开始的 EF 核心 GitHub 存储库上对此进行了非常好的讨论。也值得一看DbContextFactory 这里的源代码。

或者,您还可以DbContextFactory通过在构造函数中设置 ServiceLifetime 参数来更改 的生命周期:

services.AddDbContextFactory<FusionContext>(options => 
    options.UseSqlServer(YourSqlConnection), 
    ServiceLifetime.Scoped);
Run Code Online (Sandbox Code Playgroud)

这些选项的配置应与普通 DbContext完全一样,因为这些选项将在工厂创建的 DbContext 上设置。

  • 这个答案对于升级到 .NET 5 并希望在同一项目中使用 Blazor 和 MVC Core 的人非常有用,尽管我不知道 ApplyOurOptions 是什么。如果这不是我不知道的 EF 的一部分,也许可以考虑将其转换为使用普通选项? (2认同)
  • “AddDbContext”的示例代码具有“contextLifetime: ServiceLifetime.Transient”。这没有什么问题,但它与默认的范围不同。由于重点是选项生命周期,因此您可能希望在示例中将服务生命周期保留为默认值以避免混淆(即删除包含“contextLifetime”的行)。 (2认同)

Sim*_*ver 14

很重要的一点:

两者AddDbContextFactory都使用AddDbContext内部注册DbContextOptions<T>共享私有方法。(来源AddCoreServicesTryAdd

这实际上意味着代码中一个出现的那个就是被使用的那个。

所以你实际上可以这样做以获得更清晰的设置:

services.AddDbContext<RRStoreContext>(options => {

   // apply options

});

services.AddDbContextFactory<RRStoreContext>(lifetime: ServiceLifetime.Scoped);
Run Code Online (Sandbox Code Playgroud)

我用以下内容向自己证明它确实具有这样的功能:

services.AddDbContextFactory<RRStoreContext>(options =>
{
   throw new Exception("Oops!");  // this should never be reached

}, ServiceLifetime.Scoped);    
Run Code Online (Sandbox Code Playgroud)

不幸的是,我有一些不是线程安全的查询拦截器(这就是我想用工厂创建多个实例的全部原因),所以我想我需要创建自己的上下文工厂,因为我对 Context 和 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

注意:我只有一个上下文需要它,因此我在我的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>();  
Run Code Online (Sandbox Code Playgroud)

我将以警告结束。CreateDbContext如果除了“正常”注入的 DbContext 之外还使用工厂 ( ),请特别确保不要混合实体。例如,如果您在错误的上下文中调用 SaveChanges,那么您的实体将不会被保存。

  • 基本上花了一整天的时间来探索这个问题以及异步的一些相关问题。设法将需要 1.5 秒的操作节省 1 秒。它每天运行一次,这样我每年大约可以节省 6 分钟:-) (3认同)

Mar*_*der 7

从 EF Core 6 (.NET 6) 开始,在大多数情况下您不需要同时使用两者,因为AddDbContextFactory还将上下文类型本身注册为作用域服务。

Singleton因此,在具有生命周期注入的服务中IDbContextFactory<MyDbContext>,以及在具有生命周期的服务Scoped(例如MVC或API控制器)中MyDbContext直接注入。

DbContextOptions如果您想在每种情况下使用不同的或者如果Singleton生命周期DbContextOptions不符合您的需求,您必须采用其他答案提供的解决方案。

另请参阅:
相关 Github 增强问题