EF Core + Oracle: c贸mo hacer que las migraciones sean idempotentes



Por lo general, el marco EF Core se usa junto con MS SQL, otro producto de Microsoft. Sin embargo, esto no es un dogma. Por ejemplo, en CUSTIS escribimos l贸gica de negocios en C #, y usamos Oracle para administrar las bases de datos. EF Core tiene un maravilloso mecanismo de migraci贸n, pero en nuestro caso no son idempotentes. El hecho es que Oracle y otras bases de datos, como MySQL, no admiten DDL transaccional . Esto significa que si la migraci贸n cae en alg煤n punto intermedio, no se puede revertir ni revertir. 驴C贸mo implementar migraciones idempotentes a EF Core sin MS SQL?

Antecedentes


Nuestra compa帽铆a tiene una herramienta bastante poderosa para instalar parches en una base de datos Oracle, que usamos en varios proyectos. Fue escrito cuando no hubo Liquibase, migraciones EF y otras herramientas abiertas. Patcher le permite trabajar con cientos de bases de datos, rastrear el historial de instalaci贸n, ver registros, almacenar secretos y mucho m谩s. Las secuencias de comandos para cambiar la base de datos se escriben en forma de macros SQL o m4. Con su ayuda, puede, entre otras cosas, modificar la estructura: crear tablas, columnas y otros objetos. Adem谩s, las macros m4 son idempotentes. Esto significa que si intenta crear, por ejemplo, la tabla, el script no se cae, pero ve que ya existe y omite la creaci贸n.

Supongamos que un script para instalar un parche consta de dos operaciones:

  1. Crear tabla A.
  2. Creando la Tabla B.

Si la secuencia de comandos falla despu茅s de la primera operaci贸n, la tabla A permanecer谩 en Oracle. Volver a aplicar el parche funcionar谩 correctamente: la secuencia de comandos verificar谩 que A ya existe, por lo que pasar谩 inmediatamente a la segunda operaci贸n.

Adem谩s de las ventajas, el parcheador todav铆a tiene un inconveniente: la herramienta est谩 cerrada y solo se usa en CUSTIS. Los desarrolladores tienen que aprender a trabajar con 茅l y, fuera de la empresa, esa experiencia no es muy valiosa. Adem谩s, el parcheador no admite el modo de operaci贸n Code First, por lo que todos los scripts para cambiar la estructura de la base de datos deben escribirse manualmente.

Quer铆amos probar un mecanismo listo para instalar parches y elegimos la migraci贸n. A finales de 2019, se acaba de lanzar otro proyecto para el cliente, en el que decidimos probar un nuevo enfoque. El principal problema de este mecanismo era la no idepotencia de las migraciones.

Problema


En MS SQL, una cadena de instrucciones DDL o migraci贸n se realiza como una transacci贸n compuesta 煤nica. En caso de interrupci贸n, la operaci贸n se cancela por completo. Oracle DDL no es transaccional, por lo que una ca铆da en la migraci贸n conducir谩 a un estado inconsistente de la base de datos.

Volvamos al parche, que consiste en dos operaciones: crear las tablas A y B. Si el migrador cae despu茅s de la primera, Oracle permanecer谩 en la tabla A. El reinicio no funcionar谩; al operador CREATE TABLEno le gustar谩 que A ya exista. Tambi茅n fallar谩 en revertir la migraci贸n: EF Core escribe en la tabla del sistema que la migraci贸n se complet贸, solo al final del proceso. Desde el punto de vista de EF Core, si la migraci贸n a煤n no se ha completado, entonces no hay nada que retroceder.

Decisi贸n


La b煤squeda de una soluci贸n preparada para Oracle en Internet no arroj贸 resultados. Todo lo que he encontrado son art铆culos sobre c贸mo escribirinstalar parches cuando trabajo con EF. Un poco m谩s tarde en StackOverflow se me ocurri贸 la idea de hacer mi IMigrationsSqlGenerator . Esta interfaz es responsable de generar c贸digo SQL que procesa las operaciones de EF.

El paquete Oracle.EntityFrameworkCore incluy贸 OracleMigrationsSqlGenerator, implementa IMigrationsSqlGenerator. Por ejemplo, si desea agregar una columna, se generar谩 el siguiente c贸digo:

ALTER TABLE MY_TABLE ADD (MY_COLUMN DATE)

Luego, el c贸digo se pasa a otras clases para ejecutarse en la base de datos.

Para comenzar, intent茅 anular un par de operaciones OracleMigrationsSqlGenerator. La tarea result贸 ser bastante factible, y comenc茅 a escribir un migrante idempotente. As铆 es como surgi贸 CUSTIS.OracleIdempotentSqlGenerator .

Antes de la operaci贸n EF, nuestro migrador verifica si se ha realizado antes. Por ejemplo, se agrega una columna como esta:

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;

Utilizando


Usar el paquete es muy simple: solo necesita reemplazarlo IMigrationsSqlGeneratoren el contexto correcto:

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

Las migraciones se forman y establecen mediante las herramientas est谩ndar para EF Core :

dotnet ef migrations add v1.0.1
dotnet ef database update

El enfoque general establecido en CUSTIS.OracleIdempotentSqlGenerator se puede implementar en generadores escritos para MySQL, MariaDB, Teradata, AmazonAurora y otras bases de datos en las que DDL no es transaccional.

Referencias


El paquete est谩 disponible en NuGet
Sources en GitHub

All Articles