实体框架核心中的数据库独立性

时间:2019-11-08 11:01:53

标签: c# entity-framework-core entity-framework-core-2.2

我有一个使用postgres的简单WPF / EF Core 2.2.4应用程序。
我正在分析在SQL Server上迁移它的可能策略。

在非ORM应用程序中,很常见的是将特定于数据库的引用限制为连接字符串,从而提供一种动态加载数据库驱动程序的方式(JDBC模型的思想)。在该模型中,您必须解决编写跨数据库工作的SQL的问题。

实际上,最主要的问题是SQL编写问题,从一开始就解决了。因此,我觉得有点荒谬的是,我们以辅助方法的形式重新引入了数据库依赖性。

我的第一个问题是关于DbContext。 OnConfiguring接收用于传递连接字符串的DbContextOptionsBuilder。 但是为了传递连接字符串,您使用了数据库提供程序提供的扩展方法提供的特定于数据库的方法。 在下面的示例中就是optionsBuilder.UseNpgsql(connstr)。 我应该如何在独立于数据库的应用程序中解决这个问题?

class MyDbContext: DbContext
{

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {

    string connstr = 
            ConfigurationManager
            .ConnectionStrings["MYAPP_PROD"].ConnectionString;

    optionsBuilder.UseNpgsql(connstr);

    }

}

第二个问题是:我如何以动态方式加载整个数据库包,以便可以配置而不是对其进行编码? 实际上,我使用NuGet来获取软件包:

Npgsql.EntityFrameworkCore.PostgreSQL

说我要使用:

Microsoft.EntityFrameworkCore.SqlServer

这怎么办?

2 个答案:

答案 0 :(得分:1)

使用策略模式基于外部配置注册相关的数据库提供程序。

interface IDbProvider {
    bool AppliesTo(string providerName);
    DbContextOptions<T> LoadProvider<T>();
}
public class PostgresSqlProvider : IDbProvider {

    public bool AppliesTo(string providerName) {
        return providerName.Equals("Postgres");
    }

    public DbContextOptions<T> LoadProvider<T>() {
        //load provider.
    }
}
var providers = new [] {
    new PostgresSqlProvider()
};
var selectedDbProvider = ""; //Load from user input / config

var selectedProvider = providers.SingleOrDefault(x => x.AppliesTo(selectedDbProvider));
if(selectedProvider == null) {
    throw new NotSupportedException($"Database provider {selectedDbProvider} is not supported.");
}

var options = selectedProvider.LoadProvider<DbContext>();

答案 1 :(得分:1)

此方案已被EF Core涵盖。应该使用接受构建器操作的任何Startup.ConfigureServices方法,在AddDbContext中配置提供程序。

在最简单的情况下(最脏吗?),您可以基于配置系统本身的标志或值来选择提供程序,例如:

public void ConfigureServices(IServiceCollection services)
{
    services.AddRazorPages();

    var connString=Configuration.GetConnectionString("SchoolContext");  
    var useSqlServer=Configuration.GetSection("MyDbConfig").GetValue<bool>("UseSqlServer");
    services.AddDbContext<SchoolContext>(options =>{
        if (useSqlServer)
        {
            options.UseSqlServer(connString);
        }
        else 
        {
            options.UseNpgsql(connString);
        }
    });
}

var provider=Configuration.GetSection("MyDbConfig").GetValue<ProviderEnum>("Provider");
services.AddDbContext<SchoolContext>(options =>{
    switch (provider)
    {
        case ProviderEnum.SqlServer:
             options.UseSqlServer(connString);
             break;
        case ProviderEnum.Postgres :
             options.UseNpgsql(connString);
             break;
        ...
    }
});

该标志也可以来自配置,例如来自命令行,环境变量等。

重构为....很多

扩展方法

可以将此代码提取到IServiceCollection上的扩展方法中,类似于其他上下文,例如:

public static ConfigureContexts(this IServiceCollection services,string connString, string provider)
{
    services.AddDbContext<SchoolContext>(options =>{
        switch (provider)
        {
            case ProviderEnum.SqlServer:
                 options.UseSqlServer(connString);
                 break;
            case ProviderEnum.Postgres :
                 options.UseNpgsql(connString);
                 break;
            ...
        }
    });
}

并使用过:

var connString=Configuration.GetConnectionString("SchoolContext");  
var provider=Configuration.GetSection("MyDbConfig").GetValue<ProviderEnum>("Provider");
services.ConfigureContexts(provider,connString);

构建器选择器

构建器的配置模式允许许多可以处理复杂方案的变体。例如,我们可以提前选择一个构建器方法:

var efBuilder= SelectBuilder(provider,connString);
services.AddDbContext<SchoolContext>(efBuilder);

...

Action<DbContextOptionsBuilder> SelectBuilder(ProviderEnum provider,string connString)
{
    switch (provider)
    {
        case ProviderEnum.SqlServer:
           return ConfigureSql;
        case ProviderEnum.Postgres :
           return ConfigurePostgres;
    }

    void ConfigureSqlServer(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(connString);
    }

    void ConfigurePostgres(DbContextOptionsBuilder options)
    {
        options.UseNpgSql(connString);
    }
}

在C#8中,这可以简化为:

Action<DbContextOptionsBuilder> SelectBuilder(ProviderEnum provider,string connString)
{
    return provider switch (provider) {
        ProviderEnum.SqlServer => ConfigureSql,
        ProviderEnum.Postgres => ConfigurePostgres
    };

    void ConfigureSqlServer(DbContextOptionsBuilder options)
    {
        options.UseSqlServer(connString);
    }

    void ConfigurePostgres(DbContextOptionsBuilder options)
    {
        options.UseNpgSql(connString);
    }
}

混凝土配置类

另一种可能性是创建一个强类型的配置类,并让它 提供构建器:

class MyDbConfig
{
    public ProviderEnum Provider {get;set;}
    ....
    public Action<DbContextOptionsBuilder> SelectBuilder(string connString)
    {
        return provider switch (provider) {
            ProviderEnum.SqlServer => ConfigureSql,
            ProviderEnum.Postgres => ConfigurePostgres
        };

        void ConfigureSqlServer(DbContextOptionsBuilder options)
        {
            options.UseSqlServer(connString);
        }

        void ConfigurePostgres(DbContextOptionsBuilder options)
        {
            options.UseNpgSql(connString);
        }
    }

}

并使用它:

var dbConfig=Configuration.Get<MyDbConfig>("MyDbConfig");
var efBuilder=dbCongig.SelectBuilder(connString);
services.AddDbContext<SchoolContext>(efBuilder);