EF Core + Oracle: cara membuat migrasi idempoten



Biasanya, kerangka kerja EF Core digunakan bersama dengan MS SQL, produk Microsoft lainnya. Namun, ini bukan dogma. Misalnya, di CUSTIS kami menulis logika bisnis dalam C #, dan kami menggunakan Oracle untuk mengelola basis data. EF Core memiliki mekanisme migrasi yang luar biasa, tetapi dalam kasus kami mereka tidak idempoten. Faktanya adalah bahwa Oracle dan sejumlah database lain, seperti MySQL, tidak mendukung DDL transaksional . Ini berarti bahwa jika migrasi jatuh di suatu tempat di tengah, itu tidak dapat digulung atau digulung kembali. Bagaimana cara menerapkan migrasi idempoten ke EF Core tanpa MS SQL?

Latar Belakang


Perusahaan kami memiliki alat yang cukup kuat untuk menginstal tambalan pada basis data Oracle, yang kami gunakan di sejumlah proyek. Itu ditulis ketika tidak ada Liquibase, migrasi EF, dan alat terbuka lainnya. Patcher memungkinkan Anda untuk bekerja dengan ratusan basis data, melacak riwayat instalasi, melihat log, menyimpan rahasia, dan banyak lagi. Skrip untuk mengubah database ditulis dalam bentuk makro SQL atau m4. Dengan bantuan mereka, Anda dapat, antara lain, memodifikasi struktur: membuat tabel, kolom, dan objek lainnya. Selain itu, makro m4 idempoten. Ini berarti bahwa jika Anda mencoba membuat, misalnya, tabel, skrip tidak jatuh, tetapi melihat bahwa itu sudah ada dan melompati ciptaan.

Misalkan skrip untuk menginstal tambalan terdiri dari dua operasi:

  1. Buat tabel A.
  2. Membuat Tabel B.

Jika skrip macet setelah operasi pertama, tabel A akan tetap di Oracle. Menerapkan kembali tambalan akan berfungsi dengan benar: skrip akan memverifikasi bahwa A sudah ada, sehingga akan segera melanjutkan ke operasi kedua.

Selain kelebihannya, patcher masih memiliki kekurangan - alat ini ditutup dan hanya digunakan di CUSTIS. Pengembang harus belajar untuk bekerja dengannya, dan di luar perusahaan pengalaman seperti itu tidak terlalu berharga. Selain itu, patcher tidak mendukung mode operasi Code First, sehingga semua skrip untuk mengubah struktur database harus ditulis secara manual.

Kami ingin mencoba beberapa mekanisme yang siap pakai untuk menginstal tambalan dan memilih migrasi. Pada akhir 2019, proyek lain baru saja diluncurkan untuk pelanggan, di mana kami memutuskan untuk menguji pendekatan baru. Masalah utama dari mekanisme ini adalah ketidakberpindahan migrasi.

Masalah


Dalam MS SQL, rantai pernyataan atau migrasi DDL dilakukan sebagai transaksi majemuk tunggal. Jika terjadi gangguan, operasi dibatalkan sepenuhnya. Oracle DDL bersifat non-transaksional, sehingga penurunan migrasi akan menyebabkan kondisi database yang tidak konsisten.

Mari kita kembali ke tambalan, yang terdiri dari dua operasi: membuat tabel A dan B. Jika migrator jatuh setelah yang pertama, Oracle akan tetap di tabel A. Restart tidak akan berfungsi - operator CREATE TABLEtidak akan suka bahwa A sudah ada. Ini juga akan gagal untuk memutar kembali migrasi: EF Core menulis ke tabel sistem bahwa migrasi selesai, hanya di akhir proses. Dari sudut pandang EF Core, jika migrasi belum selesai, maka tidak ada yang bisa dibatalkan.

Keputusan


Pencarian untuk solusi yang sudah jadi untuk Oracle di Internet tidak membuahkan hasil. Yang saya temukan hanyalah artikel tentang cara menulis dan  menginstal tambalan saat bekerja dengan EF. Beberapa saat kemudian di StackOverflow saya datang dengan ide untuk membuat IMigrationsSqlGenerator saya . Antarmuka ini bertanggung jawab untuk menghasilkan kode SQL yang memproses operasi EF.

Paket Oracle.EntityFrameworkCore termasuk OracleMigrationsSqlGenerator, mengimplementasikan IMigrationsSqlGenerator. Misalnya, jika Anda ingin menambahkan kolom, kode berikut akan dihasilkan:

ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)

Kemudian kode tersebut diteruskan ke kelas lain untuk dijalankan dalam database.

Untuk memulai, saya mencoba menimpa sepasang operasi OracleMigrationsSqlGenerator. Tugas itu ternyata cukup layak, dan saya mulai menulis migran idempoten. Inilah bagaimana CUSTIS.OracleIdempotentSqlGenerator muncul .

Sebelum operasi EF, migrator kami memeriksa untuk melihat apakah itu telah dilakukan sebelumnya. Misalnya, kolom ditambahkan seperti ini:

DECLARE
    i NUMBER;
BEGIN
    SELECT COUNT(*) INTO i
    FROM user_tab_columns
    WHERE table_name = UPPER('MY_TABLE') AND column_name = UPPER('MY_COLUMN');
    IF I != 1 THEN
        EXECUTE IMMEDIATE 'ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)';  
    END IF;       
END;

Menggunakan


Menggunakan paket ini sangat sederhana - Anda hanya perlu menggantinya IMigrationsSqlGeneratordalam konteks yang benar:

public class MyDbContext : DbContext
{
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.ReplaceService<IMigrationsSqlGenerator, IdempotentSqlGenerator>();
    }
}

Migrasi dibentuk dan ditetapkan oleh alat standar untuk EF Core :

dotnet ef migrations add v1.0.1
dotnet ef database update

Pendekatan umum yang ditetapkan dalam CUSTIS.OracleIdempotentSqlGenerator dapat diimplementasikan dalam generator yang ditulis untuk MySQL, MariaDB, Teradata, AmazonAurora dan database lain di mana DDL tidak transaksional.

Referensi


Paket ini tersedia di Sumber NuGet
di GitHub

All Articles