EF种子多对多相关表

时间:2018-03-13 18:56:42

标签: c# entity-framework

我有以下简化的多对多相关模型:

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

创建的数据库表如下:

dbo.Singers
Id
Name

dbo.Songs
Id
Title

dbo.SingerSongs
Singer_Id
Song_Id

我使用以下种子代码将项添加到已填充的表中,同时防止重复:

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

最近,我更新了种子代码,将“新歌手”链接到“现有歌曲”和“新歌”,如下所示:

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

这不起作用(没有添加关系)可能是因为之前已经添加了“New Singer”。如果我手动删除“新歌手”,歌手会在播种时加上关系。但是,我不想删除项目,因此我可以添加关系。我如何使这个种子工作?

更新:播种前已经存在“新歌”时出现重复“新歌”的问题已通过更新的代码修复。

3 个答案:

答案 0 :(得分:2)

为了提供更多背景信息,AddOrUpdate远远低于其名称所暗示的内容。

  1. 该方法查明实体是否存在。如果没有,则将其标记为Added;如果是,则将其标记为Modified。但是,标记为Added时,标记为Added的嵌套实体,标记为Modified仅将实体本身标记为Modified - 并且仅标记其标量属性,即不标记关联。

  2. 它有一个恼人的bug:如果找到现有实例,它会停止跟踪代码中可见的实例,并开始跟踪内部(不可见)实例。 代码中对实例的任何后续更改都将被忽略

  3. 简而言之:它适用于添加或更新隔离的实体,而不适用于相关实体。我认为不可避免的结论是:不要使用AddOrUpdate来播种相关实体

    (就个人而言,我会更进一步,不要使用它,期间)。

答案 1 :(得分:1)

您还需要将歌手添加到歌曲中。两者都列出了Singer.Songs&amp; Song.Singers需要由您的代码维护。

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

答案 2 :(得分:1)

对于重复问题:

newSong未被实体框架跟踪,因此不与数据库中的任何现有记录相关联。当它被添加到newSinger时,它被视为新记录并分配了新的id(主键)。因此,你似乎得到了newSong的重复。

要修复,您需要跟踪newSong,以便EF将其与其基础记录相关联。在这种情况下,您可以使用:

context.Attach(newSong)

通常,将跟踪由查询检索的实体。 e.g:

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

关于关系问题:

Thisthis表示AddOrUpdate不会更新关系。一种解决方案是单独更新关系,而另一种解决方案是明确说明所有添加的实体的主键值。我怀疑后者也会删除重复的问题。

您可以尝试以下方式:

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