我们如何与EF 6 MSSQL和PostgresSQL交朋友

图片

曾几何时,有一个使用MSSQL DBMS的EF 6项目。 并且需要增加使用PostgreSQL的功能。 我们在这里没想到会出现问题,因为有大量关于此主题的文章,并且在论坛上您可以找到有关类似问题的讨论。 但是,实际上,并非所有事情都那么简单,在本文中,我们将讨论这种体验,在集成新提供程序时遇到的问题以及我们选择的解决方案。

介绍性


我们有一个盒装产品,并且它已经建立了结构。 最初,它被配置为与一个DBMS-MSSQL一起使用。 该项目具有一个采用EF 6实施(代码优先方法)的数据访问层。 我们通过EF 6迁移进行迁移。 迁移是手动创建的。 数据库的初始安装是通过控制台应用程序进行的,其中连接字符串上的上下文已初始化,并作为参数传递:

static void Main(string[] args) { if (args.Length == 0) { throw new Exception("No arguments in command line"); } var connectionString = args[0]; Console.WriteLine($"Initializing dbcontext via {connectionString}"); try { using (var context = MyDbContext(connectionString)) { Console.WriteLine("Database created"); } } catch (Exception e) { Console.WriteLine(e.Message); throw; } } 

同时,在另一个项目中描述了EF基础结构和域域,该项目作为库连接到控制台应用程序。 基础结构项目中的上下文构造函数如下所示:

 public class MyDbContext : IdentityDbContext<User, Role, Key, UserLogin, UserRole, UserClaim>, IUnitOfWork { public MyDbContext(string connectionString) : base(connectionString) { Database.SetInitializer(new DbInitializer()); Database.Initialize(true); } } 

首次发射


我们要做的第一件事是通过nuget将两个软件包连接到项目:Npgsql和EntityFramework6.Npgsql。

我们还在控制台应用程序的App.config中注册了Postgres的设置。

entityFramework部分将默认的postgres工厂指定为连接工厂:

 <entityFramework> <!--<defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />--> <defaultConnectionFactory type="Npgsql.NpgsqlConnectionFactory, EntityFramework6.Npgsql" /> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> <provider invariantName="Npgsql" type="Npgsql.NpgsqlServices, EntityFramework6.Npgsql" /> </providers> </entityFramework> 

在DbProviderFactories部分中,注册了新提供程序的工厂:

 <system.data> <DbProviderFactories> <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql" type="Npgsql.NpgsqlFactory, Npgsql" /> </DbProviderFactories> </system.data> 

并且,他们立即尝试通过在连接字符串中指定Postgres服务器的地址和服务器admin的凭据来初始化数据库。 结果是以下行:
“服务器=本地主机; 数据库= TestPostgresDB; 集成安全性=否; 用户ID = postgres; 密码= pa $$ w0rd”
不出所料,由于采用了手动EF迁移模式,初始化未通过,并且发生了与当前模型的数据库映像不匹配的错误。 为了避免使用新的提供程序创建主迁移并在Postgres上测试数据库初始化,我们对基础结构配置进行了一些调整。

首先,我们打开了“自动迁移”功能,这是一个开发人员对团队中的域模型和EF基础架构进行更改的有用选项:

 public sealed class Configuration : DbMigrationsConfiguration<MyDbContext> { public Configuration() { AutomaticMigrationsEnabled = true; ContextKey = "Project.Infrastructure.MyDbContext"; } } 

其次,我们在继承的类CreateDatabaseIfNotExists的重定义方法InitializeDatabase中指定了一个新的提供程序,在此我们开始迁移:

 public class DbInitializer : CreateDatabaseIfNotExists<MyDbContext> { public override void InitializeDatabase(MyDbContext context) { DbMigrator dbMigrator = new DbMigrator(new Configuration { //TargetDatabase = new DbConnectionInfo(context.Database.Connection.ConnectionString, "System.Data.SqlClient") TargetDatabase = new DbConnectionInfo(context.Database.Connection.ConnectionString, "Npgsql") }); // There some code for run migrations } } 

接下来,我们再次使用相同的连接字符串作为参数启动控制台应用程序。 这次,上下文的初始化没有错误,并且我们的域模型安全地适合了新的Postgres数据库。 “ __MigrationHistory”标签出现在新数据库中,其中有一个记录是第一次自动创建的迁移。

总结一下:我们能够将新的提供程序连接到现有项目而没有任何问题,但是同时更改了迁移机制的设置。

开启手动迁移模式


如上所述,启用自动迁移模式后,您的团队将失去在域和数据访问区域中的并行开发能力。 对于我们来说,这种选择是不可接受的。 因此,我们需要在项目中设置手动迁移模式。

首先,我们将AutomaticMigrationsEnabled字段返回为false。 然后有必要处理新迁移的创建。 我们知道,至少应将不同DBMS的迁移存储在不同的项目文件夹中。 因此,我们决定在名为PostgresMigrations的基础结构项目中创建一个用于Postgres迁移的新文件夹(为清楚起见,该文件夹具有MsSql迁移,我们将其重命名为MsSqlMigrations),然后将MsSql迁移配置文件复制到其中。 同时,我们没有将所有现有的MsSql迁移复制到PostgresSql。 首先,由于它们都包含MsSql提供程序的配置快照,因此,我们将无法在新的DBMS上使用它们。 其次,更改的历史记录对于新的DBMS而言并不重要,我们可以了解域模型状态的最新快照。

我们认为一切准备就绪,可以构成第一次迁移到Postgres的过程。 在自动迁移模式打开的情况下初始化上下文期间创建的数据库已删除。 并且,由于以下事实的指导:对于第一次迁移,您需要基于域模型的当前状态创建物理数据库,因此我们很高兴在Package Manager控制台中对Update-Database命令进行了评分,仅指定了连接字符串参数。 结果,我们收到与连接到DBMS有关的错误。

在进一步研究了Update-Database命令的工作原理之后,我们执行了以下操作:

  • 在迁移配置设置中添加了以下代码:

    对于MsSql:

     public Configuration() { AutomaticMigrationsEnabled = false; ContextKey = "Project.Infrastructure.MyDbContext"; MigrationsDirectory = @"MsSqlMigrations"; } 

    对于Postgres:

     public Configuration() { AutomaticMigrationsEnabled = false; ContextKey = "Project.Infrastructure.MyDbContext"; MigrationsDirectory = @"PostgresMigrations"; } 
  • 指示了传递提供程序名称的Update-Database命令的必要参数
  • 添加的参数指示包含ef基础结构描述的项目以及带有新提供程序的迁移配置的文件夹

结果,我们得到了以下命令:
Update-Database -ProjectName“ Project.Infrastructure” -ConfigurationTypeName Project.Infrastructure.PostgresMigrations.Configuration -ConnectionString“服务器=本地主机; 数据库= TestPostgresDB; 集成安全性=否; 用户ID = postgres; 密码= pa $$ w0rd“ -ConnectionProviderName” Npgsql“
执行完此命令后,我们可以使用相似的参数执行Add-Migration命令,并命名第一个迁移InitialCreate:
Add-Migration-名称“ InitialCreate” -ProjectName“ CrossTech.DSS.Infrastructure” -ConfigurationTypeName CrossTech.DSS.Infrastructure.PostgresMigrations.Configuration -ConnectionString“服务器=本地主机; 数据库= TestPostgresDB; 集成安全性=否; 用户ID = postgres; 密码= pa $$ w0rd“ -ConnectionProviderName” Npgsql“
一个新文件出现在PostgresMigrations文件夹中:2017010120705068_InitialCreate.cs

然后,我们删除了执行Update-Database命令后创建的数据库,并使用上面指示的连接字符串作为参数启动了控制台应用程序。 因此,我们已经在手动创建的迁移的基础上获得了数据库。

总结:我们能够以最小的努力为Postgres提供程序添加第一个迁移,并通过控制台应用程序初始化上下文,获得一个新数据库,第一次手动迁移的更改就进入了该数据库。

在提供商之间切换


我们还有一个悬而未决的问题:如何配置上下文初始化,以便可以在运行时访问特定的DBMS?

任务是,在上下文的初始化阶段,可以选择所需提供者的一个或另一个目标数据库。 由于反复尝试配置此开关,因此我们提出了一个如下所示的解决方案。

在app.config中项目的控制台应用程序中(如果不使用app.config,则不使用machine.config),我们添加带有提供程序和连接名称的新连接字符串,并在上下文构造函数中“删除”连接名称而不是连接字符串。 同时,我们通过DbConfiguration实例的单例将连接字符串本身连接到上下文。 我们将从DbConfiguration继承的类的实例作为参数传递。

结果继承的DbConfiguration类:

 public class DbConfig : DbConfiguration { public DbConfig(string connectionName, string connectionString, string provideName) { ConfigurationManager.ConnectionStrings.Add(new ConnectionStringSettings(connectionName, connectionString, provideName)); switch (connectionName) { case "PostgresDbConnection": this.SetDefaultConnectionFactory(new NpgsqlConnectionFactory()); this.SetProviderServices(provideName, NpgsqlServices.Instance); this.SetProviderFactory(provideName, NpgsqlFactory.Instance); break; case "MsSqlDbConnection": this.SetDefaultConnectionFactory(new SqlConnectionFactory()); this.SetProviderServices(provideName, SqlProviderServices.Instance); this.SetProviderFactory(provideName, SqlClientFactory.Instance); this.SetDefaultConnectionFactory(new SqlConnectionFactory()); break; } } } 

现在上下文初始化本身如下所示:

 var connectionName = args[0]; var connectionString = args[1]; var provideName = args[2]; DbConfiguration.SetConfiguration(new DbConfig(connectionName, connectionString, provideName)); using (var context = MyDbContext(connectionName)) { Console.WriteLine("Database created"); } 

仔细跟踪的人可能会注意到他必须对代码进行另一处更改。 这是数据库初始化期间目标数据库的定义,该定义在前面介绍的InitializeDatabase方法中发生。

我们添加了一个简单的开关来确定特定提供程序的迁移配置:

 public class DbInitializer : CreateDatabaseIfNotExists<MyDbContext> { private string _connectionName; public DbInitializer(string connectionName) { _connectionName = connectionName; } public override void InitializeDatabase(MyDbContext context) { DbMigrationsConfiguration<MyDbContext> config; switch (_connectionName) { case "PostgresDbConnection": config = new PostgresMigrations.Configuration(); break; case "MsSqlDbConnection": config = new MsSqlMigrations.Configuration(); break; default: config = null; break; } if (config == null) return; config.TargetDatabase = new DbConnectionInfo(_connectionName); DbMigrator dbMigrator = new DbMigrator(config); // There some code for run migrations } } 

上下文构造函数本身开始看起来像这样:

 public MyDbContext(string connectionNameParam) : base(connectionString) { Database.SetInitializer(new DbInitializer(connectionName = connectionNameParam)); Database.Initialize(true); } 

接下来,我们启动了控制台应用程序,并将MsSql应用程序参数指定为DBMS提供程序。 我们为应用程序设置参数如下:
“ MsSqlDbConnection”“服务器=本地主机\ SQLEXPRESS; 数据库= TestMsSqlDB; 用户ID = sa; 密码= pa $$ w0rd“” System.Data.SqlClient“

MsSql数据库创建没有错误。

然后我们指定了应用程序参数:
“ PostgresDbConnection”“服务器=本地主机; 数据库= TestPostgresDB; 集成安全性=否; 用户ID = postgres; 密码= pa $$ w0rd“” Npgsql“
Postgres数据库也已创建,没有错误。

因此,还有一个小计-为了让EF为特定提供程序初始化数据库上下文,您需要在运行时:

  • “指示”向该提供者的迁移机制
  • 在上下文初始化之前配置DBMS连接字符串

我们以团队的方式处理两个DBMS的迁移


如我们所见,最有趣的部分开始于域中新变化的出现。 您需要考虑一个特定的提供程序为两个DBMS生成迁移。

因此,对于MSSQL Server,您需要执行顺序命令(对于Postgres,在创建第一个迁移时上述命令):

  • 根据上一个快照更新数据库
    Update-Database -ProjectName“ Project.Infrastructure” -ConfigurationTypeName Project.Infrastructure.MsSqlMigrations.Configuration -ConnectionString“服务器=本地主机; 数据库= TestMsSqlDB; 集成安全性=否; 用户ID = sa; 密码= pa $$ w0rd“ -ConnectionProviderName” System.Data.SqlClient“
  • 添加新的迁移
    Add-Migration-名称“ SomeMigrationName” -ProjectName“ Project.Infrastructure” -ConfigurationTypeName Project.Infrastructure.MsSqlMigrations.Configuration -ConnectionString“服务器=本地主机; 数据库= TestMsSqlDB; 集成安全性=否; 用户ID = sa; 密码= pa $$ w0rd“ -ConnectionProviderName” System.Data.SqlClient“

当开发人员并行地对域进行更改时,在版本控制系统中合并这些更改时会遇到多个冲突(为简单起见,我们将其称为git)。 这是由于以下事实:向EF的迁移是依次进行的。 而且,如果一位开发人员创建了迁移,那么另一位开发人员将无法成功地按顺序添加迁移。 每个后续迁移都存储有关前一个迁移的信息。 因此,在迁移到最后创建的模型快照时,有必要更新所谓的模型快照。

同时,解决团队中EF迁移的冲突归结为优先考虑特定开发人员变更的重要性。 并且其更改的优先级较高,应该首先将其填充到git中,并且根据约定的层次结构,其余开发人员需要执行以下操作:

  1. 删除创建的本地迁移
  2. 将更改从存储库移交给您自己,其他优先级高的同事已经在这里进行了迁移
  3. 创建本地迁移并将结果更改上传回git

据我们所熟悉的EF迁移机制,我们可以判断,所描述的团队开发方法是目前唯一的方法。 我们认为此解决方案并不理想,但它拥有生命权。 对于我们来说,寻找替代EF迁移机制的替代方案的问题已经迫在眉睫。

总结


与EF Migrations一起使用多个使用EF6的DBMS的工作是真实的,但是在此版本中,Microsoft的人员没有考虑使用版本控制系统进行团队并行工作的可能性。

市场上有许多可选的EF迁移解决方案(付费和免费):DbUp,RoundhousE,ThinkingHome.Migrator,FluentMigrator等。 从评论来看,他们比EF迁移更像是开发人员。

幸运的是,我们现在有机会在项目中进行某种升级。 并且在不久的将来,我们将切换到EF Core。 我们权衡了EF核心迁移机制的利弊,得出的结论是,使用第三方解决方案(即Fluent Migrator)会更加方便。

我们希望您对我们的经验感兴趣。 准备接受评论并回答问题,欢迎光临!

Source: https://habr.com/ru/post/zh-CN445910/


All Articles