我正在使用PostgreSQL,我有ApplicationDbContext,如下所示:
public class ApplicationDbContext : DbContext
{
private readonly DatabaseSettings _databaseOptions;
public ApplicationDbContext() { }
public ApplicationDbContext(IOptions<DatabaseSettings> databaseOptions)
{
_databaseOptions = databaseOptions.Value;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.HasPostgresExtension("citext");
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (_databaseOptions == null)
{
optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
else
{
optionsBuilder.UseNpgsql(_databaseOptions.ConnectionString,
npgsqlOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: _databaseOptions.MaxRetryCount,
maxRetryDelay: TimeSpan.FromSeconds(_databaseOptions.MaxRetryDelay),
errorCodesToAdd: null);
});
}
}
}
这种背景是许多其他背景的基础。我正在提高性能并尝试使用上下文池。文档说要添加轮询,我应该:
services.AddDbContextPool<EmployeeContext>(options => options.UseNpgsql(connection));
但我想存储。在OnConfiguration方法中使用Npgsql和DbContext的其他配置。如何实现?
除了使用它有争议的好处(来自文档:">具有节省一些 DbContext 实例初始化成本的优点")之外,DbContext池根本不适用于你的方案,因为你的上下文包含 EF Core 不知道的状态:
private readonly DatabaseSettings _databaseOptions;
文档的"限制"部分明确指出:
警告!
如果在派生的 DbContext 类中维护自己的状态(例如,私有字段),而不应在请求之间共享,请避免使用 DbContext 池。EF Core 只会重置在将 DbContext 实例添加到池之前识别的状态。
需要optionsAction
AddDbContextPool
是有原因的,而对于AddDbContext
它是可选的。这是因为上述限制,以及DbContext
派生类必须具有具有单个DbContextOptions
参数的单个公共构造函数的额外要求。您可以通过传递空操作来愚弄AddDbContextPool
,从而轻松看到这一点:
services.AddDbContextPool<ApplicationDbContext>(options => { });
但是在运行时你会得到InvalidOperationException
说
类型为"ApplicationDbContext"的 DbContext 不能池化,因为它没有单个公共构造函数接受 DbContextOptions 类型的单个参数。
因此,为了有资格进行池化,您必须删除所有这些
private readonly DatabaseSettings _databaseOptions;
public ApplicationDbContext() { }
public ApplicationDbContext(IOptions<DatabaseSettings> databaseOptions)
{
_databaseOptions = databaseOptions.Value;
}
并添加此内容
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }
现在你应该清楚地看到为什么你所要求的是不可能的。您的OnConfiguring
方法需要DatabaseSettings
,但您无法提供它。因此,必须在外部配置options
。
换句话说,您的要求是相互排斥的,因此没有解决方案。
作为@Ivan Stoev答案的附录,如果有人需要一种方法为选项实例创建可重用的逻辑,然后用于初始化PooledDbContext
的配置,他们可以采取以下方法:
正如 Ivan 所澄清的那样,您的DbContext
本身必须只有一个接受DbContextOptions<TContext>
的构造函数。但是,没有什么可以阻止您在传递给AddDbContextPool()
方法Action<DbContextOptions<TContext>>
中创建自己的独立ConfigurationBuilder
。例如。。。
public class Program
{
static int Main(string[] args)
{
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((host, services) => {
// Build DatabaseSettings from config just like IOptions<T> would.
var config = host.Configuration;
var settings = new DatabaseSettings();
config.Bind(nameof(DatabaseSettings), settings);
// Use settings to configure pool.
services.AddPooledDbContextFactory<EmployeeContext>(optionsBuilder => {
if (settings.ConnectionString == null)
{
optionsBuilder.UseInMemoryDatabase(Guid.NewGuid().ToString());
}
else
{
optionsBuilder.UseNpgsql(settings.ConnectionString,
npgsqlOptionsAction: sqlOptions =>
{
sqlOptions.EnableRetryOnFailure(
maxRetryCount: settings.MaxRetryCount,
maxRetryDelay: TimeSpan.FromSeconds(settings.MaxRetryDelay),
errorCodesToAdd: null);
});
}
});
// Add example scoped configuration options
services.AddOptions<MyOptions>();
// Initialize each DbContext upon request
services.AddScoped<EmployeeContext>(srv => {
var factory = srv.GetRequiredService<IDbContextFactory<EmployeeContext>>();
var snapshot = srv.GetRequiredService<IOptionsSnapshot<MyOptions>>();
var ctx = factory.CreateDbContext();
// ctx.SomeProperty = snapshot.Value.SomeProperty;
return ctx;
});
}).Build();
}
}
如果您在使用池时确实需要按实例DbContext
配置,则可以遵循此处的文档建议。
通过服务集合访问DbContext
实例,并使用从池IDbContextFactory<TContext>
实例化DbContext
的注册工厂方法,然后可以对使用DbContext
的代码执行任何类型的初始化交接。当与IOptionsSnapshot<T>
或IOptionsMonitor<T>
结合使用以将实时配置数据应用于此初始化过程时,这尤其有用。