tutorial relaciones relacion muchos many llaves framework foraneas first español ejemplo code entity-framework ef-code-first ef-migrations

entity-framework - relaciones - relacion muchos a muchos entity framework



Cómo generar datos con relaciones de muchos a muchos en Entity Framework Migrations (3)

Respuesta actualizada

Asegúrate de leer la sección "Usar AddOrUpdate correctamente" a continuación para obtener una respuesta completa.

En primer lugar, vamos a crear una clave primaria compuesta (que consiste en la identificación de la parcela y la identificación del artículo) para eliminar los duplicados. Agregue el siguiente método en la clase DbContext:

protected override void OnModelCreating(DbModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); modelBuilder.Entity<Parcel>() .HasMany(p => p.Items) .WithMany(r => r.Parcels) .Map(m => { m.ToTable("ParcelItems"); m.MapLeftKey("ParcelId"); m.MapRightKey("BuyingItemId"); }); }

Luego implementa el método Seed así:

protected override void Seed(Context context) { context.Parcels.AddOrUpdate( p => p.Id, new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 }, new Parcel { Id = 2, Description = "Parcel 2", Weight = 2.0 }, new Parcel { Id = 3, Description = "Parcel 3", Weight = 3.0 } ); context.BuyingItems.AddOrUpdate( b => b.Id, new BuyingItem { Id = 1, Price = 10m }, new BuyingItem { Id = 2, Price = 20m } ); // Make sure that the above entities are created in the database context.SaveChanges(); var p1 = context.Parcels.Find(1); // Uncomment the following line if you are not using lazy loading. //context.Entry(p1).Collection(p => p.Items).Load(); var p2 = context.Parcels.Find(2); // Uncomment the following line if you are not using lazy loading. //context.Entry(p2).Collection(p => p.Items).Load(); var i1 = context.BuyingItems.Find(1); var i2 = context.BuyingItems.Find(2); p1.Items.Add(i1); p1.Items.Add(i2); // Uncomment to test whether this fails or not, it will work, and guess what, no duplicates!!! //p1.Items.Add(i1); //p1.Items.Add(i1); //p1.Items.Add(i1); //p1.Items.Add(i1); //p1.Items.Add(i1); p2.Items.Add(i1); p2.Items.Add(i2); // The following WON''T work, since we''re assigning a new collection, it''ll try to insert duplicate values only to fail. //p1.Items = new[] { i1, i2 }; //p2.Items = new[] { i2 }; }

Aquí nos aseguramos de que las entidades se crean / actualizan en la base de datos llamando a context.SaveChanges() dentro del método Seed . Después de eso, recuperamos la parcela requerida y compramos objetos usando el context . Luego, usaremos la propiedad Items (que es una colección) en los objetos de Parcel para agregar BuyingItem como nos plazca.

Tenga en cuenta que, no importa cuántas veces llamemos al método Add utilizando el mismo objeto de elemento, no terminamos con una violación de la clave principal. Esto se debe a que EF utiliza internamente HashSet<T> para administrar la colección Parcel.Items . Un HashSet<Item> , por su naturaleza, no te permitirá agregar elementos duplicados.

Además, si de alguna manera usted trata de eludir este comportamiento de EF, como he demostrado en el ejemplo, nuestra clave principal no permitirá que ingresen los duplicados.

Usando AddOrUpdate correctamente

Cuando use un campo Id típico (int, identidad) como una expresión de identificador con el método AddOrUpdate , debe tener cuidado.

En este caso, si elimina manualmente una de las filas de la tabla Parcel, terminará creando duplicados cada vez que ejecute el método Seed (incluso con el método Seed actualizado que he proporcionado anteriormente).

Considere el siguiente código,

context.Parcels.AddOrUpdate( p => p.Id, new Parcel { Id = 1, Description = "Parcel 1", Weight = 1.0 }, new Parcel { Id = 2, Description = "Parcel 1", Weight = 1.0 }, new Parcel { Id = 3, Description = "Parcel 1", Weight = 1.0 } );

Técnicamente (considerando el Id. De sustitución aquí), las filas son únicas, pero desde el punto de vista del usuario final, son duplicados.

La verdadera solución aquí es usar el campo Description como expresión de identificador. Agregue este atributo a la propiedad Description de la clase Parcel para hacerlo único, [MaxLength(255), Index(IsUnique=true)] . Actualice los siguientes fragmentos en el método Seed :

context.Parcels.AddOrUpdate( p => p.Description, new Parcel { Description = "Parcel 1", Weight = 1.0 }, new Parcel { Description = "Parcel 2", Weight = 2.0 }, new Parcel { Description = "Parcel 3", Weight = 3.0 } ); // Make sure that the above entities are created in the database context.SaveChanges(); var p1 = context.Parcels.Single(p => p.Description == "Parcel 1");

Tenga en cuenta que no estoy usando el campo Id , ya que EF lo ignorará al insertar filas. Y estamos usando la Description para recuperar el objeto de parcela correcto, sin importar el valor de Id .

Respuesta antigua

Me gustaría añadir algunas observaciones aquí:

  1. El uso de Id probablemente no servirá de nada si la columna Id es un campo generado por la base de datos. EF lo va a ignorar.

  2. Este método parece funcionar bien cuando el método Seed se ejecuta una vez. No creará ningún duplicado, sin embargo, si lo ejecuta por segunda vez (y la mayoría de nosotros tenemos que hacerlo a menudo), puede inyectar duplicados. En mi caso lo hizo.

Este tutorial de Tom Dykstra me mostró la forma correcta de hacerlo. Funciona porque no damos nada por sentado. No especificamos IDs. En su lugar, consultamos el contexto mediante claves únicas conocidas y agregamos entidades relacionadas (que nuevamente se adquieren al consultar el contexto). Funcionó como un encanto en mi caso.

Yo uso la migración del marco de la entidad (en modo de migración automática). Todo está bien, pero tengo una pregunta: ¿Cómo debo sembrar datos cuando tengo una relación de muchos a muchos? Por ejemplo tengo dos clases modelo:

public class Parcel { public int Id { get; set; } public string Description { get; set; } public double Weight { get; set; } public virtual ICollection<BuyingItem> Items { get; set; } } public class BuyingItem { public int Id { get; set; } public decimal Price { get; set; } public virtual ICollection<Parcel> Parcels { get; set; } }

Entiendo cómo sembrar datos simples (para la clase PaymentSystem) y relaciones de uno a muchos, pero ¿qué código debo escribir en el método Seed para generar algunas instancias de Parcel y BuyingItem? Me refiero a usar DbContext.AddOrUpdate (), porque no quiero duplicar datos cada vez que ejecuto Update-Database.

protected override void Seed(ParcelDbContext context) { context.AddOrUpdate(ps => ps.Id, new PaymentSystem {Id = 1, Name = "Visa"}, new PaymentSystem {Id = 2, Name = "PayPal"}, new PaymentSystem {Id = 3, Name = "Cash"}); }

protected override void Seed(Context context) { base.Seed(context); // This will create Parcel, BuyingItems and relations only once context.AddOrUpdate(new Parcel() { Id = 1, Description = "Test", Items = new List<BuyingItem> { new BuyingItem() { Id = 1, Price = 10M }, new BuyingItem() { Id = 2, Price = 20M } } }); context.SaveChanges(); }

Sí. Este código crea un paquete, artículos de compra y relación, pero si necesito el mismo artículo de compra en otro paquete (tienen una relación de muchos a muchos) y si repito este código para el segundo paquete, duplicará los artículos de compra en la base de datos (aunque las mismas identificaciones). Ejemplo:

protected override void Seed(Context context) { base.Seed(context); context.AddOrUpdate(new Parcel() { Id = 1, Description = "Test", Items = new List<BuyingItem> { new BuyingItem() { Id = 1, Price = 10M }, new BuyingItem() { Id = 2, Price = 20M } } }); context.AddOrUpdate(new Parcel() { Id = 2, Description = "Test2", Items = new List<BuyingItem> { new BuyingItem() { Id = 1, Price = 10M }, new BuyingItem() { Id = 2, Price = 20M } } }); context.SaveChanges(); }

¿Cómo puedo agregar los mismos artículos de compra en diferentes paquetes?


De acuerdo. Entiendo cómo debería estar en esa situación:

protected override void Seed(Context context) { base.Seed(context); var buyingItems = new[] { new BuyingItem { Id = 1, Price = 10m }, new BuyingItem { Id = 2, Price = 20m, } } context.AddOrUpdate(new Parcel() { Id = 1, Description = "Test", Items = new List<BuyingItem> { buyingItems[0], buyingItems[1] } }, new Parcel() { Id = 2, Description = "Test2", Items = new List<BuyingItem> { buyingItems[0], buyingItems[1] } }); context.SaveChanges(); }

No hay duplicados en la base de datos.

Gracias, Ladislav, me diste un vector adecuado para encontrar una solución para mi tarea.


Debe completar la relación muchos a muchos de la misma manera que construye una relación muchos a muchos en cualquier código de EF:

protected override void Seed(Context context) { base.Seed(context); // This will create Parcel, BuyingItems and relations only once context.AddOrUpdate(new Parcel() { Id = 1, Description = "Test", Items = new List<BuyingItem> { new BuyingItem() { Id = 1, Price = 10M }, new BuyingItem() { Id = 2, Price = 20M } } }); context.SaveChanges(); }

Especificar la Id que se usará en la base de datos es crucial, de lo contrario cada Update-Database creará nuevos registros.

AddOrUpdate no admite el cambio de relaciones de ninguna manera, por lo que no puede usarlo para agregar o eliminar relaciones en la próxima migración. Si lo necesita, debe eliminar la relación manualmente cargando Parcel con BuyingItems y llamando a Remove o Add en la colección de navegación para romper o agregar una nueva relación.