简体   繁体   English

Entity Framework Core 中的数据库独立性

[英]database independence in Entity Framework Core

I have a simple WPF/EF Core 2.2.4 application that uses postgres.我有一个使用 postgres 的简单 WPF/EF Core 2.2.4 应用程序。
I'm analyzing the possible strategies to migrate it on SQL Server.我正在分析在 SQL 服务器上迁移它的可能策略。

In non ORM applications, it's quite common to limit the database-specific reference to the connection string togheter with providing a way to dynamically load the database driver (thinking of JDBC model).在非 ORM 应用程序中,通过提供动态加载数据库驱动程序的方法(考虑 JDBC 模型)来限制对连接字符串的特定于数据库的引用是很常见的。 In that model you have to address the problem of writing SQL that works cross databases.在 model 中,您必须解决编写跨数据库工作的 SQL 的问题。

Here the problem of SQL writing, actually the major one, is solved right from the start.这里SQL写的问题,其实是大问题,一开始就解决了。 So I find it quite paradoxical that we sort of reintroduce database dependency in the form of helper methods.所以我觉得我们以辅助方法的形式重新引入数据库依赖是很矛盾的。

My first question is about the DbContext.我的第一个问题是关于 DbContext。 The OnConfiguring receives a DbContextOptionsBuilder that is used to pass Connection string. OnConfiguring 接收一个用于传递连接字符串的 DbContextOptionsBuilder。 But in order to pass the connection string you use a database-specific method that is provided as an extension method by the database provider.但是为了传递连接字符串,您使用数据库提供程序作为扩展方法提供的特定于数据库的方法。 That is the optionsBuilder.UseNpgsql(connstr) in the following example.这就是以下示例中的optionsBuilder.UseNpgsql(connstr) How should I address this in a database-independend application?我应该如何在独立于数据库的应用程序中解决这个问题?

class MyDbContext: DbContext
{

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {

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

    optionsBuilder.UseNpgsql(connstr);

    }

}

The second question is: how can I load the entire database package in a dynamic way, so that I can manage to configure it instead of harcoding it?第二个问题是:如何以动态方式加载整个数据库 package,以便我可以设法对其进行配置而不是对其进行编码? Actually I use NuGet to get the package:实际上我使用 NuGet 来获取 package:

Npgsql.EntityFrameworkCore.PostgreSQL

Say that I want to use:说我想使用:

Microsoft.EntityFrameworkCore.SqlServer

How can this be done?如何才能做到这一点?

Use the strategy pattern to register the relevant database provider based on external configuration.使用策略模式根据外部配置注册相关的数据库提供者。

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>();

This scenario is already covered by EF Core. EF Core 已涵盖此方案。 Configuring providers should be done in Startup.ConfigureServices , using any of the AddDbContext methods that accept a builder action.配置提供者应该在Startup.ConfigureServices中完成,使用任何接受构建器操作的AddDbContext方法。

In the simplest case (dirtiest?), you can select providers based on a flag or a value that comes from the config system itself eg:在最简单的情况下(最脏?),您可以基于来自配置系统本身的标志或值的 select 提供程序,例如:

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);
        }
    });
}

or或者

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;
        ...
    }
});

That flag can come from configuration as well, eg from the command-line, environment variables, etc.该标志也可以来自配置,例如来自命令行、环境变量等。

Refactoring to.... lots重构为....很多

Extension method扩展方法

This code can be extracted to an extension method on IServiceCollection , similar to other contexts, eg:可以将此代码提取到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;
            ...
        }
    });
}

and used:并使用:

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

Builder picker生成器选择器

The builder, configuration patterns allow many variations that can handle complex scenarios.构建器、配置模式允许许多可以处理复杂场景的变体。 For example, we can pick a builder method in advance:例如,我们可以提前选择一个构建器方法:

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);
    }
}

In C# 8 this could be reduced to:在 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);
    }
}

Concrete config class具体配置 class

Another possibility is to create a strongly-typed configuration class and have it provide the builder:另一种可能性是创建一个强类型配置 class 并让它提供构建器:

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);
        }
    }

}

and use it:并使用它:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM