Skip to main content

Migraciones de base de datos

Resumen

Las migraciones son una forma estructurada de modificar un esquema de base de datos y son necesarias cuando se deben realizar cambios en las tablas de la base de datos de Crono o cuando se necesitan agregar nuevas tablas. Las migraciones pueden encontrarse en el núcleo de Crono o en un módulo, dependiendo de quién haya creado la entidad relacionada.

Framework de Migraciones

Crono utiliza Fluent Migrator como su framework para migraciones de bases de datos.

  • Fluent Migrator es compatible con múltiples sistemas de bases de datos como SQL Server, MySQL o PostgreSQL.
  • Las migraciones se describen en clases de C#, que pueden integrarse al sistema de control de versiones de Fluent Migrator.

Operaciones Adicionales

Fluent Migrator también puede:

  • Escribir datos en la base de datos.
  • Actualizar datos existentes.

Sin embargo, estas tareas suelen ser más convenientes utilizando ISembradorDatos o su implementación abstracta SembradorDatos.

info

Crono prefiere Fluent Migrator sobre las migraciones de EF Core, debido a que:

  • EF Migrations no proporciona herramientas agnósticas al proveedor de la base de datos.
  • Fluent Migrator permite una definición más flexible y compatible del esquema.

Migrador de base de datos

DbMigrador es responsable de realizar las migraciones.
El método MigrarAsync se utiliza para migrar la base de datos a una versión específica o a la más reciente si no se especifica una versión.

warning

MigrarAsync tiene un parámetro para ensamblados que permite realizar solo las migraciones de un módulo específico. Este parámetro siempre debe configurarse.
Si es null, ¡se ejecutan las migraciones de todos los módulos!

El ejemplo siguiente de MigrarAsync revierte todas las migraciones de un módulo en particular, lo que permite eliminar todos los cambios realizados por el módulo en el esquema de la base de datos.

internal class Modulo : ModuloBase
{
private readonly DbMigrador<CronoDbContext> _dbMigrador;

public Modulo(DbMigrador<CronoDbContext> dbMigrador)
{
_dbMigrador = dbMigrador;
}

public override async Task DesinstalarAsync()
{
await _dbMigrador.MigrarAsync(-1, GetType().Assembly);
await base.DesinstalarAsync();
}
}

¿Como luce una migración?

Una migración es una clase interna que hereda de la clase abstracta Migration de Fluent Migrator.
Los archivos de migración deben nombrarse usando el patrón:

YYYYMMDDHHMMSS_<nombre de la migración>.cs ,esto asegura que se ordenen por fecha en orden ascendente.

La clase de migración debe decorarse con MigracionVersionAttribute para especificar una versión y una descripción breve.

  • Formato del timestamp:
    La versión se determina mediante un timestamp que normalmente representa la fecha en la que se creó la migración.
    Los formatos admitidos son:
    • yyyy-MM-dd HH:mm:ss (recomendado)
    • yyyy/MM/dd HH:mm:ss
    • yyyy.MM.dd HH:mm:ss

Formato para la descripción breve

Para uniformidad, se recomienda el formato:
<nombre del módulo>:<nombre de la migración>
Ejemplo:

  • Nucleo: ProductoEtiqueta
  • La primera migración suele llamarse Inicio. Ejemplo: MegaBuscador: Inicio.
[MigracionVersion("2024-11-21 16:00:00", "Nucleo: ProductoEtiqueta")]
internal class ProductoEtiqueta : Migration, IRecursosRegionalesProveedor, ISembradorDatos<CronoDbContexto>
{
// La implementación de 'IRecursosRegionalesProveedor' o 'ISembradorDatos<T>' es opcional aquí

public SembradorDatosEtapa Etapa => SembradorDatosEtapa.Temprano;
public bool RevertirEnCasoError=> false;

public override void Up()
{
// Código omitido para mayor claridad.
}

public override void Down()
{
// Código omitido para mayor claridad.
}

public async Task SembrarAsync(CronoDbContexto contexto, CancellationToken cancelToken = default)
{
await contexto.MigrarRecursosLocalesAsync(MigrarRecursosLocales);
}

public void MigrarRecursosLocales(IdiomaRecursosConstructor constructor)
{
// Código omitido para mayor claridad.
}
}
Método o propiedadDescripción
IMigration.UpRecopila las expresiones de migración hacia arriba (up).
IMigration.DownRecopila las expresiones de migración hacia abajo (down).
ISembradorDatos.Etapa

Obtiene un valor que indica la etapa en la que se realiza la siembra de datos en la migración.
Valores posibles: Temprano (temprano) y Tarde (tardío).

ISembradorDatos.RevertirEnCasoErrorObtiene un valor que indica si la migración debe revertirse completamente si ocurre un error durante la siembra de datos.
ISembradorDatos.SembrarAsyncInserta cualquier dato en la base de datos.
IRecursosRegionalesProveedor.MigrarRecursosLocalesInserta o actualiza recursos de localización después de realizar una migración.

Más ejemplos pueden encontrarse en el módulo HerramientasDesarrollo.
Estos son solo para fines ilustrativos y, por lo tanto, están comentados para evitar que se ejecuten.

info

En la migración ProductoEtiqueta, cada instrucción está precedida por una verificación de existencia del recurso relacionado (para una tabla, columna, clave foránea, índice de campo, etc.). Esto no es obligatorio pero se recomienda. Esto hace que tu migración sea menos vulnerable y asegura que, en caso de eventos impredecibles, la migración se ejecute correctamente, independientemente de cuántas veces se ejecute. Si tu migración se ejecutó parcialmente debido a un error, no podrá ejecutarse nuevamente de forma exitosa sin estas verificaciones de existencia, y se requerirían cambios manuales en el esquema de la base de datos.

Una migración también puede heredar de AutoReversingMigration. En este caso, no es necesario implementar el método Down, porque FluentMigrator genera las expresiones necesarias automáticamente a partir de las expresiones del método Up. Esto solo funciona para algunas expresiones como CREATE TABLE, pero no para DROP TABLE.

warning

Eliminar una tabla añadida por el método Up debe ser bien planificado. El usuario podría haber almacenado una gran cantidad de datos en ella, los cuales no deberían eliminarse sin previo aviso. En caso de duda, simplemente deja la tabla en la base de datos al desinstalar el módulo. A menudo, el método Down de la migración inicial se deja vacío por esta razón.

Historial de migraciones

Fluent Migrator mantiene un historial de versiones de las migraciones ejecutadas exitosamente en la tabla de base de datos __InformacionVersionMigracion. El contenido de esta tabla podría verse así:

note

Imagen (tabla __InformacionVersionMigracion con el historial de migraciones ejecutadas) pendiente de migrar desde GitBook.

Al eliminar una entrada en este historial, es posible ejecutar la migración asociada la próxima vez que se inicie la aplicación. Por ejemplo, si quiero ejecutar nuevamente la migración inicial del módulo Memoria Cache Salida, elimino la fila correspondiente a esa migración (ver la fila resaltada en la imagen anterior). También elimino la tabla MemoriaCache porque es creada por esa migración. Sin eliminar la tabla, la migración se ejecutaría sin hacer nada porque MemoriaCache ya existe, o generaría un error si no verificara primero si la tabla ya existía.

Inserción de datos

La inserción de datos permite agregar datos a la base de datos o actualizar datos existentes. Si esto se realiza junto con una migración, generalmente se implementa ISembradorDatos<CronoDbContexto> directamente a través de su clase de migración, como se mostró anteriormente.

Sin una migración, o si se necesitan transferir grandes cantidades de datos a la base de datos, se utiliza una clase separada que hereda de SembradorDatos<CronoDbContexto>. Por ejemplo, si necesitas agregar datos de muestra a la base de datos durante la instalación de Crono o de un módulo.