EF Core + Oracle: comment rendre les migrations idempotentes



En règle générale, le cadre EF Core est utilisé conjointement avec MS SQL, un autre produit Microsoft. Cependant, ce n'est pas un dogme. Par exemple, chez CUSTIS, nous écrivons la logique métier en C #, et nous utilisons Oracle pour gérer les bases de données. EF Core a un merveilleux mécanisme de migration, mais dans notre cas, ils ne sont pas idempotents. Le fait est qu'Oracle et un certain nombre d'autres bases de données, telles que MySQL, ne prennent pas en charge le DDL transactionnel . Cela signifie que si la migration se situe quelque part au milieu, elle ne peut pas être annulée ou annulée. Comment implémenter des migrations idempotentes vers EF Core sans MS SQL?

Contexte


Notre entreprise dispose d'un outil assez puissant pour installer des correctifs sur une base de données Oracle, que nous utilisons dans un certain nombre de projets. Il a été écrit quand il n'y avait pas de migrations Liquibase, EF et autres outils ouverts. Patcher vous permet de travailler avec des centaines de bases de données, de suivre l'historique de l'installation, d'afficher les journaux, de stocker les secrets et bien plus encore. Les scripts de modification de la base de données sont écrits sous forme de macros SQL ou m4. Avec leur aide, vous pouvez, entre autres, modifier la structure: créer des tableaux, des colonnes et d'autres objets. De plus, les macros m4 sont idempotentes. Cela signifie que si vous essayez de créer, par exemple, la table, le script ne tombe pas, mais voit qu'il existe déjà et ignore la création.

Supposons qu'un script pour installer un correctif se compose de deux opérations:

  1. Créez la table A.
  2. Création du tableau B.

Si le script se bloque après la première opération, la table A reste dans Oracle. La réapplication du correctif fonctionnera correctement: le script vérifiera que A existe déjà, il passera donc immédiatement à la deuxième opération.

En plus des avantages, le patcher a toujours un inconvénient - l'outil est fermé et n'est utilisé que dans CUSTIS. Les développeurs doivent apprendre à travailler avec lui, et en dehors de l'entreprise, une telle expérience n'est pas très précieuse. De plus, le patcher ne prend pas en charge le mode de fonctionnement Code First, de sorte que tous les scripts de modification de la structure de la base de données doivent être écrits manuellement.

Nous voulions essayer un mécanisme prêt à l'emploi pour installer des correctifs et avons choisi la migration. Fin 2019, un autre projet vient d'être lancé pour le client, sur lequel nous avons décidé de tester une nouvelle approche. Le principal problème de ce mécanisme était la non-idéalité des migrations.

Problème


Dans MS SQL, une chaîne d'instructions DDL ou de migration est effectuée comme une transaction composée unique. En cas d'interruption, l'opération est complètement annulée. Oracle DDL n'est pas transactionnel, donc une baisse de la migration entraînera un état incohérent de la base de données.

Revenons au patch, qui se compose de deux opérations: création des tables A et B. Si le migrateur tombe après la première, Oracle restera dans la table A. Le redémarrage ne fonctionnera pas - l'opérateur CREATE TABLEn'aimera pas que A existe déjà. Il ne réussira pas non plus à annuler la migration: EF Core écrit dans la table système que la migration est terminée, uniquement à la toute fin du processus. Du point de vue d'EF Core, si la migration n'est pas encore terminée, il n'y a rien à annuler.

Décision


La recherche d'une solution prête à l'emploi pour Oracle sur Internet n'a donné aucun résultat. Tout ce que j'ai trouvé, ce sont des articles sur la façon d' écrire et d'  installer des correctifs lorsque vous travaillez avec EF. Un peu plus tard, sur StackOverflow, j'ai eu l'idée de créer mon IMigrationsSqlGenerator . Cette interface est chargée de générer du code SQL qui traite les opérations EF.

Le package Oracle.EntityFrameworkCore comprenait OracleMigrationsSqlGenerator, implémente IMigrationsSqlGenerator. Par exemple, si vous souhaitez ajouter une colonne, le code suivant sera généré:

ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)

Ensuite, le code est transmis à d'autres classes pour s'exécuter dans la base de données.

Pour commencer, j'ai essayé de remplacer une paire d'opérations OracleMigrationsSqlGenerator. La tâche s'est avérée tout à fait réalisable et j'ai commencé à écrire un migrant idempotent. C'est ainsi qu'estCUSTIS.OracleIdempotentSqlGenerator .

Avant l'opération EF, notre migrateur vérifie si elle a déjà été effectuée. Par exemple, une colonne est ajoutée comme ceci:

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;

En utilisant


L'utilisation du package est très simple - il vous suffit de le remplacer IMigrationsSqlGeneratordans le bon contexte:

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

Les migrations sont formées et définies par les outils standard pour EF Core :

dotnet ef migrations add v1.0.1
dotnet ef database update

L'approche générale définie dans CUSTIS.OracleIdempotentSqlGenerator peut être implémentée dans des générateurs écrits pour MySQL, MariaDB, Teradata, AmazonAurora et d'autres bases de données dans lesquelles DDL n'est pas transactionnel.

Références


Le package est disponible sur NuGet
Sources sur GitHub

All Articles