How to seed data with many-to-may relations in Entity Framework Migrations

Dmitry Gorshkov picture Dmitry Gorshkov · Dec 18, 2011 · Viewed 20.7k times · Source

I use entity framework migration (in Automatic migration mode). Everything is okay, but I have one question:

How should I seed data when I have many-to-many relationships?

For example, I have two model classes:

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 understand how to seed simple data (for PaymentSystem class) and one-to-many relationships, but what code should I write in the Seed method to generate some instances of Parcel and BuyingItem? I mean using DbContext.AddOrUpdate(), because I don't want to duplicate data every time I run 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 code creates Parcel, BuyingItems and their relationship, but if I need the same BuyingItem in another Parcel (they have a many-to-many relationship) and I repeat this code for the second parcel - it will duplicate BuyingItems in the database (though I set the same Ids).

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 can I add the same BuyingItem in different Parcels?

Answer

Ladislav Mrnka picture Ladislav Mrnka · Dec 18, 2011

You must fill many-to-many relation in the same way as you build many-to-many relation in any EF code:

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 which will be used in database is crucial otherwise each Update-Database will create new records.

AddOrUpdate doesn't support changing relations in any way so you cannot use it to add or remove relations in next migration. If you need it you must manually remove relation by loading Parcel with BuyingItems and calling Remove or Add on navigation collection to break or add new relation.