Las migraciones de EF Core son el sistema de control de versiones para el esquema de tu base de datos. Cada cambio en tu modelo de entidades queda capturado como un archivo de migración con marca de tiempo y reversible. Este recorrido cubre todo: desde la primera migración hasta las estrategias de despliegue en producción.
La entidad Products y las cuatro migraciones reales de abajo — incluida la del backfill editada a mano — están en
samples/ef-core-migrations-walkthrough.
Las generó dotnet ef, no se escribieron a mano, así que puedes inspeccionar y ejecutar los archivos exactos (ver Míralo funcionar).
Requisitos previos
Instala las herramientas de EF Core de forma global y agrega el paquete de diseño a tu proyecto:
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef # Actualizar si ya está instalado
# Agregar al proyecto
dotnet add package Microsoft.EntityFrameworkCore.DesignVerifica que funciona:
dotnet ef --version
# Entity Framework Core .NET Command-line Tools 10.x.xMantén la versión de la herramienta dotnet-ef alineada con la de tus paquetes Microsoft.EntityFrameworkCore.*. Una herramienta varias versiones mayores por detrás de tus paquetes (una de EF Core 8 contra proyectos de EF Core 10, por ejemplo) puede fallar al cargar el ensamblado de diseño. dotnet tool update --global dotnet-ef --version 10.* la fija.
Cómo agregar tu primera migración
Después de configurar tu DbContext y las entidades, crea la migración inicial:
dotnet ef migrations add InitialCreateEF Core inspecciona tu DbContext, lo compara con el estado actual de la base de datos (vacía, en el caso de la primera migración) y genera los archivos de migración.
Aparecen tres archivos en una carpeta Migrations/:
Migrations/
20250120143000_InitialCreate.cs ← La migración en sí
20250120143000_InitialCreate.Designer.cs ← Metadatos de instantánea (no editar)
AppDbContextModelSnapshot.cs ← Instantánea del modelo actual (no editar)Cómo entender el archivo de migración
// Migrations/20250120143000_InitialCreate.cs
public partial class InitialCreate : Migration
{
/// <summary>
/// Se ejecuta al aplicar la migración (dotnet ef database update)
/// </summary>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Products",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
Name = table.Column<string>(type: "nvarchar(200)", maxLength: 200, nullable: false),
Price = table.Column<decimal>(type: "decimal(18,2)", precision: 18, scale: 2, nullable: false),
CreatedAt = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Products", x => x.Id);
});
}
/// <summary>
/// Se ejecuta al revertir la migración (dotnet ef database update <previous>)
/// </summary>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "Products");
}
}El archivo ModelSnapshot es crítico: EF Core lo usa para determinar qué cambió entre migraciones. Nunca lo elimines.
Cómo aplicar migraciones
# Aplicar todas las migraciones pendientes
dotnet ef database update
# Aplicar hasta una migración específica
dotnet ef database update AddProductDescription
# Aplicar solo la migración inicial
dotnet ef database update InitialCreateEF Core mantiene una tabla __EFMigrationsHistory en tu base de datos que registra qué migraciones se han aplicado:
SELECT * FROM __EFMigrationsHistory;
-- MigrationId | ProductVersion
-- 20250120143000_InitialCreate | 8.0.0
-- 20250124091500_AddProductDescription | 8.0.0Cómo agregar migraciones posteriores
Después de modificar una entidad, agrega una nueva migración:
// Agregar una propiedad Description al Product
public class Product
{
public int Id { get; set; }
public required string Name { get; set; }
public decimal Price { get; set; }
public string? Description { get; set; } // Nueva propiedad
public DateTime CreatedAt { get; set; }
}dotnet ef migrations add AddProductDescription
dotnet ef database updateLa migración generada contiene únicamente el delta:
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Description",
table: "Products",
type: "nvarchar(max)",
nullable: true);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Description",
table: "Products");
}Cómo revertir migraciones
Revertir a una migración específica
# Revertir hasta después de InitialCreate (deshace AddProductDescription)
dotnet ef database update InitialCreate
# Revertir todas las migraciones (base de datos vacía — tablas eliminadas, pero __EFMigrationsHistory permanece)
dotnet ef database update 0Revertir elimina datos. dotnet ef database update 0 eliminará todas tus tablas. Haz esto solo en desarrollo o con un respaldo confirmado.
Eliminar la última migración (antes de aplicarla)
Si todavía no has aplicado una migración, puedes eliminarla completamente:
dotnet ef migrations removeEsto borra el archivo de migración y revierte el ModelSnapshot. Solo puedes eliminar la migración más reciente, y únicamente si no se ha aplicado a ninguna base de datos.
Si accidentalmente ejecutas dotnet ef database update sobre una migración con errores, primero debes revertir la base de datos antes de eliminar el archivo de migración.
Cómo personalizar migraciones
Las migraciones generadas por EF Core son un punto de partida: puedes editarlas para agregar inicialización de datos, columnas calculadas u otras operaciones que EF Core no puede detectar automáticamente:
protected override void Up(MigrationBuilder migrationBuilder)
{
// EF Core generó esto
migrationBuilder.AddColumn<string>(
name: "Slug",
table: "Products",
nullable: true);
// Tú agregaste esto — rellena Slug con los datos existentes de Name
migrationBuilder.Sql(@"
UPDATE Products
SET Slug = LOWER(REPLACE(Name, ' ', '-'))
WHERE Slug IS NULL
");
// Luego hazlo no nulo después de rellenarlo
migrationBuilder.AlterColumn<string>(
name: "Slug",
table: "Products",
nullable: false,
oldClrType: typeof(string),
oldNullable: true);
}Editar una migración que ya se aplicó a cualquier base de datos (desarrollo, staging, producción) es peligroso. El ModelSnapshot no coincidirá con tu archivo de migración. Edita únicamente migraciones que no hayan sido aplicadas.
Esta edición exacta es la migración AddProductSlug del sample. EF generó primero un único AddColumn no-nullable con defaultValue: "" — que compila, pero deja cada fila existente con un slug vacío. Se editó a mano a la forma de tres pasos de arriba, y el sample siembra filas antes de aplicarla, así que puedes ver cómo Mechanical Keyboard se convierte en mechanical-keyboard. Un detalle que conviene saber: mantén el tipo de columna de tu migración editada igual al del modelo (nvarchar(max) aquí), o dotnet ef migrations has-pending-model-changes reportará una diferencia fantasma contra el snapshot.
Cómo consolidar migraciones
Con el tiempo, se acumulan cientos de migraciones. Consolidarlas las combina en una sola para tener un historial más limpio. Esto se hace típicamente en un límite de versión mayor.
Método 1: Empezar de cero (destructivo — solo para desarrollo)
# 1. Eliminar todos los archivos de migración
# 2. Eliminar la base de datos
dotnet ef database drop --force
# 3. Agregar una única migración inicial nueva
dotnet ef migrations add InitialCreate
# 4. Recrear la base de datos
dotnet ef database updateMétodo 2: Consolidar sin perder datos (seguro para producción)
# 1. Anotar el nombre de la migración actual
dotnet ef migrations list
# 2. Agregar una nueva migración de consolidación que parta desde cero
# (EF Core no generará nada — el modelo coincide con la BD)
dotnet ef migrations add Squash_v2 --no-build
# 3. Reemplazar manualmente el Up() y el Down() de la nueva migración
# con el SQL completo de creación/destrucción del esquema
# 4. Actualizar __EFMigrationsHistory en producción para eliminar las entradas antiguas
# y agregar únicamente la nueva entrada de migración consolidadaUn enfoque más práctico usando la salida de Script-Migration:
# Generar un script SQL del esquema actual completo
dotnet ef migrations script 0 AddProductDescription --output schema_v2.sqlCómo generar scripts SQL
Para despliegues en producción, genera un script SQL en lugar de ejecutar dotnet ef database update directamente:
# Script completo desde cero hasta la versión más reciente
dotnet ef migrations script --output migration.sql
# Script desde una migración específica hasta la más reciente (idempotente)
dotnet ef migrations script InitialCreate --idempotent --output migration.sql
# Script entre dos migraciones específicas
dotnet ef migrations script InitialCreate AddProductDescription --output delta.sqlEl flag --idempotent genera un script que verifica __EFMigrationsHistory antes de ejecutar cada migración, por lo que es seguro ejecutarlo múltiples veces:
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20250124091500_AddProductDescription')
BEGIN
ALTER TABLE [Products] ADD [Description] nvarchar(max) NULL;
END;
GOEstrategia de despliegue en producción
Opción 1: Migración automática al arrancar
Aplica las migraciones pendientes cuando la aplicación inicia:
// Program.cs
var app = builder.Build();
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Database.MigrateAsync();
}
app.Run();Ideal para: Equipos pequeños, despliegues blue/green donde solo corre una versión a la vez.
Riesgoso cuando: Varias instancias arrancan simultáneamente (condición de carrera en las migraciones). Mitiga esto con un bloqueo distribuido o ejecutando las migraciones como un paso previo al despliegue.
Opción 2: Migración como trabajo independiente
Ejecuta las migraciones como un trabajo puntual antes de desplegar la nueva versión de la aplicación:
# En tu pipeline de CI/CD, antes de desplegar la app:
dotnet ef database update --connection "$PRODUCTION_CONNECTION_STRING"
# O usando un trabajo de migración dedicado en Docker:
dotnet run --project MyApp.Migrations// MyApp.Migrations/Program.cs (proyecto de migración dedicado)
var host = Host.CreateDefaultBuilder(args)
.ConfigureServices((context, services) =>
{
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(context.Configuration.GetConnectionString("DefaultConnection")));
})
.Build();
using var scope = host.Services.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Console.WriteLine("Aplicando migraciones...");
await db.Database.MigrateAsync();
Console.WriteLine("Migraciones completadas.");Opción 3: Scripts SQL en CI/CD
El enfoque más seguro para entornos regulados:
# Ejemplo con GitHub Actions
- name: Generar script de migración
run: dotnet ef migrations script --idempotent --output migration.sql
- name: Aplicar script de migración
run: |
sqlcmd -S ${{ secrets.DB_SERVER }} \
-d ${{ secrets.DB_NAME }} \
-U ${{ secrets.DB_USER }} \
-P ${{ secrets.DB_PASS }} \
-i migration.sqlBuenas prácticas con migraciones
Mantén las migraciones pequeñas y enfocadas. Una migración que agrega una columna y rellena datos existentes son dos operaciones: considera dividirlas para hacer los rollbacks más seguros.
Usa nombres de migración descriptivos que expliquen qué cambió: AddUserEmailIndex, RenameProductCodeToSku, CreateOrdersTable. La marca de tiempo ya es única; el nombre es para las personas.
Nunca renombres ni elimines archivos de migración que se hayan aplicado en producción. EF Core usa el nombre del archivo para compararlo con __EFMigrationsHistory. Renombrarlo provoca errores de "migración no encontrada".
Cómo verificar el estado de las migraciones
# Listar todas las migraciones y su estado de aplicación
dotnet ef migrations list
# Salida:
# 20250120143000_InitialCreate (Applied)
# 20250124091500_AddProductDescription (Applied)
# 20250127110000_AddIndexOnProductName (Pending)Verificación mediante código:
var pendingMigrations = await db.Database.GetPendingMigrationsAsync();
var appliedMigrations = await db.Database.GetAppliedMigrationsAsync();
if (pendingMigrations.Any())
{
_logger.LogWarning("Migraciones pendientes: {Migrations}",
string.Join(", ", pendingMigrations));
}Míralo funcionar
samples/ef-core-migrations-walkthrough
es este tutorial en archivos ejecutables. La carpeta Migrations/ tiene cuatro migraciones
reales en el orden de arriba — InitialCreate, AddProductDescription,
AddIndexOnProductName y la editada a mano AddProductSlug — cada una generada por
dotnet ef migrations add. Abre InitialCreate y verás el mismo CreateTable(...) de
antes, producido por la herramienta en vez de transcrito.
Ejecutarlo cuenta la historia del backfill de principio a fin: en una base de datos nueva
migra al esquema previo a Slug, inserta dos filas y luego aplica AddProductSlug para que
el UPDATE corra sobre datos que ya existen:
Migrating to AddIndexOnProductName (pre-Slug schema)...
Seeded 2 rows, then applying AddProductSlug (backfills Slug)...
Products (Slug backfilled from Name by the AddProductSlug migration):
Mechanical Keyboard -> slug "mechanical-keyboard"
USB C Cable -> slug "usb-c-cable"
El sample apunta a su propia base de datos (JorgenHocSamples_Migrations) precisamente
porque los comandos de este artículo — database update 0, database drop --force — son
destructivos. Apúntalos a una base de datos desechable, nunca a una compartida.
dotnet ef migrations has-pending-model-changes devuelve "No changes" para el sample, que es
como confirmas que una migración editada a mano sigue coincidiendo con el snapshot del modelo.
Lectura Relacionada
Las migraciones son una pieza del panorama más amplio de EF Core — la guía completa de EF Core cubre cómo encajan a su alrededor el modelo, el contexto y el pipeline de consultas.
Dos características del esquema interactúan con las migraciones de formas que conviene conocer antes de toparte con ellas: los filtros de consulta globales, que cambian lo que devuelven tus consultas sin cambiar el esquema, y las consultas SQL directas, que necesitarás cuando una migración requiera una transformación de datos que la API a nivel de modelo no puede expresar.