【问题标题】:Multiple DBContext at run time with multiple providers运行时具有多个提供程序的多个 DBContext
【发布时间】:2021-08-17 18:39:42
【问题描述】:

我需要创建一个使用多个数据库提供程序的 dotnet 核心项目。一种使用 SqlServer 提供程序,另一种使用 MySql 提供程序。我不想同时添加 DbContext 并使用开关来确定他们需要在整个代码中使用哪个上下文。我工作的公司正在从 SQL Server 迁移到 MySql,但需要一年时间才能完成。

我想弄清楚如何创建一个我可以使用的通用 DbContext,它将根据客户(需要数据库)返回正确的 DbContext。我在 dotnet core 中这样做。

尝试做这样的事情:

services
    .AddEntityFrameworkMySql()
    .AddEntityFrameworkSqlServer()
    .AddDbContext<GenericDbContext>();

【问题讨论】:

  • 我认为您想要实现的是创建一些“工厂”,它将基于“客户”(无论它是什么)解析为 DbContext,对吧?
  • 正确,我想要一个可以根据客户在运行时实现的工厂。我忘了提到我们在 Dotnet Core 中使用的是 EF 3.0。

标签: .net asp.net-core .net-core ef-core-6-preview


【解决方案1】:

您可以通过创建工厂类来实现您的目标。

假设您有一个接受 DbContextOptions&lt;T&gt; 的 DbContext 类:

class AppDbContext: DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
    {
    }
}

我们可以复制 EF Core 如何在工厂内部注册和实例化 DbContext 并动态生成和提供选项。

我使用ConnectionConfiguration 作为连接信息的通用占位符。您可能更愿意将此信息存储在 Customer 实体中。

record ConnectionConfiguration(string Provider, string ConnectionString);

class DynamicDbContextFactory<TContext> where TContext: DbContext
{
    private readonly IServiceProvider _serviceProvider;
    private Func<IServiceProvider, DbContextOptions<TContext>, TContext> _factory;

    public DynamicDbContextFactory(IServiceProvider serviceProvider, IDbContextFactorySource<TContext> factorySource)
    {
        _serviceProvider = serviceProvider;
        _factory = factorySource.Factory;
    }

    public Task<TContext> CreateDbContextAsync(ConnectionConfiguration connection)
    {
        var builder = new DbContextOptionsBuilder<TContext>()
            .UseApplicationServiceProvider(_serviceProvider);
        builder = connection.Provider switch
        {
            "sqlite" => builder.UseSqlite(connection.ConnectionString),
            "npgsql" => builder.UseNpgsql(connection.ConnectionString),
            _ => throw new InvalidOperationException("No such provider")
        };
        var db = _factory(_serviceProvider, builder.Options);
        return Task.FromResult(db);
    }
}

这里我使用IDbContextFactorySource,它是一个没有支持保证的内部类型,在编译过程中会引发警告。但是你可以简单地复制its source code for finding a suitable constructor & creating a factory method

然后我们需要注册几个服务:

services.AddDbContext<AppDbContext>(db => db.UseInMemoryDatabase(nameof(AppDbContext)));
services.AddSingleton<IDbContextFactorySource<AppDbContext>, DbContextFactorySource<AppDbContext>>();
services.AddSingleton(typeof(DynamicDbContextFactory<>)); // register as open generic type and let DI close the type during resolution

然后我们可以从服务提供者那里解析这个工厂并动态创建一个实例:

class CustomerService
{
    private DynamicDbContextFactory<AppDbContext> _contextFactory;

    public CustomerService(DynamicDbContextFactory<AppDbContext> contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public async Task SaveThingsAsync()
    {
        // fetch connection info from somewhere
        var conn = new ConnectionConfiguration(Provider: "sqlite", ConnectionString: "Data Source=:memory:");
        await using var db = await _contextFactory.CreateDbContextAsync(conn);

        await db.Set<Sales>().AddAsync(new Sale( /*...*/));
        await db.SaveChangesAsync();
    }
}

注意:由于您是创建 DbContext 的人,因此您还应该在完成后将其处置(使用using 语句,或通过实现IDisposable)。

【讨论】:

  • 您使用哪个包进行 ConnectionConfiguration?
  • 这是我创建的一个类(实际上是record)。见代码:record ConnectionConfiguration(string Provider, string ConnectionString);
猜你喜欢
  • 2020-08-29
  • 1970-01-01
  • 2021-06-01
  • 2023-03-22
  • 2014-01-11
  • 2018-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多