主键约束更新 many-to-many 自引用 table in entity framework code-first

primary key constraint updating many-to-many self referential table in entity framework code-first

TL;DR 用 DTO 中的自引用多对多关系重新水合 entity framework object 并更新它的正确方法是什么使用新值以便数据库正确更新?

我有以下实体(无关内容已删减)

 public class Role
    {
        [Key]
        [Required]
        public String RoleId { get; set; }
        public List<Role> Children { get; set; }
    }

在我的dbContext中,我设置了多对多关系

     modelBuilder.Entity<Role>().HasMany(r => r.Children).WithMany();

我正在使用 MVC 前端,web-api 后端用于 n-tier 设置,以及一个 mssql 数据库。

发生以下事件链

浏览器->MVC 控制器->REST 调用 Web API->WebAPI 控制器->DB 上下文查询

这个链条发生了两次,一次是在编辑模式下查看页面,另一次是当用户按下保存按钮以保存时。

在实体上设置children时,它们总是先存在(IE,你不会同时创建parent和children,你只是将现有的 child 添加到 parent)

有一个 MVC 模型和网络使用的 DTO API,我 re-hydrate 到 web-api 端的实体。

  public IHttpActionResult UpdateRoleInfo(RoleVM roleInfo){
     //lookup existing entity to update
     var existing = db.Roles.FirstOrDefault(y => y.RoleId == roleInfo.ExistingRoleId);

     ...Something happens here (see below for things i've tried)...

    db.SaveChanges();
}

我的第一次尝试是这样的:

 existing.Children = roleInfo.Children

这试图重新创建所有现有的 children 作为保存的一部分。 (角色违反主键约束 table)

我把它改成了

  //Fetch all of the roles from the database to lookup the existing children
  var allRoles = GetRoles();
  //Have to reselect the roles from the DB so the DB doesn't try to recreate new ones for the children.
  var childrenToAdd = roleInfo.Roles.Select(role2 => allRoles.FirstOrDefault(r => r.RoleId == role2.RoleId)).ToList();
  existing.Children = childrenToAdd;

这可以正确地更新一个没有任何 children 的角色,第一次添加一些,但是如果你更新一个已经有 children 的角色,它会尝试re-add child 第二次进入数据库,在 roles_role table

上出现主键冲突

然后我尝试pre-pending这个代码到上面的第二个,

                existing.Children.Clear();
                db.SaveChanges();

我希望这会删除此 parent 的多对多 table 中所有现有的 parent-child 关系,然后使用新的 child 重新创建它们仁。为什么不呢?

TL;DR 用 DTO 中的自引用多对多关系重新水合 entity framework object 并更新它的正确方法是什么使用新值以便数据库正确更新?

尝试通过

关闭自动检测更改(在从数据库中检索之前)
context.Configuration.AutoDetectChangesEnabled = false;

然后在您正在更新的特定角色对象上将状态设置为已修改

context.Entry(role).State = EntityState.Modified;

我自己还没有在自引用多对多 table 上尝试过,但是以这种方式添加和更新实体可以避免 EF 错误地推断你是什么的各种麻烦 adding/updating

找到问题了。

在实体的初始加载中,我使用 include 语句预先加载子项。

当我更新实体时,当我再次从数据库中获取它时,我没有急于加载子项。因此 additions/updates 感到困惑。一旦我在上传过程中将 include 放入上面的场景 #2 中(不需要显式清除)

db.Roles.Include("Children").FirstOrDefault(z => z.RoleId == RoleId);

此外,如果您在处理不同表之间的关系时遇到同样的问题,请确保图中涉及的所有实体都来自相同的数据库上下文!

https://msdn.microsoft.com/en-us/magazine/dn166926.aspx