Start Debugging

HasData vs UseSeeding para popular dados no EF Core 11: qual voce deve usar?

Use HasData apenas para dados de referencia fixos e proprios do modelo. Use UseSeeding e UseAsyncSeeding para todo o resto no EF Core 11. Uma comparacao lado a lado com as regras que forcam a decisao.

Para popular dados no EF Core 11, use HasData apenas para tabelas de referencia pequenas, fixas e deterministicas que voce queira versionar dentro das suas migracoes (codigos de pais, moedas, linhas de consulta tipo enum). Use UseSeeding e UseAsyncSeeding para todo o resto: qualquer coisa com chaves geradas pelo banco de dados, valores calculados ou nao deterministicos, logica condicional, propriedades de navegacao, ou linhas que dependem do que ja esta no banco de dados. O time do EF renomeou HasData para “model-managed data” justamente para desencorajar seu uso como ferramenta de seeding geral. Este artigo usa .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0) e C# 14.

Se voce so lembrar de uma linha: HasData participa do modelo e do diff da migracao, UseSeeding roda como codigo comum contra um DbContext vivo. Quase todos os bugs dolorosos de seeding vem de escolher o primeiro quando voce precisava do segundo.

A matriz de recursos

Esta e a tabela que voce veio buscar. Cada linha e uma restricao real, nao uma impressao.

RecursoHasData (model-managed data)UseSeeding / UseAsyncSeeding
Configurado emOnModelCreating (o modelo)DbContextOptionsBuilder
Quando rodaDentro do SQL de migracao (InsertData)Em Migrate/EnsureCreated e dotnet ef database update
Chaves primariasDevem ser especificadas a maoChaves geradas pelo banco funcionam bem
Valores nao deterministicosProibidos (re-diff a cada build)Permitidos (Guid.NewGuid(), DateTime.UtcNow)
Propriedades de navegacaoNao, apenas chaves estrangeirasSim, insercao de grafos completos
Logica condicionalNaoSim, voce escreve o if
Chamadas externas / transformacoesNaoSim (hashing, HTTP, leitura de arquivos)
IdempotenciaAutomatica (EF faz o diff)Voce escreve a verificacao de existencia
Capturado no controle de versaoSim, no snapshot da migracaoNao, e codigo de inicializacao
Atualiza linhas existentesSim, via diff da migracaoSo se voce escrever a atualizacao
Disponivel desdeEF Core 1.0 (era HasData)EF Core 9, atual no EF Core 11

A linha mais importante de todas e “valores nao deterministicos”. HasData e comparado contra o snapshot do modelo a cada build, entao um DateTime.UtcNow ou um Guid.NewGuid() no seu seed faz o modelo parecer alterado toda vez, e o EF lanca PendingModelChangesWarning ou gera um gotejar infindavel de migracoes inuteis. Esse e o muro contra o qual a maioria esbarra, geralmente durante uma migracao do EF Core 6 para o EF Core 11.

Quando escolher HasData

HasData e a ferramenta certa quando os dados sao genuinamente parte do significado do seu schema. O teste mental: voce ficaria confortavel codificando essas linhas em uma constante, e elas quase nunca vao mudar?

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<OrderStatus>().HasData(
        new OrderStatus { Id = 1, Name = "Pending" },
        new OrderStatus { Id = 2, Name = "Shipped" },
        new OrderStatus { Id = 3, Name = "Delivered" });
}

As chaves devem ser definidas explicitamente e os valores devem ser deterministicos. Nada de Guid.NewGuid(), nada de DateTime.Now, nenhuma propriedade de navegacao (defina a coluna de chave estrangeira diretamente). Quebre qualquer uma dessas e o HasData vai brigar com voce a cada build.

Quando escolher UseSeeding e UseAsyncSeeding

UseSeeding e o mecanismo de proposito geral recomendado no EF Core 11. E codigo de aplicacao comum com um DbContext vivo, entao os limites do HasData simplesmente nao existem. Recorra a ele sempre que qualquer uma destas for verdadeira:

// .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0), C# 14
services.AddDbContext<AppDbContext>(options =>
    options
        .UseSqlServer(connectionString)
        .UseAsyncSeeding(async (context, _, ct) =>
        {
            if (!await context.Set<User>().AnyAsync(u => u.Email == "admin@example.com", ct))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"), // runtime transform
                    CreatedAt = DateTime.UtcNow                        // non-deterministic
                });
                await context.SaveChangesAsync(ct);
            }
        })
        .UseSeeding((context, _) =>
        {
            if (!context.Set<User>().Any(u => u.Email == "admin@example.com"))
            {
                context.Set<User>().Add(new User
                {
                    Email = "admin@example.com",
                    PasswordHash = PasswordHasher.Hash("change-me"),
                    CreatedAt = DateTime.UtcNow
                });
                context.SaveChanges();
            }
        }));

A pegadinha e a que a maioria nao percebe: voce tem que implementar os dois overloads, e a verificacao de existencia fica por sua conta. As ferramentas do EF Core (dotnet ef database update) so chamam o UseSeeding sincrono; seu caminho de tempo de execucao chama o UseAsyncSeeding. Implemente um e nao o outro e o seeding silenciosamente nao faz nada a partir do caminho que voce esqueceu. Para os mecanismos completos, veja como popular dados com UseSeeding e UseAsyncSeeding.

O que de fato acontece em disco

A forma mais clara de ver a diferenca e olhar o que cada um produz.

HasData se materializa na sua migracao. Apos dotnet ef migrations add SeedStatuses, o metodo Up gerado contem chamadas com forma de SQL literal:

// .NET 11, EF Core 11 generated migration
migrationBuilder.InsertData(
    table: "OrderStatuses",
    columns: new[] { "Id", "Name" },
    values: new object[,]
    {
        { 1, "Pending" },
        { 2, "Shipped" },
        { 3, "Delivered" }
    });

Os dados agora sao um artefato versionado. Roda uma vez, dentro da transacao da migracao, e o EF o rastreia no snapshot do modelo, entao uma edicao posterior produz um UpdateData. E exatamente por isso que um valor nao deterministico e veneno aqui: o snapshot nunca casaria, entao o EF acreditaria que o modelo mudou a cada build.

UseSeeding nao produz nada em disco. E um callback que dispara depois que o schema esta atualizado, em cada Migrate, EnsureCreated ou dotnet ef database update, incluindo execucoes em que nenhuma migracao foi aplicada. Como roda incondicionalmente, a verificacao de existencia nao e tarefa opcional de manutencao, e a unica coisa entre voce e as linhas duplicadas. O EF Core 11 protege o callback com o mesmo mecanismo de bloqueio de migracao que usa para migracoes, entao duas instancias da aplicacao iniciando ao mesmo tempo nao vao rodar ambas o seed concorrentemente, mas o bloqueio nao torna idempotente um if que falta.

A pegadinha que decide por voce

Algumas restricoes anulam a preferencia por completo. Se alguma destas se aplica, a decisao ja esta tomada por voce:

Voce pode perfeitamente usar os dois em uma so aplicacao. Uma divisao comum e saudavel: HasData para as tres tabelas de consulta sem as quais seu schema nao funciona, e UseSeeding para os dados de demonstracao, o admin padrao e qualquer coisa especifica de tenant. Eles nao entram em conflito, porque rodam em estagios diferentes.

A recomendacao, repetida

Por padrao use UseSeeding e UseAsyncSeeding no EF Core 11. Sao o mecanismo recomendado, lidam com os dados dinamicos, com chave gerada e condicionais que as aplicacoes reais de fato populam, e falham ruidosamente em vez de corromper silenciosamente seu historico de migracoes. Reserve HasData para o caso estreito que ele foi renomeado para servir: dados de referencia pequenos, fixos e deterministicos com chaves atribuidas a mao que voce genuinamente queira versionar e diffar junto ao seu schema.

A armadilha a evitar e o padrao historico. Por anos HasData foi a unica opcao embutida, entao as bases de codigo recorriam a ele por reflexo e depois se afogavam em migracoes fantasma e PendingModelChangesWarning. Se voce esta comecando do zero no EF Core 11, inverta esse instinto: UseSeeding primeiro, HasData apenas quando voce conseguir nomear por que os dados pertencem ao modelo. Se voce mantem entidades baseadas em records, a mesma divisao se aplica de forma limpa, ja que records funcionam bem com EF Core 11 sob os dois mecanismos.

Relacionado

Fontes

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< Voltar