我有以下简化的多对多相关模型:
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”。如果我手动删除“新歌手”,歌手会在播种时加上关系。但是,我不想删除项目,因此我可以添加关系。我如何使这个种子工作?
更新:播种前已经存在“新歌”时出现重复“新歌”的问题已通过更新的代码修复。
答案 0 :(得分:2)
为了提供更多背景信息,AddOrUpdate
远远低于其名称所暗示的内容。
该方法查明实体是否存在。如果没有,则将其标记为Added
;如果是,则将其标记为Modified
。但是,标记为Added
时,标记为Added
的嵌套实体,标记为Modified
仅将实体本身标记为Modified
- 并且仅标记其标量属性,即不标记关联。
它有一个恼人的bug:如果找到现有实例,它会停止跟踪代码中可见的实例,并开始跟踪内部(不可见)实例。 代码中对实例的任何后续更改都将被忽略。
简而言之:它适用于添加或更新隔离的实体,而不适用于相关实体。我认为不可避免的结论是:不要使用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");
关于关系问题:
This和this表示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();