Bagaimana kami berteman dengan EF 6 MSSQL dan PostgresSQL

gambar

Sekali waktu ada proyek di EF 6 dengan MSSQL DBMS. Dan ada kebutuhan untuk menambahkan kemampuan untuk bekerja dengan PostgreSQL. Kami tidak mengharapkan masalah di sini, karena ada banyak artikel tentang topik ini, dan di forum Anda dapat menemukan diskusi tentang masalah yang sama. Namun, pada kenyataannya, tidak semuanya ternyata sangat sederhana, dan dalam artikel ini kita akan berbicara tentang pengalaman ini, tentang masalah yang kami temui selama integrasi penyedia baru, dan tentang solusi yang kami pilih.

Pendahuluan


Kami memiliki produk kotak, dan memiliki struktur yang sudah mapan. Awalnya, itu dikonfigurasi untuk bekerja dengan satu DBMS - MSSQL. Proyek ini memiliki lapisan akses data dengan implementasi EF 6 (pendekatan Code First). Kami bekerja dengan migrasi melalui Migrasi EF 6. Migrasi dibuat secara manual. Instalasi awal basis data terjadi dari aplikasi konsol dengan inisialisasi konteks pada string koneksi, diteruskan sebagai argumen:

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

Pada saat yang sama, infrastruktur EF dan domain domain dijelaskan dalam proyek lain, yang terhubung ke aplikasi konsol sebagai perpustakaan. Konstruktor konteks dalam proyek infrastruktur terlihat seperti ini:

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

Peluncuran pertama


Hal pertama yang kami lakukan adalah menghubungkan dua paket ke proyek melalui nuget: Npgsql dan EntityFramework6.Npgsql.

Kami juga mendaftarkan pengaturan untuk Postgres di App.config dari aplikasi konsol kami.

Bagian entityFramework menetapkan pabrik postgres default sebagai pabrik koneksi:

 <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> 

Di bagian DbProviderFactories, pabrik penyedia baru terdaftar:

 <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> 

Dan segera, mereka mencoba menginisialisasi database dengan menentukan alamat server Postgres dan kredensial admin server dalam string koneksi. Hasilnya adalah baris berikut:
“Server = localhost; DataBase = TestPostgresDB; Keamanan Terpadu = salah; User Id = postgres; kata sandi = pa $$ w0rd ”
Seperti yang diharapkan, berkat mode Migrasi EF manual, inisialisasi tidak lulus, dan terjadi kesalahan yang tidak cocok dengan gambar database model saat ini. Untuk mengatasi penciptaan migrasi utama dengan penyedia baru dan menguji inisialisasi database pada Postgres, kami sedikit menyesuaikan konfigurasi infrastruktur kami.

Pertama, kami mengaktifkan "migrasi otomatis" - opsi yang berguna jika satu pengembang membuat perubahan pada model domain dan infrastruktur EF di tim:

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

Kedua, kami menetapkan penyedia baru dalam metode yang didefinisikan ulang InitializeDatabase dari kelas yang diwarisi CreateDatabaseIfNotExists, tempat kami memulai migrasi:

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

Selanjutnya, kami meluncurkan aplikasi konsol kami lagi dengan string koneksi yang sama sebagai argumen. Kali ini, inisialisasi konteks berjalan tanpa kesalahan, dan model domain kami dengan aman masuk ke dalam database Postgres baru. Label "__MigrationHistory" muncul di database baru, di mana ada satu catatan migrasi pertama yang dibuat secara otomatis.

Untuk meringkas: kami dapat menghubungkan penyedia baru ke proyek yang ada tanpa masalah, tetapi pada saat yang sama mengubah pengaturan mekanisme migrasi.

Aktifkan mode migrasi manual


Seperti disebutkan di atas, ketika mode migrasi otomatis aktif, Anda menghilangkan tim pengembangan paralel Anda di domain dan area akses data. Bagi kami, opsi ini tidak dapat diterima. Oleh karena itu, kami perlu menyiapkan mode migrasi manual dalam proyek.

Pertama, kami mengembalikan bidang AutomaticMigrationsEnabled ke false. Maka itu perlu untuk berurusan dengan penciptaan migrasi baru. Kami memahami bahwa migrasi untuk DBMS yang berbeda, setidaknya, harus disimpan dalam folder proyek yang berbeda. Oleh karena itu, kami memutuskan untuk membuat folder baru untuk migrasi Postgres dalam proyek infrastruktur yang disebut PostgresMigrations (folder dengan migrasi MsSql, untuk kejelasan, kami menamainya MsSqlMigrations), dan menyalin file konfigurasi migrasi MsSql ke sana. Pada saat yang sama, kami tidak menyalin semua migrasi MsSql yang ada ke PostgresSql. Pertama, karena semuanya berisi snapshot dari konfigurasi untuk penyedia MsSql dan, oleh karena itu, kami tidak akan dapat menggunakannya pada DBMS baru. Kedua, sejarah perubahan tidak penting untuk DBMS baru, dan kita bisa bertahan dengan snapshot terbaru dari keadaan model domain.

Kami pikir semuanya siap untuk pembentukan migrasi pertama ke Postgres. Database yang dibuat selama inisialisasi konteks dengan mode migrasi otomatis dihidupkan telah dihapus. Dan, dipandu oleh fakta bahwa untuk migrasi pertama Anda perlu membuat database fisik berdasarkan keadaan model domain saat ini, kami dengan senang hati mencetak perintah Update-Database di Package Manager Console, yang hanya menentukan parameter string koneksi. Akibatnya, kami mendapatkan kesalahan terkait dengan koneksi ke DBMS.

Selain mempelajari prinsip kerja perintah Update-Database, kami melakukan hal berikut:

  • menambahkan kode berikut ke pengaturan konfigurasi migrasi:

    untuk MsSql:

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

    untuk Postgres:

     public Configuration() { AutomaticMigrationsEnabled = false; ContextKey = "Project.Infrastructure.MyDbContext"; MigrationsDirectory = @"PostgresMigrations"; } 
  • menunjukkan parameter yang diperlukan dari perintah Update-Database lewat nama penyedia
  • menambahkan parameter yang menunjukkan proyek yang berisi deskripsi infrastruktur ef, dan folder dengan konfigurasi migrasi penyedia baru

Sebagai hasilnya, kami mendapat perintah ini:
Update-Database -ProjectName "Project.Infrastructure" -ConfigurationTypeName Project.Infrastructure.PostgresMigrations.Configuration -ConnectionString "Server = localhost; DataBase = TestPostgresDB; Keamanan Terpadu = salah; User Id = postgres; password = pa $$ w0rd "-ConnectionProviderName" Npgsql "
Setelah menjalankan perintah ini, kami dapat menjalankan perintah Add-Migration dengan parameter yang serupa, menamai migrasi pertama InitialCreate:
Add-Migration -Nama "InitialCreate" -ProjectName "CrossTech.DSS.Infrastructure" -ConfigurationTypeName CrossTech.DSS.Infrastructure.PostgresMigrations.Configuration -ConnectionString "Server = localhost; DataBase = TestPostgresDB; Keamanan Terpadu = salah; User Id = postgres; password = pa $$ w0rd "-ConnectionProviderName" Npgsql "
File baru telah muncul di folder PostgresMigrations: 2017010120705068_InitialCreate.cs

Kemudian kami menghapus database yang dibuat setelah menjalankan perintah Update-Database dan meluncurkan aplikasi konsol kami dengan string koneksi yang ditunjukkan di atas sebagai argumen. Jadi kami sudah mendapatkan basis data berdasarkan migrasi yang dibuat secara manual.

Untuk meringkas: kami dapat, dengan sedikit usaha, untuk menambahkan migrasi pertama untuk penyedia Postgres dan menginisialisasi konteks melalui aplikasi konsol, mendapatkan database baru, di mana perubahan dari migrasi manual pertama kami datang.

Beralih di antara penyedia


Kami masih memiliki satu pertanyaan terbuka: bagaimana cara mengonfigurasi inisialisasi konteks sehingga memungkinkan untuk mengakses DBMS tertentu dalam runtime?

Tugasnya adalah bahwa pada tahap inisialisasi konteks dimungkinkan untuk memilih satu atau beberapa database target dari penyedia yang diinginkan. Sebagai hasil dari upaya berulang-ulang untuk mengkonfigurasi switch ini, kami datang dengan solusi yang terlihat seperti ini.

Dalam aplikasi konsol proyek di app.config (dan jika Anda tidak menggunakan app.config, maka machine.config), kami menambahkan string koneksi baru dengan penyedia dan nama koneksi, dan dalam konstruktor konteks kami “menjatuhkan” nama koneksi alih-alih string koneksi. Pada saat yang sama, kami menghubungkan string koneksi itu sendiri ke konteks melalui singleton dari instance DbConfiguration. Kami melewati instance dari kelas yang diwarisi dari DbConfiguration sebagai parameter.

Kelas DbConfiguration bawaan yang dihasilkan:

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

Dan inisialisasi konteks itu sendiri sekarang terlihat seperti ini:

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

Dan yang mengikuti dengan hati-hati, dia mungkin memperhatikan bahwa kita harus membuat satu lagi perubahan dalam kode. Ini adalah definisi dari database target selama inisialisasi database, yang terjadi dalam metode InitializeDatabase yang dijelaskan sebelumnya.

Kami menambahkan sakelar sederhana untuk menentukan konfigurasi migrasi penyedia tertentu:

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

Dan konstruktor konteks itu sendiri mulai terlihat seperti ini:

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

Selanjutnya, kami meluncurkan aplikasi konsol dan menentukan parameter aplikasi MsSql sebagai penyedia DBMS. Kami menetapkan argumen untuk aplikasi sebagai berikut:
"MsSqlDbConnection" "Server = localhost \ SQLEXPRESS; Database = TestMsSqlDB; ID Pengguna = sa; password = pa $$ w0rd "" System.Data.SqlClient "

Database MsSql dibuat tanpa kesalahan.

Kemudian kami tentukan argumen aplikasi:
"PostgresDbConnection" "Server = localhost; DataBase = TestPostgresDB; Keamanan Terpadu = salah; User Id = postgres; kata sandi = pa $$ w0rd "" Npgsql "
Basis data Postgres juga dibuat tanpa kesalahan.

Jadi, satu lagi subtotal - agar EF dapat menginisialisasi konteks basis data untuk penyedia tertentu, dalam runtime yang Anda butuhkan:

  • “Tunjukkan” mekanisme migrasi ke penyedia ini
  • mengkonfigurasi string koneksi DBMS sebelum inisialisasi konteks

Kami bekerja dengan migrasi dua DBMS dalam satu tim


Seperti yang kita lihat, bagian yang paling menarik dimulai setelah kemunculan perubahan baru di domain. Anda perlu membuat migrasi untuk dua DBMS dengan mempertimbangkan penyedia tertentu.

Jadi, untuk MSSQL Server, Anda perlu menjalankan perintah berurutan (untuk Postgres, perintah yang dijelaskan di atas, saat membuat migrasi pertama):

  • memperbarui basis data sesuai dengan snapshot terakhir
    Update-Database -ProjectName "Project.Infrastructure" -ConfigurationTypeName Project.Infrastructure.MsSqlMigrations.Configuration -ConnectionString "Server = localhost; DataBase = TestMsSqlDB; Keamanan Terpadu = salah; ID Pengguna = sa; password = pa $$ w0rd "-ConnectionProviderName" System.Data.SqlClient "
  • menambahkan migrasi baru
    Add-Migration -Name "SomeMigrationName" -ProjectName "Project.Infrastructure" -ConfigurationTypeName Project.Infrastructure.MsSqlMigrations.Configuration -ConnectionString "Server = localhost; DataBase = TestMsSqlDB; Keamanan Terpadu = salah; ID Pengguna = sa; password = pa $$ w0rd "-ConnectionProviderName" System.Data.SqlClient "

Ketika pengembang membuat perubahan pada domain secara paralel, kami mendapatkan beberapa konflik saat menggabungkan perubahan ini dalam sistem kontrol versi (untuk kesederhanaan kami akan memanggil git). Ini disebabkan oleh kenyataan bahwa migrasi ke EF berjalan berurutan satu demi satu. Dan jika satu pengembang membuat migrasi, maka pengembang lain tidak akan berhasil menambahkan migrasi secara berurutan. Setiap migrasi selanjutnya menyimpan informasi tentang yang sebelumnya. Dengan demikian, perlu memperbarui snapshot model yang disebut dalam migrasi ke yang dibuat terakhir.

Pada saat yang sama, menyelesaikan konflik pada migrasi EF di tim turun ke memprioritaskan pentingnya perubahan dari pengembang tertentu. Dan yang perubahannya lebih tinggi dalam prioritas, mereka harus menjadi yang pertama mengisinya dalam git, dan pengembang lainnya sesuai dengan hierarki yang disepakati perlu melakukan hal berikut:

  1. hapus migrasi lokal yang dibuat
  2. tarik perubahan dari repositori ke diri Anda sendiri, di mana kolega lain dengan prioritas tinggi telah menuangkan migrasi mereka
  3. buat migrasi lokal dan unggah perubahan yang dihasilkan kembali ke git

Sejauh yang kita kenal dengan mekanisme migrasi EF, kita dapat menilai bahwa pendekatan pengembangan tim yang dijelaskan adalah satu-satunya saat ini. Kami tidak menganggap solusi ini ideal, tetapi memiliki hak untuk hidup. Dan pertanyaan untuk menemukan alternatif bagi mekanisme Migrasi EF telah menjadi hal yang mendesak bagi kami.

Kesimpulannya


Bekerja dengan beberapa DBMS menggunakan EF6 bersamaan dengan Migrasi EF adalah nyata, tetapi dalam versi ini orang-orang dari Microsoft tidak memperhitungkan kemungkinan kerja paralel dari tim menggunakan sistem kontrol versi.

Ada banyak alternatif solusi Migrasi EF di pasaran (baik berbayar maupun gratis): DbUp, RoundhousE, ThinkingHome.Migrator, FluentMigrator, dll. Dan dilihat dari ulasannya, mereka lebih mirip pengembang daripada Migrasi EF.

Untungnya, kami sekarang memiliki kesempatan untuk melakukan semacam peningkatan dalam proyek kami. Dan dalam waktu dekat kami akan beralih ke EF Core. Kami mempertimbangkan pro dan kontra dari mekanisme Migrasi Inti EF dan sampai pada kesimpulan bahwa akan lebih mudah bagi kami untuk bekerja dengan solusi pihak ketiga, yaitu Fluent Migrator.

Kami harap Anda tertarik dengan pengalaman kami. Siap menerima komentar dan menjawab pertanyaan, Selamat Datang!

Source: https://habr.com/ru/post/id445910/


All Articles