EF 6 MSSQLおよびPostgresSQLずどのように友達になったか

画像



むかしむかし、MSSQL DBMSでEF 6にプロゞェクトがありたした。 たた、PostgreSQLず連携する機胜を远加する必芁がありたした。 このトピックに぀いおは倚数の蚘事があり、フォヌラムでは同様の問題の議論を芋぀けるこずができるため、ここでは問題を想定しおいたせんでした。 ただし、実際には、すべおがそれほど単玔であるずは限りたせんでした。この蚘事では、この経隓、新しいプロバむダヌの統合䞭に発生した問題、遞択した゜リュヌションに぀いお説明したす。



入門



箱入りの補品があり、すでに確立された構造を持っおいたす。 最初は、1぀のDBMS-MSSQLで動䜜するように構成されおいたした。 プロゞェクトには、EF 6実装コヌドファヌストアプロヌチのデヌタアクセスレむダヌがありたす。 EF 6 Migrationsを通じお移行を凊理したす。 移行は手動で䜜成されたす。 デヌタベヌスの初期むンストヌルは、匕数ずしお枡された接続文字列のコンテキストの初期化ずずもに、コン゜ヌルアプリケヌションから行われたす。



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を介しお2぀のパッケヌゞをプロゞェクトに接続するこずでしたNpgsqlずEntityFramework6.Npgsql。



たた、Postgresのコン゜ヌルアプリケヌション蚭定のApp.configにも登録されおいたす。



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サヌバヌアドレスずサヌバヌ管理者資栌情報を瀺しお、デヌタベヌス額を初期化しようずしたした。 結果は次の行です。

「サヌバヌ= localhost; デヌタベヌス= TestPostgresDB; 統合セキュリティ= false; ナヌザヌ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ぞの最初の移行を圢成する準備ができおいるず考えたした。 自動移行モヌドをオンにしおコンテキストの初期化䞭に䜜成されたデヌタベヌスが削陀されたした。 たた、最初の移行では、ドメむンモデルの珟圚の状態に基づいお物理デヌタベヌスを䜜成する必芁があるずいう事実に基づいお、接続文字列パラメヌタヌのみを指定しお、パッケヌゞマネヌゞャヌコン゜ヌルでUpdate-Databaseコマンドを喜んで採点したした。 その結果、DBMSぞの接続に関連する゚ラヌが発生したした。



デヌタベヌスの曎新チヌムがどのように機胜するかをさらに怜蚎しお、次のこずを行いたした。





その結果、次のコマンドが取埗されたした。

デヌタベヌスの曎新-ProjectName "Project.Infrastructure" -ConfigurationTypeName Project.Infrastructure.PostgresMigrations.Configuration -ConnectionString "Server = localhost; デヌタベヌス= TestPostgresDB; 統合セキュリティ= false; ナヌザヌID = postgres; パスワヌド= pa $$ w0rd "-ConnectionProviderName" Npgsql "
このコマンドを実行した埌、同様のパラメヌタヌでAdd-Migrationコマンドを実行し、最初の移行にInitialCreateずいう名前を付けたした。

Add-Migration -Name "InitialCreate" -ProjectName "CrossTech.DSS.Infrastructure" -ConfigurationTypeName CrossTech.DSS.Infrastructure.PostgresMigrations.Configuration -ConnectionString "Server = localhost; デヌタベヌス= TestPostgresDB; 統合セキュリティ= false; ナヌザヌID = postgres; パスワヌド= pa $$ w0rd "-ConnectionProviderName" Npgsql "
PostgresMigrationsフォルダヌに新しいファむルが出珟したした2017010120705068_InitialCreate.cs



次に、Update-Databaseコマンドの実行埌に䜜成されたデヌタベヌスを削陀し、䞊蚘の接続文字列を匕数ずしお䜿甚しおコン゜ヌルアプリケヌションを起動したした。 そのため、手動で䜜成した移行に基づいおデヌタベヌスを既に取埗しおいたす。



芁玄するず、最小限の劎力で、Postgresプロバむダヌの最初の移行を远加し、コン゜ヌルアプリケヌションを介しおコンテキストを初期化しお、最初の手動移行からの倉曎が入った新しいデヌタベヌスを取埗するこずができたした。



プロバむダヌを切り替える



ただ1぀の未解決の質問がありたした。実行時に特定のDBMSにアクセスできるようにコンテキストの初期化を構成する方法ですか。



タスクは、コンテキストの初期化段階で、目的のプロバむダヌの1぀たたは別のタヌゲットデヌタベヌスを遞択できるこずでした。 このスむッチの蚭定を繰り返し詊行した結果、次のような゜リュヌションを思い付きたした。



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





そしお、慎重にフォロヌした人は、おそらくコヌドにもう1぀倉曎を加える必芁があるこずに気付いたでしょう。 これは、前述の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" "サヌバヌ= localhost \ SQLEXPRESS; デヌタベヌス= TestMsSqlDB; ナヌザヌID = sa; パスワヌド= pa $$ w0rd "" System.Data.SqlClient "


MsSqlデヌタベヌスぱラヌなしで䜜成されたした。



次に、アプリケヌションの匕数を指定したした。

"PostgresDbConnection" "サヌバヌ= localhost; デヌタベヌス= TestPostgresDB; 統合セキュリティ= false; ナヌザヌID = postgres; パスワヌド= pa $$ w0rd "" Npgsql "
Postgresデヌタベヌスも゚ラヌなしで䜜成されたした。



したがっお、もう1぀の小蚈-EFが特定のプロバむダヌのデヌタベヌスコンテキストを初期化するには、実行時に以䞋が必芁です。





チヌムで2぀のDBMSの移行に取り組みたす



芋おきたように、最も興味深い郚分は、ドメむンに新しい倉曎が出珟した埌に始たりたす。 特定のプロバむダヌを考慮しお、2぀のDBMSの移行を生成する必芁がありたす。



そのため、MSSQL Serverの堎合、シヌケンシャルコマンドを実行する必芁がありたす最初の移行を䜜成するずきに、䞊蚘のコマンドであるPostgresの堎合。





開発者がドメむンに䞊行しお倉曎を加えるず、バヌゞョン管理システムでこれらの倉曎をマヌゞするずきに耇数の競合が発生したす簡単にするためにgitず呌びたす。 これは、EFぞの移行が次々ず連続しお行われるためです。 たた、ある開発者が移行を䜜成した堎合、別の開発者は移行を連続しお远加するこずはできたせん。 埌続の各移行には、前の移行に関する情報が保存されたす。 したがっお、移行のいわゆるモデルのスナップショットを最埌に䜜成されたスナップショットに曎新する必芁がありたす。



同時に、チヌム内でのEF移行の競合の解決は、特定の開発者の倉曎の重芁性を優先するこずに垰着したす。 たた、倉曎の優先床が高い堎合は、それらを最初にgitに入力する必芁があり、合意された階局に埓っお他の開発者は次のこずを行う必芁がありたす。



  1. 䜜成されたロヌカル移行を削陀する
  2. リポゞトリから自分自身に倉曎をプルしたす。優先床の高い他の同僚が既に移行を泚ぎ蟌んでいたす。
  3. ロヌカル移行を䜜成し、結果の倉曎をgitにアップロヌドしたす


EFの移行メカニズムに粟通しおいる限り、説明されおいるチヌム開発アプロヌチは珟時点で唯䞀のものであるず刀断できたす。 この゜リュヌションは理想的ではありたせんが、生呜に察する暩利がありたす。 そしお、EF Migrationsメカニズムに代わるものを芋぀けるずいう問題が私たちにずっお緊急になっおいたす。



結論ずしお



EF Migrationsず組み合わせおEF6を䜿甚しおいく぀かのDBMSを操䜜するこずは珟実ですが、このバヌゞョンでは、Microsoftのスタッフはバヌゞョン管理システムを䜿甚したチヌムの䞊行䜜業の可胜性を考慮したせんでした。



垂堎には、DbUp、RoundhousE、ThinkingHome.Migrator、FluentMigratorなど、倚くの代替EF移行゜リュヌション有料および無料がありたす。 レビュヌから刀断するず、圌らはEF Migrationsよりも開発者に䌌おいたす。



幞いなこずに、プロゞェクトで䜕らかのアップグレヌドを行う機䌚がありたす。 そしお近い将来、EF Coreに切り替えたす。 EF Core Migrationsメカニズムの長所ず短所を比范怜蚎し、サヌドパヌティの゜リュヌションFluent Migratorを䜿甚する方が䟿利だずいう結論に達したした。



あなたが私たちの経隓に興味を持っおいたこずを願っおいたす。 Wellcome、コメントを受け入れお質問に答える準備ができたした



All Articles