实体框架具有相同密钥的两个不同对象不起作用

时间:2014-10-22 22:54:58

标签: c# entity-framework

我试图在我的主对象中插入对象引用,但如果我不使用以前的托管对象,EntityFramework会抱怨。我只是想在创建对象时避免依赖dbContext。

简化示例:

class Movie {
    public ApplicationUser Owner { get;set; }
}

var myMovie = db.Movies.FirstOrDefault(m, m => m.Id = 1);
myMovie.Owner = new ApplicationUser { Id = 2 };

// I have to attach or change its state, otherwise, EF will complain the object is not complete
db.Entry(myMovie.Owner).State = EntityState.Unchanged;

不知何故,如果上次加载了相同的ApplicationUser,我会收到此错误:

  

保存或接受更改失败,因为“ApplicationUser”类型的多个实体具有相同的主键值。确保显式设置的主键值是唯一的。确保在数据库和Entity Framework模型中正确配置了数据库生成的主键。使用实体设计器进行数据库优先/模型优先配置。使用'HasDatabaseGeneratedOption'流畅API或'DatabaseGeneratedAttribute'进行代码优先配置。

我该如何避免这个问题?最理想的是,我不想告诉那个新对象的状态。

2 个答案:

答案 0 :(得分:6)

如果您有一个只读取数据而不修改数据的实例,则可以使用AsNoTracking()这将阻止您的上下文知道的模型的附加实例(它基本上是只读的)。 / p>

以下代码应该可以工作,即使它已经两次检索同一个对象。

var myMovieReadOnly = db.Movies.AsNoTracking().FirstOrDefault(m, m => m.Id = 1);

var myMovie = db.Movies.FirstOrDefault(m, m => m.Id = 1);
myMovie.Owner = new ApplicationUser { Id = 2 };

db.SaveChanges();

另一个注意事项是AsNoTraking()还可以在您只读取数据的情况下节省性能。


编辑:重新阅读并意识到它是ApplicationUser模型而不是电影,但同样的概念应该适用于第一个实例的撤销。


Edit2:

根据我们的评论,您还可以执行以下操作,如果您已经知道ID,则根本不需要进行查找:

class Movie {
    public int OwnerId { get;set; }
    public ApplicationUser Owner { get;set; }
}

var myMovie = db.Movies.FirstOrDefault(m, m => m.Id = 1);
myMovie.OwnerId = 2;
db.SaveChanges();

答案 1 :(得分:2)

  

如果上次加载了相同的ApplicationUser,我收到此错误

首先检查ApplicationUser是否已加载。

var newId = 2;
var newApplicationUser = db.ApplicationUsers.Local.FirstOrdefault(u => u.Id == newId)
                             ?? new ApplicationUser { Id = newId };
myMovie.Owner = newApplicationUser;