//JorgenHoc
← Todos os artigos
EF CorePor Jorge CalderónAtualizado 11 min read

Migrations do EF Core: O Guia Definitivo

Um guia completo sobre migrations do EF Core: adicionar, entender, aplicar, reverter, consolidar e implantar migrations com segurança em bancos de dados de produção.

#entity-framework#dotnet#database

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.Design

Verifique se está funcionando:

dotnet ef --version
# Entity Framework Core .NET Command-line Tools 10.x.x
💡

Mantenha 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 InitialCreate

O 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 InitialCreate

O 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.0

Adicionando 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 update

A 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 0
⚠️

Reverter 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 remove

Isso 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 update

Mé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 consolidada

Uma 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.sql

Gerando 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.sql

O 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;
GO

Estraté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.sql

Boas 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"
Saída de console do sample de migrations: as quatro migrations InitialCreate, AddProductDescription, AddIndexOnProductName e AddProductSlug listadas como pendentes, depois o programa migrando para o esquema anterior ao Slug, semeando duas linhas e aplicando AddProductSlug; a tabela __EFMigrationsHistory lista as quatro migrations aplicadas, e os dois produtos mostram seu Slug preenchido a partir do Name — Mechanical Keyboard para mechanical-keyboard e USB C Cable para usb-c-cable.
A execução do sample em um banco novo: quatro migrations reais aplicadas em ordem, e a editada à mão AddProductSlug preenchendo Slug a partir de Name em linhas que existiam antes da coluna.
💡

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.

Leituras adicionais

Sobre o autor

Jorge Calderón

Engenheiro de software com mais de uma década construindo e operando aplicações .NET em produção — camadas de dados com EF Core, serviços intensivos em async e implantações em Azure e contêineres. Todos os benchmarks e projetos de exemplo destes guias estão publicados em um repositório público no GitHub para que você possa reproduzi-los.

Perfil no GitHubLinkedIn ↗Benchmarks e código de exemplo

Artigos relacionados