Entity Framework 6 插入重复值

10

我有以下两个实体:

public class Artist
{
    [Key]
    public string ArtistId { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Genre> Genres { get; set; }
}

public class Genre
{
    [Key]
    public int GenreId { get; set; }
    public string Name { get; set; }

    public virtual ICollection<Artist> Artist { get; set; }
}

在我的程序中,我创建了一些艺术家并希望将它们保存:
using (var context = new ArtistContext())
{
    var artists = _fullArtists.Select(x => x.Artist);

    foreach (var artist in artists)
    {
        context.Artists.AddOrUpdate(artist);
    }

    context.SaveChanges();
}

Entity Framework正确地创建了三个表:

艺术家(ArtistId,Name)
流派(GenreId,Name)
艺术家流派(ArtistId,GenreId)

但不幸的是,当我的艺术家样本数据看起来像这样时:

var a1 = new Artist { Name = "a1" };
a1.Genres.Add(new Genre { Name="rock"});
var a2 = new Artist { Name = "a2" };
a2.Genres.Add(new Genre { Name="rock"});

这将在表Genre中创建2条记录:

IdName
rock   
rock   

而不是创建一次并重复使用。

  • 您知道这是否是配置问题,或者如何告诉EF不要插入重复项,而是重复使用现有项吗?

提前感谢


编辑:不幸的是,Sergey Berezovskiy的解决方案没有起作用(或者可能是我做错了:D)

现在我拥有以下内容:

using (var workUnit = WorkUnitFactory.CreateWorkUnit())
{
    var snapShot = new Snapshot { Date = DateTime.Now.Date };

     //ICollection<Genre> genres = _fullArtists.Select(x => x.ToArtist(snapShot)).SelectMany(x => x.Genres).Distinct(new GenreComparer()).ToList();
     //workUnit.Repository<IGenreRepository>().InsertEntities(genres);
     //workUnit.Commit();

     var artists = _fullArtists.Select(x => x.ToArtist(snapShot)).ToList();

     workUnit.Repository<IArtistRepository>().InsertEntities(artists);
     workUnit.Commit();
}

ArtistExtensions.cs

    public static class ArtistExtensions
    {
        public static Artist ToArtist(this FullArtistWrapper value, Snapshot snapShot)
        {
            if (value == null) { throw new ArgumentNullException(); }

            var artist = new Artist
            {
                ArtistId = value.Id,
                Name = value.Name
            };

            var genres = value.Genres.Select(x => x.ToGenre()).ToList();
            artist.Genres.AddRange(genres);

            return artist;
        }
    }

GenreExtensions.cs

public static class GenreExtensions
    {
        public static Genre ToGenre(this string value)
        {
            using (var workUnit = WorkUnitFactory.CreateWorkUnit())
            {
                return workUnit.Repository<IGenreRepository>().GetGenres().FirstOrDefault(x => x.Name == value) ??
                            new Genre {Name = value};
            }
        }
    }

很遗憾,EF在数据库中仍会插入重复的GenresInsertEntities(...)是这样的:
public void InsertEntities<TPersistentEntity>(ICollection<TPersistentEntity> persistentEntitiesToBeInserted) where TPersistentEntity : PersistenceEntity
    {
        persistentEntitiesToBeInserted.ForEach(this.Add);
    }

public void Add(PersistenceEntity entity)
    {
        var dbSet = this.Context.Set(entity.GetType());
        dbSet.Add(entity);
    }
  • 我是否误解了Sergey的答案,或者EF仍然插入重复数据的另一个原因是什么?

再次感谢


“Genres”是一组包含“Genre”对象的集合。您如何将字符串“rock”添加到该集合中? - Sergey Berezovskiy
抱歉..当然是a1.Genres.Add(new Genre { Name = "rock" }); :-) - xeraphim
3个回答

9
从 EF 的角度来看,如果两个实体指向数据库中的同一行,则它们是相同的。即,两个实体应该具有相同的非零键。
如果您想只有一个名称为“rock”的 Genre 实体,则应将完全相同的 genre 实体添加到第二个艺术家的 genres 集合中,或者您可以有两个实体,但它们应该具有相同的非零 id。我假设您有一些 Add 扩展方法,它会创建新的 genre 并将其添加到艺术家的 genres 中:
public static void Add(this ICollection<Genre> genres, string name)
{
    genres.Add(new Genre { Name = name });
}

每次调用此方法都将创建独立的genre实例。因此,创建的实体的id将等于零,EF会将它们视为不同的实体。例如:
 a1.Genres.Add(new Genre { Name = "rock" });
 a1.Genres.Add(new Genre { Name = "rock" });

在保存更改时,EF将在流派集合中找到两个对象。EF将检查实体ID并生成相应的SQL查询。如果ID为零,则会生成INSERT查询。对于非零ID,EF将生成UPDATE查询。在这种情况下,您将有两个插入(稍微简化-见下面的注释)。如何解决这个问题?您可以为所有艺术家使用完全相同的流派实体:

var rock = new Genre { Name = "rock" };
var a1 = new Artist { Name = "a1" };
a1.Genres.Add(rock);
var a2 = new Artist { Name = "a2" };
a2.Genres.Add(rock);

如果您不想在数据库中插入新的“rock”行,则可以使用现有的行而不是创建新行:
var rock = db.Genres.FirstOrDefault(g => g.Name == "rock") ?? new Genre { Name = "rock" };

1
非常感谢您的非常易懂的回答! :-) - xeraphim
@xeraphim 顺便说一下,我已经简化了查询生成的过程 - EF 实际上跟踪实体的状态。新实体将具有“Detached”状态。在将实体添加到某个 dbset 后,它的状态将更改为“Added”。当 EF 生成查询时,实体的状态是它实际上正在查看的内容。即使实体的 ID 为零,您也可以手动更改状态并将其设置为“Modified”。EF 将生成 UPDATE 查询。但是,当您尝试保存更改时,它将失败并出现并发异常 :) - Sergey Berezovskiy
我尝试了实现您的答案,但不幸的是它并没有像我希望的那样工作 :-( 我已经在原帖中添加了一个编辑.. 您能否再次查看并看看我是否误解了您的答案或做错了什么?非常感谢 :-) - xeraphim
@xeraphim 请不要在问题中编辑后续问题 - 最好是创建新的小问题。但这一次我会帮助你。问题出在 workUnit.Commit() 上,它是在你处理所有艺术家之后调用的。因此,如果您在数据库中没有某些流派,您将创建 N 次新的 Genre 实体,并为每个艺术家分配新对象。当您创建新的流派时,您需要保存更改。EF 将为该实体分配 ID,您将能够将相同的流派实体添加到每个艺术家中。 - Sergey Berezovskiy
1
谢谢,我回家后会试试这个 :-) 非常感谢,Sergey! - xeraphim
显示剩余2条评论

1
在你链接艺术家到类型的类中,然后在代码中使用名称字段添加了2个类型。
如果你这样做了,你就会留在你的艺术家中并添加2个。
a1.Add("rock");
a1.Add("rock");

1
我认为有两个可能的原因导致它不起作用:
  1. 你只提交了一次,在代码块的末尾。因此,EF将所有类型都添加为新类型。
  2. 你的基类 PersistenceEntity 可能没有包括 Id 属性,这是一个 Key。而你的 Add 方法接受 PersistenceEntity 主类。这可能会影响所有对象作为新对象。

没错,PersistenceEntity 只是一个空的抽象类。我回家后会尝试添加 Id 属性并将其定义为键,谢谢! - xeraphim
Id属性作为主实体类的键添加,提供了筛选实体的机制。你的主要目标应该是传递参数T,例如Add(T entity),而不是Add(PersistenceEntity entity) - Engineert

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接