简体   繁体   中英

EF Seed Many-to-Many Related Tables

I have the following simplified many-to-many related models:

public class Singer
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public virtual ICollection<Song> Songs { get; set; }
}

public class Song
{
    [Key]
    public int Id { get; set; }

    public string Title { get; set; }

    public virtual ICollection<Singer> Singers { get; set; }
}

The DB tables created are below:

dbo.Singers
Id
Name

dbo.Songs
Id
Title

dbo.SingerSongs
Singer_Id
Song_Id

I used the following seed code to add items to already populated tables while preventing duplicates:

public static void SeedNewSingerAndSong(AppContext context)
{
    // Create new song
    Song newSong = new Song() { Title = "New Song" };

    context.Songs.AddOrUpdate(
        item => item.Title,
        newSong
    );

    context.SaveChanges();

    // Create new Singer
    Singer newSinger = new Singer() { Name = "New Singer" };

    context.Singers.AddOrUpdate(
        item => item.Name,
        newSinger
    );

    context.SaveChanges();
}

Recently, I updated the seed code to link "New Singer" to "Existing Song" and "New Song" as follows:

public static void SeedNewSingerAndSong(AppContext context)
{
    // Create new song
    Song newSong = new Song() { Title = "New Song" };

    context.Songs.AddOrUpdate(
        item => item.Title,
        newSong
    );

    context.SaveChanges();

    // Find existing songs
    Song foundExistingSong = context.Songs.Single(x => x.Title == "Existing Song");
    Song foundNewSong = context.Songs.Single(x => x.Title == "New Song");

    // Create new Singer
    Singer newSinger = new Singer() { Name = "New Singer" };

    // Assign songs to new Singer
    newSinger.Songs.Add(foundExistingSong);
    newSinger.Songs.Add(foundNewSong);

    context.Singers.AddOrUpdate(
        item => item.Name,
        newSinger
    );

    context.SaveChanges();
}

This doesn't work (no relationship gets added) probably because "New Singer" has already been added previously. If I manually delete "New Singer", the singer gets added together with the relationships when seeding. However, I don't want to delete items just so I could add relationships. How do I make this seed work?

Update: The problem with duplicate "New Song" when "New Song" already exists prior to seeding has been fixed with the updated code.

To give some more background information, AddOrUpdate does far less than its name suggests.

  1. The method finds out if an entity exists. If it doesn't, it marks it as Added ; if it does, it marks it as Modified . However, whereas marking as Added also marks nested entities as Added , marking as Modified only marks the entity itself as Modified -- and only its scalar properties, ie not its associations.

  2. It has an annoying bug : if it finds an existing instance it stops tracking the instance that's visible in your code and starts tracking an internal (invisible) instance. Any subsequent changes to the instance in your code will be ignored .

In short: it's good for adding or updating isolated entities, not for related entities. I think the inevitable conclusion is: don't use AddOrUpdate to seed related entities .

(Personally, I would take this a step further and not use it, period).

You need to add the Singer to the Songs as well. Both lists Singer.Songs & Song.Singers need to be maintained by your code.

public static void SeedNewSingerAndSong(AppContext context)
{
    ...

    // Assign songs to new Singer
    newSinger.Songs.Add(foundExistingSong);
    newSinger.Songs.Add(foundNewSong);

    // Assign Singer to Songs
    foundExistingSong.Singers.Add(newSinger);
    foundfoundNewSong.Singers.Add(newSinger);

    // The following duplicates "New Song" as explained in *Additional Info
    context.Singers.AddOrUpdate(
        item => item.Name,
        newSinger
    );

    context.SaveChanges();
}

For the duplicate issue:

newSong is not being tracked by Entity Framework and therefore isn't associated with any existing record in the database. When it is added to newSinger it is viewed as a new record and assigned a new id (primary key). Hence you appear to get duplicates of newSong.

To fix, you need to make newSong tracked so that EF associates it with its underlying record. In this case you can use:

context.Attach(newSong)

In general an entity retrieved by a query will be tracked. eg:

Song newSong = context.Songs.Single(x => x.Title == "New Song");

For the relationships issue:

This and this indicate that AddOrUpdate doesn't update relationships. One solution is to update the relationships separately while the other is to explicitly state the primary key values for all the added entities. I suspect that the latter would also remove the duplicate issue.

You could try something like:

var newSinger = context.Singers.Include("Songs").Single(x => x.Name == "New Singer");
newSinger.Songs.Clear();
newSinger.Songs = new List<Songs>{ foundNewSong, foundExistingSong };
context.SaveChanges();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM