如果所有的配置都在DbContext的OnConfiguring方法中,如何使用AddDbContextPool?

12

我正在使用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));

但是我想在OnConfiguring方法中使用UseNpgsql和DbContext的其他配置。如何实现?


你正在运行什么类型的应用程序?它是一个ASP.NET应用程序吗? - jpgrassi
是的,ASP.NET WebAPI。 - Pr.Dumbledor
2个回答

16

除了有争议的使用好处(来自文档:"具有节省初始化DbContext实例成本的优点")之外,DbContext池化在您的情况下根本不适用,因为您的上下文包含EF Core不知道的状态

private readonly DatabaseSettings _databaseOptions;

文档中的Limitations章节清楚地指出:

警告!

如果您在派生的 DbContext 类中维护了自己的状态(例如私有字段),并且这些状态不应在请求之间共享,请避免使用 DbContext 池。EF Core 将仅重置在将 DbContext 实例添加到池之前知道的状态。


AddDbContextPooloptionsAction 是必需的,而对于 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必须在外部配置。

换句话说,你的需求是互相排斥的,因此没有解决方案。


我该如何更改它? - Pr.Dumbledor
正如我所提到的,像你这样的上下文不符合上下文池的条件。换句话说,只需使用 AddDbContext - Ivan Stoev
如果您需要这些“DatabaseSettings”,则无法使用上下文池。上下文池需要具有单个公共构造函数且带有“DbContextOptions”(或“DbContextOptions<TContext>”)参数的db上下文。因此,您必须删除“DatabaseSettings”参数,但是那样您将不知道如何在“OnConfiguring”中配置上下文,因此必须在外部进行配置。正如您所看到的,问题不在于“AddDbContextPool”调用(可以轻松地使用空操作调用它),而在于您的要求组合没有解决方案。 - Ivan Stoev
我不会说这个问题没有解决方案 - 至少在某种程度上是这样的。@Pr.Dumbledor,虽然@Ivan Stoev关于一切都是正确的,但我认为这取决于您是否可以妥协并在外部设置DatabaseSettings选项,在何时何地创建数据库上下文,您只需检查DatabaseSettings选项是否不存在(传递给构造函数),并创建一个内存中的而不是真正的Psql数据库连接? MaxRetryCountMaxRetryDelay始终相同吗? - eja
1
@eja 但它们包含了最重要的信息 - 连接字符串。如果没有这个,基本上在 OnConfiguring 中什么也做不了。这正是 OP 在帖子末尾想要的 - "但我想将 DbContext 的 UseNpgsql 和其他配置存储在 OnConfiguring 方法中。如何实现?" 对我来说,这听起来像是“无解” :) - Ivan Stoev
@IvanStoev 我明白了。无论如何,“如何更改它”和“如何为上下文池更改它”的评论都让我想到了什么。我同意,使用上下文池的唯一方法是,如果 OP 可以将 DatabaseSettingsOnConfiguring 覆盖移出 ApplicationDbContext。我不确定我是否漏掉了什么,他不能在 services.AddDbContextPool() 中检查连接字符串,并在内存或 Psql 上下文中创建一个吗?是的,只有在可以删除 OnConfiguring 的情况下才可以这样做..? @Pr.Dumbledor - eja

2
作为对@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>结合使用以将实时配置数据应用于此初始化过程时,这非常有用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接