c# - studio - entity framework initializer seed
Marco de la entidad-Migraciones-Código primero-Siembra por migración (4)
Estoy investigando las migraciones en un esfuerzo por limpiar nuestros procesos de implementación. Mientras menos intervención manual se requiera al impulsar un cambio en la producción, mejor.
Me he encontrado con 3 inconvenientes principales con el sistema de migraciones. Son obstáculos si no puedo encontrar una manera limpia de rodearlos.
1. ¿Cómo agrego los datos de semilla por migración?
Ejecuto el comando "add-migration" que andamia un nuevo archivo de migración con las funciones Arriba y Abajo. Ahora, quiero hacer cambios a los datos automáticamente con los cambios hacia arriba y hacia abajo. No quiero agregar los datos de Seed al método Configuration.Seed ya que esto se ejecuta para todas las migraciones, lo que termina con todo tipo de problemas de duplicación.
2. Si lo anterior no es posible, ¿cómo evito las duplicaciones?
Tengo una enumeración en la que realizo un ciclo para agregar los valores a la base de datos.
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
context.SaveChanges();
Aunque estoy usando AddOrUpdate, todavía obtengo duplicados en la base de datos. El código anterior me lleva a mi tercer y último problema:
3. ¿Cómo puedo sembrar las claves primarias?
Mi enumerable con el código anterior es:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
public int AccessId { get; set; }
public string Name { get; set; }
}
Estoy especificando los valores que quiero como mi clave principal, pero Entity Framework parece ignorarlo. Todavía terminan siendo 1,2,3. ¿Cómo obtengo que sea 10,20,30?
¿Están estas limitaciones de EF en este momento o son restricciones intencionales para evitar algún otro tipo de catástrofe que no estoy viendo?
- Cuando he corregido los datos que deseo insertar con una migración, pongo los insertos directamente en la migración de Up () usando llamadas a
Sql("Insert ...")
. Vea la nota a la mitad de esta página: cómo insertar datos fijos - Para evitar duplicados en el método Seed, llama a la sobrecarga AddOrUpdate que toma una expresión de identificador que especifica la clave natural: consulta esta respuesta y esta entrada de blog .
- Las claves principales que son enteros se crean como campos de identidad de forma predeterminada. Para especificar lo contrario, use el atributo
[DatabaseGenerated(DatabaseGeneratedOption.None)]
Creo que esta es una buena explicación de los métodos Initializer y Seed
Aquí hay un ejemplo de cómo usar el método AddOrUpdate:
foreach(var enumValue in Enum.GetValues(typeof(Access.Level)))
{
context.Access.AddOrUpdate(
x => x.Name, //the natural key is "Name"
new Access { AccessId = ((int)enumValue), Name = enumValue.ToString() }
);
}
Como posible solución al ítem 1, realicé una implementación de la estrategia IDatabaseInitializer
que ejecutará el método de Semilla de cada migración pendiente solamente, necesitará implementar una interfaz personalizada de IMigrationSeed
en cada una de sus clases de DbMigration
, entonces el método de Seed
será implementado justo después de los métodos Up
y Down
de cada clase de migración.
Esto ayuda a resolver dos problemas para mí:
- Migración del modelo de base de datos grupal con migración de datos de base de datos (o siembra)
- Compruebe qué parte del código de migración de la semilla realmente debería estar ejecutándose, no verificando los datos en la base de datos, sino utilizando datos ya conocidos, que es el modelo de la base de datos que se acaba de crear.
La interfaz se ve así
public interface IMigrationSeed<TContext>
{
void Seed(TContext context);
}
A continuación se muestra la nueva implementación que llamará a este método de Seed
public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
: IDatabaseInitializer<TContext>
where TContext : DbContext
where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
public virtual void InitializeDatabase(TContext context)
{
var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
var pendingMigrations = migratorBase.GetPendingMigrations().ToArray();
if (pendingMigrations.Any()) // Is there anything to migrate?
{
// Applying all migrations
migratorBase.Update();
// Here all migrations are applied
foreach (var pendingMigration in pendingMigrations)
{
var migrationName = pendingMigration.Substring(pendingMigration.IndexOf(''_'') + 1);
var t = typeof(TMigrationsConfiguration).Assembly.GetType(
typeof(TMigrationsConfiguration).Namespace + "." + migrationName);
if (t != null
&& t.GetInterfaces().Any(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(IMigrationSeed<>)))
{
// Apply migration seed
var seedMigration = (IMigrationSeed<TContext>)Activator.CreateInstance(t);
seedMigration.Seed(context);
context.SaveChanges();
}
}
}
}
}
Lo bueno aquí es que tienes un contexto EF real para manipular los datos de semilla, al igual que la implementación estándar de EF Seed. Sin embargo, esto puede ser extraño si, por ejemplo, decide eliminar una tabla que fue sembrada en una migración anterior, tendrá que refactorizar su código de semilla existente en consecuencia.
EDITAR: como alternativa para implementar el método de inicialización después de Up y Down, puede crear una clase parcial de la misma clase de migración; esto me pareció útil, ya que me permite eliminar de forma segura la clase de migración cuando deseo volver a generar el misma migración.
Hola, he encontrado una información muy útil para su problema en este enlace: Safari Books Online
"1. Cómo agrego los datos de semilla por migración:" Como puede ver en el ejemplo, necesita crear una nueva confirmación para la siembra. Esta configuración de inicialización debe invocarse después de la migración.
public sealed class Configuration : DbMigrationsConfiguration
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SafariCodeFirst.SeminarContext context)
{
// This method will be called after migrating to the latest version.
// You can use the DbSet<T>.AddOrUpdate() helper extension method
// to avoid creating duplicate seed data. E.g.
//
// context.People.AddOrUpdate(
// p => p.FullName,
// new Person { FullName = "Andrew Peters" },
// new Person { FullName = "Brice Lambson" },
// new Person { FullName = "Rowan Miller" }
// );
//
}
}
"2. Si lo anterior no es posible, ¿cómo evito las duplicaciones?"
AddOrUpdate debe ayudarle exactamente a avoding los duplicados si obtiene un error aquí podría tener un error de configuración publicar la pila de llamadas por favor. Mira el ejemplo!
"3. ¿Cómo puedo sembrar las claves primarias?"
Aquí también está en su definición de clave. Si su clave es DatabaseGenerated(DatabaseGeneratedOption.Identity)
, entonces no tiene que proporcionarla. En algunos otros senarios, necesita crear uno nuevo, depende del tipo de clave.
"¿Son estas limitaciones de EF en este momento o son restricciones intencionales para evitar algún otro tipo de catástrofe que no estoy viendo?"
¡No que yo sepa!
OK, así que con un poco de golpes he logrado golpear EF en sumisión. Aquí esta lo que hice:
1. No hay forma de que encuentre datos para una migración específica. Todo debe ir al método común Configuration.Seed.
2. Para evitar duplicados, tuve que hacer 2 cosas. Para mis enumeraciones, escribí el siguiente código de semilla:
foreach (var enumValue in Enum.GetValues(typeof(Access.Level)))
{
var id = (int)enumValue;
var val = enumValue.ToString();
if(!context.Access.Any(e => e.AccessId == id))
context.Access.Add(
new Access { AccessId = id, Name = val }
);
}
context.SaveChanges();
Así que, básicamente, solo verifica si existe y agregando si no
3. Para que lo anterior funcione, debe ser capaz de insertar valores de clave primaria. Afortunadamente para mí, esta tabla siempre tendrá los mismos datos estáticos para poder desactivar el incremento automático. Para hacer eso, el código se ve así:
public class Access
{
public enum Level
{
None = 10,
Read = 20,
ReadWrite = 30
}
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int AccessId { get; set; }
public string Name { get; set; }
}