Entity Framework Core如何在同一个DBContext中使用多个连接字符串?

21

我有一个使用Entity Framework Core的Asp.Net Core应用程序,我按以下方式进行初始化:

services.AddDbContext<ApplicationDbContext>(options => 
         options.UseSqlServer(sqlConnectionString));

这个方案虽然可以正常运作,但我有一个场景需要在正常操作时从主数据库读写,但有些操作需要从备用服务器读取(一个只读的复制目标,我们用于报告)。

由于新的Core API通过Dependency Injection和StartUp.cs中的配置完成所有操作,因此我该如何切换连接字符串,但仍使用同一个ApplicationDbContext类呢?

我知道一种选项是创建另一个ApplicationDbContext类的副本,并使用不同的连接字符串将其注册到DI系统中,但我想避免维护两个完全相同的DBContext对象,因为有时我需要从不同的数据库服务器(但具有完全相同的模式)读取。

非常感谢任何提示!


我有点困惑,但您只是想能够选择要连接的数据库,对吗?并且这不会在应用程序运行时更改?如果是这样,那么在 options.UseSqlServer 之前加一个条件是否就足够了呢?例如:if(Environment.GetEnvironmentVariable("FIRSTENVIRONMENT") options.UseSqlServer("firstEnvironementConnectionString") else options.UseSqlServer("secondEnvironementConnectionString") - Adrian
那个五分钟的编辑真是让人头疼,不管怎样,修复后的代码在这里。 if(Environment.GetEnvironmentVariable("FIRSTENVIRONMENT") == "environmentString") options.UseSqlServer("firstEnvironementConnectionString") else options.UseSqlServer("secondEnvironementConnectionString") - Adrian
那会在应用程序级别设置连接字符串,我需要在方法级别设置它。换句话说,一行代码写入数据库A,下一行代码从数据库B读取。两者使用相同的模式,只是操作不同的数据库。 - user1142433
4个回答

37

你需要两个DbContext。

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }
}

public class MyBloggingContext : BloggingContext
{

}

public class MyBackupBloggingContext : BloggingContext
{

}

您可以这样注册它们:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<MyBloggingContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

    services.AddDbContext<MyBackupBloggingContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("BackupConnection")));

}

该死!太明显了。我一直在尝试通过EF API解决它,而不是使用继承。谢谢! - user1142433
5
以上方法从来不起作用,因为我们需要传递参数化构造函数给MyBloggingContext和MyBackUpBloggingContext。 - Siddharth
上述方法适用于将泛型T添加到基础上下文中,其中T是新的子上下文。 - Hyperdingo
1
@Siddharth 如果参数是 DbContextOptions<xxx>,你可以在基本上下文中使用 DbContextOptions options,这样就不会有问题了。如果您需要有序参数,那么您如何处理依赖注入? - sgt_S2
3
@Siddharth是正确的。为了使其工作,我们需要这样做:仅在基本(BloggingContext)构造函数中使用DbContextOptions(而不是DbContextOptions<BloggingContext>),在MyBloggingContext构造函数中使用DbContextOptions<MyBloggingContext>,在MyBackupBloggingContext构造函数中使用DbContextOptions<MyBackupBloggingContext>。希望这可以帮助大家。 - tala9999

10

可以这样做(在 .NET Core 3.1 上测试过):

public abstract partial class BloggingContext<T> : DbContext where T : DbContext
{
    private readonly string _connectionString;
    protected BloggingContext(string connectionString) { _connectionString = connectionString; }
    protected BloggingContext(DbContextOptions<T> options) : base(options) { }

    public virtual DbSet<Blog> Blogs { get; set; }
    public virtual DbSet<Post> Posts { get; set; } 

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        if (!optionsBuilder.IsConfigured)
        {
            optionsBuilder.UseSqlServer(_connectionString);
        }
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
    ...
    }
}

public class MyBloggingContext : BloggingContext<MyBloggingContext>
{
    public MyBloggingContext(string connectionString) : base(connectionString) { }
    public MyBloggingContext(DbContextOptions<MyBloggingContext> options) : base(options) { }
}

public class MyBackupBloggingContext : BloggingContext<MyBackupBloggingContext>
{
    public MyBackupBloggingContext(string connectionString) : base(connectionString) { }
    public MyBackupBloggingContext(DbContextOptions<MyBackupBloggingContext> options) : base(options) { }
}

在 Startup.cs 文件中

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<MyBloggingContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
    
    services.AddDbContext<MyBackupBloggingContext>(options =>
        options.UseSqlServer(Configuration.GetConnectionString("BackupConnection")));

}

在公共类调用中,我遇到了“连接字符串不可访问,因为其保护级别过高”、“方法必须具有返回类型”、“不包含接受0个参数的构造函数”和“无法解析符号T”的错误。 - Joseph Norris

7

使用 IServiceProvider 可以解析连接字符串。在下面的示例中,我将查询参数映射到来自 appsettings.json 配置,但您也可以注入任何其他逻辑。

services.AddDbContext<ApplicationDbContext>((services, optionsBuilder) =>
{
    var httpContextAccessor = services.GetService<IHttpContextAccessor>();
    var requestParam = httpContextAccessor.HttpContext.Request.Query["database"];

    var connStr = Configuration.GetConnectionString(requestParam);

    optionsBuilder.UseSqlServer(connStr);
});

?database=Connection1?database=Connection2在查询中会导致使用不同的连接字符串。当参数缺失时,提供默认值是值得的。


0

可以通过以下方式解决

public class AppDbContext : DbContext
{
    private string _connectionString { get; }

    public AppDbContext(string connectionString, DbContextOptions<AppDbContext> options) : base(options)
    {
        _connectionString = connectionString;
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(_connectionString);
    }
}

然后手动创建DbContext
var appDbContext = new AppDbContext("server=localhost;database=TestDB;Trusted_Connection=true", new DbContextOptions<AppDbContext>());

不要硬编码连接,而是从连接字符串工厂中读取。


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