在同一项目中同时使用 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.DbContextFactory
1[MyContext]':
Cannot consume scoped service
'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'.) --->
System.InvalidOperationException: Error while validating the service
descriptor 'ServiceType:
Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]':
Cannot consume scoped service
'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'. --->
System.InvalidOperationException: Cannot consume scoped service
'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'.
我在试验时也曾一度遇到此错误:
Cannot resolve scoped service
'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' from
root provider.
理论上可以同时使用AddDbContext
和AddDbContextFactory
吗?
是的,所有这些都是关于了解游戏中各种元素的生命周期并正确设置它们。
默认情况下,由 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 上设置。
重点:
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>();
最后我会发出警告。如果除了 'normal' 注入的 DbContext 之外还使用工厂 (CreateDbContext
),请特别确保不要混合实体。例如,如果您在错误的上下文中调用 SaveChanges,那么您的实体将不会被保存。
我正在尝试使用 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.IDbContextFactory
1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'.) ---> System.InvalidOperationException: Error while validating the service descriptor 'ServiceType: Microsoft.EntityFrameworkCore.IDbContextFactory1[MyContext] Lifetime: Singleton ImplementationType: Microsoft.EntityFrameworkCore.Internal.DbContextFactory
1[MyContext]': Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'. ---> System.InvalidOperationException: Cannot consume scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions1[MyContext]' from singleton 'Microsoft.EntityFrameworkCore.IDbContextFactory
1[MyContext]'.
我在试验时也曾一度遇到此错误:
Cannot resolve scoped service 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' from root provider.
理论上可以同时使用AddDbContext
和AddDbContextFactory
吗?
是的,所有这些都是关于了解游戏中各种元素的生命周期并正确设置它们。
默认情况下,由 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 上设置。
重点:
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>();
最后我会发出警告。如果除了 'normal' 注入的 DbContext 之外还使用工厂 (CreateDbContext
),请特别确保不要混合实体。例如,如果您在错误的上下文中调用 SaveChanges,那么您的实体将不会被保存。