As migrations do EF Core são o sistema de controle de versão para o esquema do seu banco de dados. Cada alteração no seu modelo de entidades é capturada como um arquivo de migration com timestamp e reversível. Este guia cobre tudo: desde a primeira migration até as estratégias de implantação em produção.
A entidade Products e as quatro migrations reais abaixo — incluindo a do backfill editada à mão — estão em
samples/ef-core-migrations-walkthrough.
Foram geradas pelo dotnet ef, não digitadas à mão, então você pode inspecionar e executar os arquivos exatos (veja Veja funcionar).
Pré-requisitos
Instale as ferramentas do EF Core globalmente e adicione o pacote de design ao seu projeto:
dotnet tool install --global dotnet-ef
dotnet tool update --global dotnet-ef # Atualizar se já estiver instalado
# Adicionar ao projeto
dotnet add package Microsoft.EntityFrameworkCore.DesignVerifique se está funcionando:
dotnet ef --version
# Entity Framework Core .NET Command-line Tools 10.x.xMantenha a versão da ferramenta dotnet-ef alinhada com a dos seus pacotes Microsoft.EntityFrameworkCore.*. Uma ferramenta várias versões maiores atrás dos seus pacotes (uma do EF Core 8 contra projetos do EF Core 10, por exemplo) pode falhar ao carregar o assembly de design. dotnet tool update --global dotnet-ef --version 10.* a fixa.
Adicionando sua primeira migration
Após configurar seu DbContext e as entidades, crie a migration inicial:
dotnet ef migrations add InitialCreateO EF Core inspeciona seu DbContext, compara com o estado atual do banco de dados (vazio, no caso da primeira migration) e gera os arquivos de migration.
Três arquivos são criados na pasta Migrations/:
Migrations/
20250120143000_InitialCreate.cs ← A migration em si
20250120143000_InitialCreate.Designer.cs ← Metadados do snapshot (não editar)
AppDbContextModelSnapshot.cs ← Snapshot do modelo atual (não editar)Entendendo o arquivo de migration
// Migrations/20250120143000_InitialCreate.cs
public partial class InitialCreate : Migration
{
/// <summary>
/// Executado ao aplicar a migration (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>
/// Executado ao reverter a migration (dotnet ef database update <previous>)
/// </summary>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "Products");
}
}O arquivo ModelSnapshot é crítico — o EF Core o utiliza para determinar o que mudou entre as migrations. Nunca o exclua.
Aplicando migrations
# Aplicar todas as migrations pendentes
dotnet ef database update
# Aplicar até uma migration específica
dotnet ef database update AddProductDescription
# Aplicar apenas a migration inicial
dotnet ef database update InitialCreateO EF Core mantém uma tabela __EFMigrationsHistory no seu banco de dados que registra quais migrations foram aplicadas:
SELECT * FROM __EFMigrationsHistory;
-- MigrationId | ProductVersion
-- 20250120143000_InitialCreate | 8.0.0
-- 20250124091500_AddProductDescription | 8.0.0Adicionando migrations subsequentes
Após alterar uma entidade, adicione uma nova migration:
// Adicionar uma propriedade Description ao 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; } // Nova propriedade
public DateTime CreatedAt { get; set; }
}dotnet ef migrations add AddProductDescription
dotnet ef database updateA migration gerada contém apenas o 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");
}Revertendo migrations
Reverter para uma migration específica
# Reverter para depois de InitialCreate (desfaz AddProductDescription)
dotnet ef database update InitialCreate
# Reverter todas as migrations (banco vazio — tabelas excluídas, mas __EFMigrationsHistory permanece)
dotnet ef database update 0Reverter exclui dados. dotnet ef database update 0 vai excluir todas as suas tabelas. Faça isso apenas em desenvolvimento ou com um backup confirmado.
Remover a última migration (antes de aplicá-la)
Se você ainda não aplicou uma migration, pode removê-la completamente:
dotnet ef migrations removeIsso exclui o arquivo de migration e reverte o ModelSnapshot. Você só pode remover a migration mais recente e somente se ela não tiver sido aplicada a nenhum banco de dados.
Se você acidentalmente executar dotnet ef database update em uma migration com erros, primeiro você precisa reverter o banco de dados antes de remover o arquivo de migration.
Personalizando migrations
As migrations geradas pelo EF Core são um ponto de partida — você pode editá-las para adicionar seed de dados, colunas calculadas ou outras operações que o EF Core não consegue detectar automaticamente:
protected override void Up(MigrationBuilder migrationBuilder)
{
// EF Core gerou isso
migrationBuilder.AddColumn<string>(
name: "Slug",
table: "Products",
nullable: true);
// Você adicionou isso — preenche Slug a partir dos dados existentes de Name
migrationBuilder.Sql(@"
UPDATE Products
SET Slug = LOWER(REPLACE(Name, ' ', '-'))
WHERE Slug IS NULL
");
// Depois torna não nulo após o preenchimento
migrationBuilder.AlterColumn<string>(
name: "Slug",
table: "Products",
nullable: false,
oldClrType: typeof(string),
oldNullable: true);
}Editar uma migration que já foi aplicada a qualquer banco de dados (desenvolvimento, staging, produção) é perigoso. O ModelSnapshot não vai corresponder ao seu arquivo de migration. Edite apenas migrations que ainda não foram aplicadas.
Esta edição exata é a migration AddProductSlug do sample. O EF gerou primeiro um único AddColumn não-nullable com defaultValue: "" — que compila, mas deixa cada linha existente com um slug vazio. Ela foi editada à mão para a forma de três passos acima, e o sample semeia linhas antes de aplicá-la, então você vê Mechanical Keyboard virar mechanical-keyboard. Um detalhe que vale saber: mantenha o tipo da coluna na sua migration editada igual ao do modelo (nvarchar(max) aqui), ou dotnet ef migrations has-pending-model-changes vai reportar uma diferença fantasma contra o snapshot.
Consolidando migrations
Com o tempo, centenas de migrations se acumulam. Consolidá-las as combina em uma única migration para um histórico mais limpo. Isso é feito tipicamente em uma fronteira de versão maior.
Método 1: Começar do zero (destrutivo — apenas para desenvolvimento)
# 1. Excluir todos os arquivos de migration
# 2. Excluir o banco de dados
dotnet ef database drop --force
# 3. Adicionar uma única nova migration inicial
dotnet ef migrations add InitialCreate
# 4. Recriar o banco de dados
dotnet ef database updateMétodo 2: Consolidar sem perder dados (seguro para produção)
# 1. Anotar o nome da migration atual
dotnet ef migrations list
# 2. Adicionar uma nova migration de consolidação que parte do zero
# (EF Core não vai gerar nada — o modelo já corresponde ao BD)
dotnet ef migrations add Squash_v2 --no-build
# 3. Substituir manualmente o Up() e o Down() da nova migration
# pelo SQL completo de criação/destruição do esquema
# 4. Atualizar __EFMigrationsHistory em produção para remover as entradas antigas
# e adicionar apenas a nova entrada de migration consolidadaUma abordagem mais prática usando a saída do Script-Migration:
# Gerar script SQL do esquema atual completo
dotnet ef migrations script 0 AddProductDescription --output schema_v2.sqlGerando scripts SQL
Para implantações em produção, gere um script SQL em vez de executar dotnet ef database update diretamente:
# Script completo do zero até a versão mais recente
dotnet ef migrations script --output migration.sql
# Script de uma migration específica até a mais recente (idempotente)
dotnet ef migrations script InitialCreate --idempotent --output migration.sql
# Script entre duas migrations específicas
dotnet ef migrations script InitialCreate AddProductDescription --output delta.sqlO flag --idempotent gera um script que verifica __EFMigrationsHistory antes de executar cada migration — seguro para executar múltiplas vezes:
IF NOT EXISTS(SELECT * FROM [__EFMigrationsHistory] WHERE [MigrationId] = N'20250124091500_AddProductDescription')
BEGIN
ALTER TABLE [Products] ADD [Description] nvarchar(max) NULL;
END;
GOEstratégia de implantação em produção
Opção 1: Migration automática na inicialização
Aplica as migrations pendentes quando a aplicação 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();Indicado para: Equipes pequenas, implantações blue/green onde apenas uma versão roda por vez.
Arriscado quando: Várias instâncias iniciam simultaneamente (condição de corrida nas migrations). Mitigue isso com um lock distribuído ou executando as migrations como uma etapa pré-implantação.
Opção 2: Migration como job separado
Execute as migrations como um job único antes de implantar a nova versão da aplicação:
# No seu pipeline de CI/CD, antes de implantar a app:
dotnet ef database update --connection "$PRODUCTION_CONNECTION_STRING"
# Ou usando um job de migration dedicado no Docker:
dotnet run --project MyApp.Migrations// MyApp.Migrations/Program.cs (projeto de migration 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 migrations...");
await db.Database.MigrateAsync();
Console.WriteLine("Migrations concluídas.");Opção 3: Scripts SQL no CI/CD
A abordagem mais segura para ambientes regulados:
# Exemplo com GitHub Actions
- name: Gerar script de migration
run: dotnet ef migrations script --idempotent --output migration.sql
- name: Aplicar script de migration
run: |
sqlcmd -S ${{ secrets.DB_SERVER }} \
-d ${{ secrets.DB_NAME }} \
-U ${{ secrets.DB_USER }} \
-P ${{ secrets.DB_PASS }} \
-i migration.sqlBoas práticas com migrations
Mantenha as migrations pequenas e focadas. Uma migration que adiciona uma coluna e preenche dados existentes são duas operações — considere dividi-las para tornar os rollbacks mais seguros.
Use nomes de migration descritivos que expliquem o que mudou: AddUserEmailIndex, RenameProductCodeToSku, CreateOrdersTable. O timestamp já é único; o nome é para as pessoas.
Nunca renomeie ou exclua arquivos de migration que foram aplicados em produção. O EF Core usa o nome do arquivo para comparar com __EFMigrationsHistory. Renomear causa erros de "migration não encontrada".
Verificando o status das migrations
# Listar todas as migrations e seu status de aplicação
dotnet ef migrations list
# Saída:
# 20250120143000_InitialCreate (Applied)
# 20250124091500_AddProductDescription (Applied)
# 20250127110000_AddIndexOnProductName (Pending)Verificação via código:
var pendingMigrations = await db.Database.GetPendingMigrationsAsync();
var appliedMigrations = await db.Database.GetAppliedMigrationsAsync();
if (pendingMigrations.Any())
{
_logger.LogWarning("Migrations pendentes: {Migrations}",
string.Join(", ", pendingMigrations));
}Veja funcionar
samples/ef-core-migrations-walkthrough
é este guia em arquivos executáveis. A pasta Migrations/ tem quatro migrations reais na
ordem acima — InitialCreate, AddProductDescription, AddIndexOnProductName e a editada
à mão AddProductSlug — cada uma gerada por dotnet ef migrations add. Abra InitialCreate
e verá o mesmo CreateTable(...) de antes, produzido pela ferramenta em vez de transcrito.
Executá-lo conta a história do backfill do início ao fim: em um banco novo, migra até o
esquema anterior ao Slug, insere duas linhas e então aplica AddProductSlug para que o
UPDATE rode sobre dados que já existem:
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"
O sample aponta para seu próprio banco de dados (JorgenHocSamples_Migrations)
justamente porque os comandos deste artigo — database update 0, database drop --force —
são destrutivos. Aponte-os para um banco descartável, nunca para um compartilhado.
dotnet ef migrations has-pending-model-changes retorna "No changes" para o sample, que é
como você confirma que uma migration editada à mão ainda corresponde ao snapshot do modelo.
Leitura Relacionada
Migrações são uma peça do panorama mais amplo do EF Core — o guia completo de EF Core cobre como o modelo, o contexto e o pipeline de consultas se encaixam ao redor delas.
Dois recursos de esquema interagem com migrações de formas que vale conhecer antes de esbarrar nelas: os filtros de consulta globais, que mudam o que suas consultas retornam sem mudar o esquema, e as consultas SQL brutas, necessárias quando uma migração exige uma transformação de dados que a API em nível de modelo não consegue expressar.