允许最终用户在运行时切换Entity Framework提供程序

8

考虑到我已经在.NET Core Web应用程序中配置了EF:

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

我也可以下载一个包来支持SQLite等功能:

services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(...));

如何在应用程序安装时允许用户选择供应商?我的意思是,例如,在WordPress中,您可以从下拉列表中选择。

.NET Core是否支持此功能?我所能看到的唯一方法就是仅重新启动应用程序...


使用案例是什么?从我的角度来看,这似乎毫无意义。对于 CMS 或博客,您永远不会“只是”更改数据库类型而不将应用程序关闭/重新启动。 - Tseng
是的 - 但在“第一次安装”时,您可以选择数据库提供程序。 我想允许客户决定使用哪个提供程序 - 而不修改源代码。 - pwas
2
虽然从应用程序内部编辑project.json并重新启动应用程序在技术上是可能的,但我认为从安全角度来看这不是一个明智的选择。在WordPress中,安装脚本会被删除,而在编译系统中要做到这一点就更困难了,如果将其留在那里,会带来安全问题。当然,没有人强迫您在启动时使用AddDbContext并通过抽象工厂解决您的DB上下文,后者读取配置值并手动选择正确的提供程序进行构造,但是这样您将需要自己管理其生命周期(即处理其释放)。 - Tseng
为什么要使用相同的数据库上下文?您可以拥有两个具有相同数据库集属性的数据库上下文,并根据用户选择使用。 - H. Herzl
1
感谢您的评论!使用两个DB上下文似乎是合理的,但我很好奇当配置了两个提供程序时会发生什么。在web.config中的连接字符串中可以指定提供程序类型,但我不确定appsettings.json是否也可以这样做。 - pwas
2个回答

4
这里有一个示例,展示了如何实现一个 DbContextFactoryDbContextProxy<T>,它将创建正确的提供程序并返回它。
public interface IDbContextFactory
{
    ApplicationContext Create();
}

public class DbContextFactory() : IDbContextFactory, IDisposable
{
    private ApplicationContext context;
    private bool disposing;

    public DbContextFactory()
    {
    }

    public ApplicationContext Create() 
    {
        if(this.context==null) 
        {
            // Get this value from some configuration
            string providerType = ...;
            // and the connection string for the database
            string connectionString = ...;

            var dbContextBuilder = new DbContextOptionsBuilder();
            if(providerType == "MSSQL") 
            {
                dbContextBuilder.UseSqlServer(connectionString);
            }
            else if(providerType == "Sqlite")
            {
                dbContextBuilder.UseSqlite(connectionString);
            }
            else 
            {
                throw new InvalidOperationException("Invalid providerType");
            }

            this.context = new ApplicationContext(dbContextBuilder);
        }

        return this.context;
    }

    public void Dispose(){
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected virtual void Dispose(bool disposing){
        if (disposing){
            disposing?.Dispose();
        }
    }
}

同时,确保您按照上面所示实现可处理模式,以便在工厂被处理时上下文也被处理,以防止DbContext在内存中停留的时间超过必要的时间,并尽快释放非托管资源。
最后将工厂注册为范围作用域,就像注册上下文本身一样:
services.AddScopedd<IDbContextFactory, DbContextFactory>();

一个更高级和通用/可扩展的方法是创建一个 IDbContextProxy<T> 类,它使用一些反射来获取正确的构造函数和 DbContextOptionsBuilder
还可以创建一个抽象提供程序创建的 IDbContextBuilder
public class SqlServerDbContextBuilder IDbContextBuilder
{
    public bool CanHandle(string providerType) => providerType == "SqlServer";

    public T CreateDbContext<T>(connectionString)
    {
        T context = ... // Create the context here

        return context;
    }
}

然后您可以通过以下方式选择正确的提供程序,而无需使用硬编码的 if/elseswitch 块:

// Inject "IEnumerable<IDbContextBuilder> builders" via constructor
var providerType = "SqlServer";
var builder = builders.Where(builder => builder.CanHandle(providerType)).First();
var context = builder.CreateDbContext<ApplicationContext>(connectionString);

添加新的提供程序就像添加依赖项和一个 XxxDbContextBuilder 类一样简单。

有关此类方法的更多信息,请参见 此处, 此处此处


谢谢,这看起来很不错! - pwas

0

我认为你可以使用使用你指定的数据库上下文的存储库,并向上下文构造函数传递参数以选择端点。我不确定这是否适用于你的情况。

我按照这篇文章来实现仓储模式,我建议你阅读一下 :)

http://cpratt.co/generic-entity-base-class/


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