EF Core + Oracle: So machen Sie Migrationen idempotent



In der Regel wird das EF Core-Framework in Verbindung mit MS SQL, einem anderen Microsoft-Produkt, verwendet. Dies ist jedoch kein Dogma. Bei CUSTIS schreiben wir beispielsweise Geschäftslogik in C # und verwenden Oracle zur Verwaltung der Datenbanken. EF Core hat einen wunderbaren Migrationsmechanismus, aber in unserem Fall sind sie nicht idempotent. Tatsache ist, dass Oracle und eine Reihe anderer Datenbanken wie MySQL keine Transaktions-DDL unterstützen . Dies bedeutet, dass die Migration, wenn sie irgendwo in der Mitte liegt, nicht zurückgesetzt oder zurückgesetzt werden kann. Wie implementiere ich idempotente Migrationen zu EF Core ohne MS SQL?

Hintergrund


Unser Unternehmen verfügt über ein ziemlich leistungsfähiges Tool zum Installieren von Patches in einer Oracle-Datenbank, das wir in einer Reihe von Projekten verwenden. Es wurde geschrieben, als es keine Liquibase, EF-Migrationen und andere offene Tools gab. Mit Patcher können Sie mit Hunderten von Datenbanken arbeiten, den Installationsverlauf verfolgen, Protokolle anzeigen, Geheimnisse speichern und vieles mehr. Skripte zum Ändern der Datenbank werden in Form von SQL- oder m4-Makros geschrieben. Mit ihrer Hilfe können Sie unter anderem die Struktur ändern: Tabellen, Spalten und andere Objekte erstellen. Darüber hinaus sind m4-Makros idempotent. Das heißt, wenn Sie beispielsweise versuchen, die Tabelle zu erstellen, fällt das Skript nicht herunter, sondern sieht, dass es bereits vorhanden ist, und überspringt die Erstellung.

Angenommen, ein Skript zum Installieren eines Patches besteht aus zwei Vorgängen:

  1. Erstellen Sie Tabelle A.
  2. Erstellen von Tabelle B.

Wenn das Skript nach der ersten Operation abstürzt, bleibt Tabelle A in Oracle. Das erneute Anwenden des Patches funktioniert ordnungsgemäß: Das Skript überprüft, ob A bereits vorhanden ist, und fährt sofort mit der zweiten Operation fort.

Zusätzlich zu den Vorteilen hat der Patcher noch einen Nachteil - das Tool ist geschlossen und wird nur in CUSTIS verwendet. Entwickler müssen lernen, mit ihm zu arbeiten, und außerhalb des Unternehmens ist eine solche Erfahrung nicht sehr wertvoll. Darüber hinaus unterstützt der Patcher den Code First-Betriebsmodus nicht, sodass alle Skripte zum Ändern der Datenbankstruktur manuell geschrieben werden müssen.

Wir wollten einen vorgefertigten Mechanismus für die Installation von Patches ausprobieren und haben uns für die Migration entschieden. Ende 2019 wurde gerade ein weiteres Projekt für den Kunden gestartet, bei dem wir beschlossen, einen neuen Ansatz zu testen. Das Hauptproblem dieses Mechanismus war die Nichtidepotenz von Migrationen.

Problem


In MS SQL wird eine Kette von DDL-Anweisungen oder eine Migration als einzelne zusammengesetzte Transaktion ausgeführt. Im Falle einer Unterbrechung wird der Vorgang vollständig abgebrochen. Oracle DDL ist nicht transaktionsbezogen, sodass ein Rückgang der Migration zu einem inkonsistenten Status der Datenbank führt.

Kehren wir zu dem Patch zurück, der aus zwei Vorgängen besteht: Erstellen der Tabellen A und B. Wenn der Migrator nach dem ersten fällt, bleibt Oracle in Tabelle A. Ein Neustart funktioniert nicht - der Operator CREATE TABLEwird nicht mögen, dass A bereits vorhanden ist. Außerdem kann die Migration nicht rückgängig gemacht werden: EF Core schreibt erst am Ende des Prozesses in die Systemtabelle, dass die Migration abgeschlossen wurde. Wenn aus Sicht von EF Core die Migration noch nicht abgeschlossen ist, gibt es nichts, was zurückgesetzt werden könnte.

Entscheidung


Die Suche nach einer vorgefertigten Lösung für Oracle im Internet ergab keine Ergebnisse. Ich habe nur Artikel zum Schreiben und  Installieren von Patches bei der Arbeit mit EF gefunden. Wenig später kam mir bei StackOverflow die Idee, meinen IMigrationsSqlGenerator zu erstellen . Diese Schnittstelle ist für die Generierung von SQL-Code verantwortlich, der EF-Operationen verarbeitet.

Das Paket Oracle.EntityFrameworkCore enthielt OracleMigrationsSqlGenerator und implementiert IMigrationsSqlGenerator. Wenn Sie beispielsweise eine Spalte hinzufügen möchten, wird der folgende Code generiert:

ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)

Anschließend wird der Code an andere Klassen übergeben, um in der Datenbank ausgeführt zu werden.

Zu Beginn habe ich versucht, zwei OracleMigrationsSqlGenerator-Vorgänge zu überschreiben. Die Aufgabe erwies sich als durchaus machbar, und ich begann, einen idempotenten Migranten zu schreiben. So entstand CUSTIS.OracleIdempotentSqlGenerator .

Vor der Operation EF überprüft unser Migrator, ob sie zuvor ausgeführt wurde. Zum Beispiel wird eine Spalte wie folgt hinzugefügt:

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;

Verwenden von


Die Verwendung des Pakets ist sehr einfach - Sie müssen es nur IMigrationsSqlGeneratorim richtigen Kontext ersetzen :

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

Migrationen werden mit den Standardtools für EF Core gebildet und festgelegt :

dotnet ef migrations add v1.0.1
dotnet ef database update

Der in CUSTIS.OracleIdempotentSqlGenerator festgelegte allgemeine Ansatz kann in Generatoren implementiert werden, die für MySQL, MariaDB, Teradata, AmazonAurora und andere Datenbanken geschrieben wurden, in denen DDL nicht transaktional ist.

Verweise


Das Paket ist in NuGet Sources auf GitHub verfügbar

All Articles