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

Use both AddDbContextFactory() and AddDbContext() extension methods in the same project

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

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

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

System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'.) ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory1[MyContext]': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'. ---> System.InvalidOperationException: Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext]'.

我在试验时也曾一度遇到此错误:

Cannot resolve scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' from root provider.

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

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

默认情况下,由 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. It's also well worth having a look at the source-code for 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 上设置。

重点:

AddDbContextFactoryAddDbContext 都使用 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>();  

最后我会发出警告。如果除了 'normal' 注入的 DbContext 之外还使用工厂 (CreateDbContext),请特别确保不要混合实体。例如,如果您在错误的上下文中调用 SaveChanges,那么您的实体将不会被保存。