【问题标题】:Two different objects with same key for entity framework does not work实体框架具有相同键的两个不同对象不起作用
【发布时间】:2023-03-14 02:02:01
【问题描述】:

我试图在我的主对象中插入对象引用,但如果我不使用它以前管理的对象,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”类型的实体具有相同的主键值。确保显式设置的主键值是唯一的。确保在数据库和实体框架模型中正确配置了数据库生成的主键。使用实体设计器进行数据库优先/模型优先配置。使用“HasDatabaseGeneratedOption”流式 API 或“DatabaseGeneratedAttribute”进行 Code First 配置。

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

【问题讨论】:

  • 我遇到了同样的异常

标签: c# entity-framework


【解决方案1】:

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

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

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 模型而不是电影,但相同的概念应该适用于第一个实例的检索。


编辑2:

如果您已经知道 ID,您还可以从我们的 cmets 执行以下操作,以防止需要进行查找:

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

【讨论】:

  • 我在很多地方都读过,但我正在编写一个 NuGet 包以供一般消费。我不能假设人们会运行 AsNoTracking。此外,我的代码可能会创建两个 Id = 2 的 ApplicationUser
  • Id 不是标识列吗?您也可以在不使用 Id 列的情况下设置关系,并让标识列完成它的工作。
  • Id 是标识列。如果您正在谈论拥有类似 myMovies.OwnerId 的内容,我没有。在 CodeFirst 中使用它并不常见。基本上,从 POST 我得到了 Id,我只想在正确的属性(这里是 Owner)中创建一个临时 ApplicationUser
  • 我的意思是如果你这样做:myMovie.Owner = new ApplicationUser();调用保存更改后,数据库将创建 ID 并处理关系。 Id 应该默认为 0,它告诉 EF 需要创建和使用身份。
  • 我在底部添加了一个新的解决方案,它确实需要将 Id 添加到模型中。我发现始终有一个可用于此类场景的 id 非常有用,并且它们也是在使用 EF Power Tools“逆向工程师代码优先”时生成的
【解决方案2】:

如果上下文先前已加载相同的 ApplicationUser,我会收到此错误

所以首先检查ApplicationUser是否被加载。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-15
    相关资源
    最近更新 更多