主键约束更新 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);
此外,如果您在处理不同表之间的关系时遇到同样的问题,请确保图中涉及的所有实体都来自相同的数据库上下文!
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);
此外,如果您在处理不同表之间的关系时遇到同样的问题,请确保图中涉及的所有实体都来自相同的数据库上下文!