Probablemente estás haciendo mal las migraciones de EF... (Español (Spanish))

Probablemente estás haciendo mal las migraciones de EF...

Sunday, 23 November 2025

//

11 minute read

Correr MigrateAsync() ¿Estás dando derechos de propietario de tu base de datos de aplicaciones y esperando que nada salga mal? Hay una mejor manera: los paquetes de migración de EF te permiten ejecutar migraciones como un paso de CI controlado, manteniendo segura tu aplicación de producción. Pero esto es lo siguiente: a veces la manera "equivocada" está realmente bien. Vamos a explorar cuándo usar cada enfoque.

Documentos oficiales: Panorama general de las migraciones | Aplicación de las migraciones | Paquetes

La manera "equivocada" (que yo uso)

Este blog utiliza MigrateAsync() En el inicio - el enfoque que estoy a punto de decirte que no uses. He aquí por qué eso está bien para mí, y por qué probablemente no es para ti.

En mi Program.cs Tengo lo siguiente:

    using (var scope = app.Services.CreateScope())
    {
        var blogContext = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
        await blogContext.Database.MigrateAsync();
    }

MigrateAsync() aplica las migraciones pendientes y crea la base de datos si es necesario. Simple - pero problemático:

  1. Dependencia de inicio ¿La migración falla? Tu aplicación no arranca.
  2. Violación de la seguridad - Tu app necesita db_owner Acabas de darle a tu aplicación de tiempo de ejecución las llaves para soltar las mesas.

Por qué me salgo con la mía: datos públicos, una sola red Docker, proyecto personal. Probablemente no puedas.

Cuando las migraciones en tiempo de ejecución están bien

  • Desarrollo local - La iteración rápida late ceremonia
  • Proyectos personales - Radio de explosión bajo, sin datos sensibles
  • Entornos de dev compuestos por Docker - Gana la conveniencia.
  • Prototipado - El esquema está cambiando constantemente de todos modos.

Cuando no lo son

  • Múltiples instancias de aplicación - Las condiciones de carrera en abundancia
  • Datos sensibles - IIP, financiera, regulada = se requiere una separación adecuada
  • Producción con usuarios reales - Migración fallida = interrupción del servicio

El camino correcto: paquetes de EF

Un paquete de EF es un ejecutable autónomo que contiene sus migraciones compiladas. dotnet ef database update empaquetado en una unidad independiente .exe.

¿Por qué ganan los paquetes?

  • Sin dependencias de tiempo de ejecución - El objetivo no necesita SDK o EF CLI
  • Separación adecuada - La aplicación nunca necesita db_owner; sólo el corredor CI lo hace, sólo durante el despliegue
  • Visibilidad de la CI - Los fallos se muestran en los registros de tuberías, no enterrados en el inicio de la aplicación
  • Seguridad de la marcha atrás El despliegue se detiene antes de que el código incorrecto se despliegue.
  • Idempotente - Rastrea lo que se aplica, ejecuta sólo lo que se necesita

Nota: Para la seguridad de la calidad de producción, utilizar Identidad administrada Pero los paquetes siguen siendo un gran paso adelante de las migraciones en tiempo de ejecución.

Ejemplo de acciones de GitHub

      - name: Install EF Core tools
        run: dotnet tool install --global dotnet-ef

      - name: Add EF tools to PATH
        run: echo "$HOME/.dotnet/tools" >> $GITHUB_PATH

      - name: Generate EF migration bundle
        run: |
          dotnet ef migrations bundle \
            --project ${{ env.WEB_PROJECT }} \
            --output efbundle.exe \
            --configuration ${{ env.BUILD_CONFIGURATION }} \
            --runtime ${{ env.RUNTIME_IDENTIFIER }} \
            --context AdminDbContext \
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

      - name: Run EF migration bundle
        run: |
          ./efbundle.exe
        env:
          AdminSite__ConnectionString: ${{ secrets.PROD_SQL_CONNECTIONSTRING }}

El paquete lee cadenas de conexión de variables de entorno y aplica migraciones pendientes. ¿Ya se ha aplicado? Sólo sale con éxito.

Paquetes locales

¿Quieres probar antes de empujar? Construye paquetes localmente.

Casos de uso: Prueba antes de CI, entrega DBA (exe autónomo, no necesita SDK), despliegues de puesta en escena, depuración con --verbose.

Crear un paquete

# Install EF CLI (once)
dotnet tool install --global dotnet-ef

# Basic bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# Self-contained (includes runtime - portable to machines without .NET)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe \
    --self-contained

# Cross-platform (e.g., build on Windows, deploy to Linux)
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle \
    --runtime linux-x64

Ejecutar su paquete

# Using default connection string from appsettings.json
./efbundle.exe

# Override with a specific connection string
./efbundle.exe --connection "Host=localhost;Database=mostlylucid;Username=postgres;Password=secret"

# Using an environment variable (matches your config key)
$env:ConnectionStrings__DefaultConnection="Host=localhost;..." # PowerShell
export ConnectionStrings__DefaultConnection="Host=localhost;..." # Bash
./efbundle.exe

Opciones de paquetes útiles

# See what migrations would be applied without running them
./efbundle.exe --dry-run

# Verbose output for debugging
./efbundle.exe --verbose

# Apply migrations up to a specific migration (useful for testing)
./efbundle.exe --target-migration "20231115_AddUserTable"

# Combine options
./efbundle.exe --verbose --dry-run

Flujo de trabajo local de pruebas

# 1. Create migration
dotnet ef migrations add AddNewFeature \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

# 2. Build bundle
dotnet ef migrations bundle \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid \
    --output efbundle.exe

# 3. Dry run first
./efbundle.exe --dry-run --verbose

# 4. Run for real
./efbundle.exe --verbose

# 5. Broken? Remove and retry
dotnet ef migrations remove \
    --project Mostlylucid.DbContext \
    --startup-project Mostlylucid

Captura errores de sintaxis, violaciones de restricciones, problemas FK - todos antes CI o producción.

Rendimiento de construcción

La generación de paquetes es lenta - 30+ segundos en grandes proyectos. No generar en cada construcción.

  • Generar manualmente cuando se prueba localmente
  • Generar en IC sólo durante el despliegue, no todas las relaciones públicas
  • Paquetes de caché si las migraciones no han cambiado

Si realmente quieres auto-generación, agrega un objetivo de MSBuild:

<Target Name="BuildMigrationBundle">
  <Exec Command="dotnet ef migrations bundle --output $(OutputPath)efbundle.exe --force" />
</Target>

Entonces: dotnet build -t:BuildMigrationBundle

Enfoque híbrido

Lo mejor de ambos mundos: comodidad local, seguridad en la producción.

if (builder.Environment.IsDevelopment())
{
    using var scope = app.Services.CreateScope();
    var context = scope.ServiceProvider.GetRequiredService<IMostlylucidDBContext>();
    await context.Database.MigrateAsync();
}
// Production: CI pipeline runs the bundle

Alternativas a los paquetes

Scripts SQL

Generar SQL plano en lugar de un ejecutable. Ideal para la revisión de DBA y los procesos de gestión de cambios existentes.

# All migrations
dotnet ef migrations script --output migrations.sql

# Idempotent (safe to run multiple times) - USE THIS
dotnet ef migrations script --idempotent --output migrations.sql

# Range of migrations
dotnet ef migrations script FromMigration ToMigration --output migrations.sql

Pros: Visibilidad completa, cualquier cliente SQL puede ejecutarlo, control de versiones amigable, flujos de trabajo de aprobación DBA.

Contras: Sin seguimiento automático (utilizar --idempotent), ejecución manual, deriva potencial si los scripts se modifican.

Ver Documentos oficiales en scripts SQL.

Scripts SQL en CI

- name: Generate and apply migrations
  run: |
    dotnet ef migrations script --idempotent --output migrations.sql
    # SQL Server
    sqlcmd -S ${{ secrets.DB_SERVER }} -d ${{ secrets.DB_NAME }} -i migrations.sql
    # Or PostgreSQL
    PGPASSWORD=${{ secrets.DB_PASSWORD }} psql -h ${{ secrets.DB_HOST }} -f migrations.sql

CADPAC (sólo servidor SQL)

CADPACs son con sede en el Estado no sobre la base de la migración. Usted define el esquema deseado, y SqlPackage lo diferencia contra la base de datos de destino.

SqlPackage.exe /Action:Publish /SourceFile:MyDatabase.dacpac /TargetConnectionString:"..."

Pros: Schema como código, generación de auto-diff, maneja todo (tablas, vistas, SPs, índices), herramientas empresariales.

Contras: SQL Server solamente, esquema en dos lugares (modelos EF + proyecto SQL), el motor diff hace opciones cuestionables, los renombres de columna se ven como drop+add.

Ver SqlPackage docs.

Cuadro comparativo

Aproximación Mejor para Requiere .NET Auto-pistas Aplicadas DBA Friendly Cross-plataform DB |----------|----------|---------------|---------------------|--------------|-------------------| | MigrateAsync() # Dev/pequeños proyectos # # Sí (tiempo de ejecución) # # Sí # No # # Sí # EF Bundles CI/CD tuberías No (self-contained) Sí Algo Sí SQL Scripts ambientes controlados por DBA No Con --idempotent # Sí # # Sí # # Sí # # Sí # Sí # # Sí # Sí # # Sí # Sí # # Sí # # Sí # # Sí # Sí # # Sí # Sí # Sí # Sí # Sí # Sí # CADPAC SQL Empresa de servidores No Sí (basado en el estado) Sí No

Consejos

El archivo de diseñador Gotcha

¿Las migraciones trabajan localmente pero no en CI? Compruebe que ha comprometido ambos archivos:

  • 20231115_AddUserTable.cs - Código de migración
  • 20231115_AddUserTable.Designer.cs - La instantánea del modelo

Falta el archivo Designer = fallo silencioso.

Múltiples DbContextos

dotnet ef migrations bundle --context BlogDbContext --output blog-migrations.exe
dotnet ef migrations bundle --context IdentityDbContext --output identity-migrations.exe

Prioridad de cadena de conexión

  1. --connection argumento
  2. Variable ambiental
  3. appsettings.json

Usar variables de entorno en IC.

IDesignTimeDbContextFactory

Las herramientas de EF necesitan instanciar su DbContext. Si su DbContext está en un proyecto separado o tiene un inicio complejo, implemente IDesignTimeDbContextFactory<T>:

public class AdminDbContextFactory : IDesignTimeDbContextFactory<AdminDbContext>
{
    public AdminDbContext CreateDbContext(string[] args)
    {
        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: true)
            .AddEnvironmentVariables()
            .AddUserSecrets<AdminDbContextFactory>()
            .Build();

        var connectionString = config["AdminSite:ConnectionString"]
            ?? throw new InvalidOperationException("Missing connection string");

        var optionsBuilder = new DbContextOptionsBuilder<AdminDbContext>();
        optionsBuilder.UseSqlServer(connectionString, sql => sql.CommandTimeout(120));

        return new AdminDbContext(optionsBuilder.Options);
    }
}

Utilice cuando: DbContext en un proyecto separado, inicio complejo, necesita secretos de usuario para el tiempo de diseño.

¿Qué hay de...?

Preguntas comunes y retroceso que he recibido.

"¿Por qué no simplemente correr dotnet ef database update en CI?"

Cubierto arriba, pero la versión corta: los paquetes son artefactos portátiles. Su paso de implementación no necesita EF CLI, código fuente o resolución de tiempo de diseño. El mismo paquete se ejecuta en pruebas, estadificación y prod - deriva cero.

"¿No es una exageración para una aplicación pequeña?"

Si estás solo, los datos son públicos, y el radio de explosión es bajo. MigrateAsync() Pero en el momento en que se agrega un segundo desarrollador, datos sensibles, o múltiples entornos, los paquetes pagan por sí mismos.

"¿Qué hay de los retrocesos?"

EF no hace retrocesos automáticos. Opciones:

  • Generar a Down() migración y ejecutarlo (pero tienes que haberlo escrito)
  • Restaurar desde la copia de seguridad
  • Escriba una migración manual para deshacer los cambios

Para sistemas críticos: probar primero las migraciones contra un clon de base de datos.

"¿Puedo ejecutar migraciones en un contenedor de Kubernetes?"

Sí. Bundle + init contenedor es un patrón sólido:

initContainers:
  - name: migrate
    image: myapp:latest
    command: ["./efbundle.exe"]
    env:
      - name: ConnectionStrings__Default
        valueFrom:
          secretKeyRef:
            name: db-secrets
            key: connection-string

El contenedor de aplicaciones espera a que se complete el init.

"¿Qué hay de FluentMigrator / DbUp / otras herramientas?"

Funcionan muy bien. Los paquetes de EF son la solución nativa de EF, pero FluentMigrator y DbUp diferencia clave: son herramientas específicas para la migración, mientras que los paquetes de EF provienen de su modelo de EF existente.

"Mi DBA quiere revisar todo SQL antes de que se ejecute"

Uso --idempotent scripts:

dotnet ef migrations script --idempotent --output migrations.sql

El DBA revisa y aprueba.

  • Ejecute el script manualmente, o
  • Una vez aprobado, ejecute el paquete (que hace lo mismo)

"¿Cómo puedo manejar las migraciones con cero tiempo de inactividad?"

Esa es una pregunta de estrategia de despliegue, no una pregunta de migraciones. Generalmente:

  1. Hacer las migraciones compatibles hacia atrás (añadir columnas anulables, no renombrar)
  2. Implementar código nuevo que maneja tanto el esquema antiguo como el nuevo
  3. Ejecutar migración
  4. Implementar código que solo utilice el nuevo esquema
  5. Limpiar (derivar columnas viejas en una migración posterior)

Los paquetes no resuelven esto, solo hacen el paso 3 más predecible.

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.