运行 时间的多个 DBContext 与多个提供者
Multiple DBContext at run time with multiple providers
我需要创建一个使用多个数据库提供程序的 dotnet 核心项目。一个使用 SqlServer 提供程序,另一个使用 MySql 提供程序。我不想同时添加 DbContext 和使用开关来确定他们需要在整个代码中使用哪个上下文。我工作的公司正在从 SQL 服务器迁移到 MySql,但这需要一年才能完成。
我想弄清楚如何创建一个我可以使用的通用 DbContext,它将 return 基于客户(需要数据库)的正确 DbContext。我在 dotnet 核心中这样做。
尝试做这样的事情:
services
.AddEntityFrameworkMySql()
.AddEntityFrameworkSqlServer()
.AddDbContext<GenericDbContext>();
您可以通过创建工厂来实现您的目标class。
假设您有一个接受 DbContextOptions<T>
:
的 DbContext class
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
).
我需要创建一个使用多个数据库提供程序的 dotnet 核心项目。一个使用 SqlServer 提供程序,另一个使用 MySql 提供程序。我不想同时添加 DbContext 和使用开关来确定他们需要在整个代码中使用哪个上下文。我工作的公司正在从 SQL 服务器迁移到 MySql,但这需要一年才能完成。
我想弄清楚如何创建一个我可以使用的通用 DbContext,它将 return 基于客户(需要数据库)的正确 DbContext。我在 dotnet 核心中这样做。
尝试做这样的事情:
services
.AddEntityFrameworkMySql()
.AddEntityFrameworkSqlServer()
.AddDbContext<GenericDbContext>();
您可以通过创建工厂来实现您的目标class。
假设您有一个接受 DbContextOptions<T>
:
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
).