新密钥对象发生主键冲突

时间:2012-12-14 21:55:21

标签: c# entity-framework primary-key entity-framework-5

我正在使用Entity Framework 5和代码优先方法。

我有两个彼此具有多对多关系的类,MoviePersonMovie通过PersonCharacter相关联。每个Movie都可以包含多个字符。

public class Movie {
    public int Id { get; set; }
    public string Title { get; set; }
    public int? Year { get; set; }
    public virtual ICollection<Character> Characters { get; set; } 

    public Movie() {
        Characters = new HashSet<Character>();
    }
}

public class Character {
    public string Name { get; set; }    
    public virtual Movie Movie { get; set; }    
    public virtual Person Person { get; set; }
}

public class Person {
    public int Id { get;set; }
    public string Name { get; set; }
    public virtual ICollection<Character> Characters { get; set; }

    public Person() {
        Characters = new HashSet<Character>();
    }
}

有时Movie可以有一个Person播放多个Character。在这种情况下尝试添加Movie时遇到问题...

假设Movie1有Person1播放的2个字符。保存更改时,EF将从Character1开始,并看到数据库中不存在Person1,并将Person插入dbo.Person。然后它将转到Character2但是看到Person1已经存在并且将抛出主键违规。

在我的MovieRepository我有Add(Movie entity)方法。我遍历Character的每个Movie并检查数据库中是否存在相关的Person。如果他们这样做,我将Person属性设置为现有Person。这在正常情况下有效,但如果Person根本不存在,那么我不确定如何避免PK违规,这是我的问题:)

public class MovieRepository : Repository<Movie> {
    public MovieRepository(MovieContext context) : base(context) {}

    public override void Add(Movie entity) {
        foreach (var character in entity.Characters) {
            var person = Context.People.FirstOrDefault(p => p.Id == character.Person.Id);
            if (person != null)
                character.Person = person;
        }
        Context.Movies.Add(entity);
    }
}

只是添加更多信息。我正在使用具有工作单元模式的存储库模式来保存我的存储库和上下文。因此UnitOfWork类负责SaveChanges()

尝试在MovieRepository.Add()中执行此操作可能不是完成此任务的最佳/适当方式。我期待着任何和所有的建议。

为了进一步澄清,我从Rotten Tomatoes API获取数据,json看起来像这样......

{data : [{
    Id: 12345,
    Title: 'Movie1',
    Year: 2010,
    Character: [{
        Name: 'Character1',
        Person: { Id: 1, Name: 'Person1' }
    },{
        Name: 'Character2',
        Person: { Id: 2, Name: 'Person2' }
    }]
}]}

2 个答案:

答案 0 :(得分:3)

你如何在同一个人身上发送2个字符的对象?它是否已经有一个独特的密钥集?否则它们将被实体框架视为两个独立的对象。如果您已经定义了唯一键,则可以先查看Local集合,然后再查看上下文,或者将该人员添加到上下文中。

// Only works if the unique key is already defined.
var person =
    Context.People.Local.FirstOrDefault(p => p.Id == character.Person.Id) ??
    Context.People.FirstOrDefault(p => p.Id == character.Person.Id);

if (person == null)
    Context.People.Add(person);

但是我建议可能会更改您的聚合,以便此方法根本不添加人员。你可以看待添加一个人并添加一个“电影”(作为{电影,角色}的聚合作为两个单独的操作。如果你单独管理人(这样一个人可以有零个或多个字符),这将是有意义的。 / p>

这会更有意义,因为电影不“拥有”人,这是一个多对多的关系。

例如在用户界面中:添加带有角色的电影时,您可以选择播放角色的人,或者在此时添加新角色。因此,在发送要保存的电影时,您将始终拥有一个character.PersonId,它将指向已保存的人物对象。

虽然这当然可能对您的特定情况不起作用,但只是一个想法。

答案 1 :(得分:0)

如果我正确理解了这个问题,这只会在以下情况发生:

  1. 此人先前未存在于数据库
  2. 这个人在电影中扮演多个角色?
  3. 更明确地说,如果数据库中已存在的人在电影中播放两个角色,你是否会遇到同样的失败?

    听起来你可能需要确保在第二次使用之前坚持(保存)新人?

    类似的东西:

    public class MovieRepository : Repository<Movie> {
    public MovieRepository(MovieContext context) : base(context) {}
    
    public override void Add(Movie entity) {
        foreach (var character in entity.Characters) {
            var person = Context.People.FirstOrDefault(p => p.Id == character.Person.Id);
            if (person != null) {
                character.Person = person;
            } else {
                personRepository.Save(character.Person)
            }
        }
        Context.Movies.Add(entity);
    }