Start Debugging

HasData vs UseSeeding para sembrar datos en EF Core 11: cual deberias usar?

Usa HasData solo para datos de referencia fijos y propios del modelo. Usa UseSeeding y UseAsyncSeeding para todo lo demas en EF Core 11. Una comparacion lado a lado con las reglas que fuerzan la decision.

Para sembrar datos en EF Core 11, usa HasData solo para tablas de referencia pequenas, fijas y deterministas que quieras versionar dentro de tus migraciones (codigos de pais, monedas, filas de busqueda tipo enum). Usa UseSeeding y UseAsyncSeeding para todo lo demas: cualquier cosa con claves generadas por la base de datos, valores calculados o no deterministas, logica condicional, propiedades de navegacion, o filas que dependen de lo que ya hay en la base de datos. El equipo de EF renombro HasData como “model-managed data” precisamente para desalentar su uso como herramienta de siembra general. Este articulo usa .NET 11, EF Core 11 (Microsoft.EntityFrameworkCore 11.0) y C# 14.

Si solo recuerdas una linea: HasData participa en el modelo y en el diff de la migracion, UseSeeding se ejecuta como codigo ordinario contra un DbContext vivo. Casi todos los errores dolorosos de siembra vienen de elegir el primero cuando necesitabas el segundo.

La matriz de funcionalidades

Esta es la tabla por la que viniste. Cada fila es una restriccion real, no una sensacion.

FuncionalidadHasData (model-managed data)UseSeeding / UseAsyncSeeding
Se configura enOnModelCreating (el modelo)DbContextOptionsBuilder
Cuando se ejecutaDentro del SQL de migracion (InsertData)En Migrate/EnsureCreated y dotnet ef database update
Claves primariasDeben especificarse a manoLas claves generadas por la base de datos funcionan bien
Valores no deterministasProhibidos (re-diff en cada compilacion)Permitidos (Guid.NewGuid(), DateTime.UtcNow)
Propiedades de navegacionNo, solo claves foraneasSi, insercion de grafos completos
Logica condicionalNoSi, tu escribes el if
Llamadas externas / transformacionesNoSi (hashing, HTTP, lectura de archivos)
IdempotenciaAutomatica (EF hace el diff)Tu escribes la comprobacion de existencia
Capturado en control de codigoSi, en el snapshot de migracionNo, es codigo de arranque
Actualiza filas existentesSi, via diff de migracionSolo si escribes la actualizacion
Disponible desdeEF Core 1.0 (era HasData)EF Core 9, vigente en EF Core 11

La fila mas importante de todas es “valores no deterministas”. HasData se compara contra el snapshot del modelo en cada compilacion, asi que un DateTime.UtcNow o un Guid.NewGuid() en tu siembra hace que el modelo parezca cambiado cada vez, y EF lanza PendingModelChangesWarning o genera un goteo interminable de migraciones inutiles. Ese es el muro contra el que choca la mayoria, normalmente durante una migracion de EF Core 6 a EF Core 11.

Cuando elegir HasData

HasData es la herramienta correcta cuando los datos son genuinamente parte del significado de tu esquema. La prueba mental: estarias comodo codificando estas filas en una constante, y casi nunca van a cambiar?

// .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" });
}

Las claves deben establecerse de forma explicita y los valores deben ser deterministas. Nada de Guid.NewGuid(), nada de DateTime.Now, ninguna propiedad de navegacion (establece la columna de clave foranea directamente). Rompe cualquiera de esas y HasData peleara contigo en cada compilacion.

Cuando elegir UseSeeding y UseAsyncSeeding

UseSeeding es el mecanismo de proposito general recomendado en EF Core 11. Es codigo de aplicacion plano con un DbContext vivo, asi que los limites de HasData simplemente no existen. Recurre a el siempre que alguna de estas sea verdadera:

// .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();
            }
        }));

La trampa es la que la mayoria pasa por alto: tienes que implementar ambos overloads, y la comprobacion de existencia corre por tu cuenta. Las herramientas de EF Core (dotnet ef database update) solo llaman al UseSeeding sincrono; tu ruta de tiempo de ejecucion llama a UseAsyncSeeding. Implementa uno y no el otro y la siembra no hace nada silenciosamente desde la ruta que olvidaste. Para los mecanismos completos, mira como sembrar datos con UseSeeding y UseAsyncSeeding.

Que sucede realmente en disco

La forma mas clara de ver la diferencia es mirar lo que produce cada uno.

HasData se materializa en tu migracion. Tras dotnet ef migrations add SeedStatuses, el metodo Up generado contiene llamadas con 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" }
    });

Los datos son ahora un artefacto versionado. Se ejecuta una vez, dentro de la transaccion de la migracion, y EF lo rastrea en el snapshot del modelo, asi que una edicion posterior produce un UpdateData. Por eso exactamente un valor no determinista es veneno aqui: el snapshot nunca coincidiria, asi que EF creeria que el modelo cambio en cada compilacion.

UseSeeding no produce nada en disco. Es un callback que se dispara despues de que el esquema esta al dia, en cada Migrate, EnsureCreated o dotnet ef database update, incluidas las ejecuciones en las que no se aplico ninguna migracion. Como se ejecuta de forma incondicional, la comprobacion de existencia no es tarea opcional de mantenimiento, es lo unico que se interpone entre ti y las filas duplicadas. EF Core 11 si protege el callback con el mismo mecanismo de bloqueo de migracion que usa para las migraciones, asi que dos instancias de la aplicacion arrancando a la vez no ejecutaran ambas la siembra de forma concurrente, pero el bloqueo no hace idempotente a un if que falta.

El detalle que decide por ti

Unas pocas restricciones anulan la preferencia por completo. Si alguna de estas aplica, la decision esta tomada por ti:

Puedes usar ambos en una sola aplicacion sin problema. Una division comun y sana: HasData para las tres tablas de busqueda sin las que tu esquema no puede funcionar, y UseSeeding para los datos de demo, el admin por defecto y cualquier cosa especifica de tenant. No entran en conflicto, porque se ejecutan en etapas distintas.

La recomendacion, repetida

Por defecto usa UseSeeding y UseAsyncSeeding en EF Core 11. Son el mecanismo recomendado, manejan los datos dinamicos, con clave generada y condicionales que las aplicaciones reales realmente siembran, y fallan ruidosamente en lugar de corromper silenciosamente tu historial de migraciones. Reserva HasData para el caso estrecho al que fue renombrado para servir: datos de referencia pequenos, fijos y deterministas con claves asignadas a mano que genuinamente quieras versionar y diffear junto a tu esquema.

La trampa a evitar es el valor por defecto historico. Durante anos HasData fue la unica opcion integrada, asi que las bases de codigo recurrian a el por reflejo y luego se ahogaban en migraciones fantasma y PendingModelChangesWarning. Si empiezas desde cero en EF Core 11, invierte ese instinto: UseSeeding primero, HasData solo cuando puedas nombrar por que los datos pertenecen al modelo. Si mantienes entidades basadas en records, la misma division aplica limpiamente, ya que los records funcionan bien con EF Core 11 bajo ambos mecanismos.

Relacionado

Fuentes

Comments

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

< Volver