In Entity Framework Migrations, how do you seed data with many-to-many relationships?

ef-code-first ef-migrations entity-framework

Question

Entity Framework Migration is what I use (in Automatic migration mode). Everything is OK, however I do have a query.

When there are many-to-many connections, how should I seed the data?

I have two model classes, for instance:

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; }
}

I know how to seed one-to-many connections and basic data (for the PaymentSystem class), but what code should I put in theSeed how to create several examples ofParcel and BuyingItem ? I mean usingDbContext.AddOrUpdate() , since I don't want to run with duplicate info every time.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();
}

This program producesParcel , BuyingItems and their connection, but if I need the same thingBuyingItem the otherParcel Since there is a many-to-many link between them, I will use the same code for both parcels, duplicating the first.BuyingItems although I put the same value in the databaseId s).

Example:

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

How do I add that?BuyingItem in contrastParcel s?

1
18
9/23/2019 11:35:53 PM

Accepted Answer

Similar to how you establish many-to-many relations in any EF code, you must fill many-to-many relations as well:

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

Specifying Id This will be utilized in the database, without which eachUpdate-Database new records be made.

AddOrUpdate cannot be used to create or delete relations in the next migration since it does not support modifying relations in any manner. You must manually delete the connection by loading it if necessary.Parcel with BuyingItems and phoningRemove or Add to break or establish a new relation on the navigation collection.

20
12/18/2011 11:47:57 AM

Popular Answer

Updated Response

Make sure you read "Using AddOrUpdate Properly" section below for a complete answer.

To prevent duplication, let's first establish a composite primary key made up of the parcel id and item id. Include the next technique in theDbContext class:

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

then carry out theSeed a process like this:

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 };
}

By calling this, we ensure that the entities are added to or modified in the database.context.SaveChanges() within theSeed method. Following that, we use to get the necessary package and purchase item objectscontext . therefore, we use theItems a cluster of properties on theParcel items should includeBuyingItem as we see fit.

Please be aware that regardless of how many times we contact theAdd Using the same item object as the method, we avoid a primary key violation. This is due to the fact that EF internallyHashSet<T> to control theParcel.Items the gathering.HashSet<Item> By nature, will prevent you from adding duplicate things.

Additionally, if you are able to go around this EF behavior as I have shown in the example, our main key will prevent the entry of duplicates.

Using AddOrUpdate Properly

When you employ an identifier expression with a normal Id field (int, identity),AddOrUpdate manner, you need to be cautious.

In this case, even with the new Seed technique, if you manually remove one of the rows from the Parcel table, you'll wind up making duplicates every time you run it.Seed the approach I described above).

Take a look at the following code,

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 }
);

Although the rows are technically distinct (given the surrogate Id in this case), they are duplicates in the eyes of the end user.

The real answer here is to make advantage of theDescription as an identifying expression for the field. Include this quality in theDescription possession of theParcel using class to set it apart:[MaxLength(255), Index(IsUnique=true)] . Update the next passages in theSeed method:

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");

Nota bene, no utilizzo ilId field since EF won't take into account while adding rows. And we're employingDescription regardless of what, to extract the appropriate parcel objectId worth is.


Old Response

Here are a few observations I would like to make:

  1. If the Id column is a database-generated field, using Id is generally not going to help. It will be ignored by EF.

  2. This approach seems to be effective when theSeed a single execution of the method is performed. It won't produce any duplicates, but if you run it again (which most of us must do often), it could inject duplicates. For me, it did.

Tom Dykstra's This instruction gave me the proper answer. We don't take anything for granted, which is why it works. IDs are not specified. Instead, we add related entities (which are once again obtained via querying context) to them and query the context using known unique keys. In my situation, it was a complete success.



Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow