EF Core更新VergaderingAgendaItem时触发重复主键错误问题
在Blazor Server项目中定义了Vergadering与VergaderingAgendaItem两个模型,EF Core自动生成了Vergaderingen、VergaderingAgendaItems、VergaderingVergadering三张数据库表(其中VergaderingVergadering是Vergadering自身多对多关联的中间表)。
仓储中实现了更新VergaderingAgendaItem的方法如下:
public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item) { using (var _db = _factory.CreateDbContext()) { _db.VergaderingAgendaItems.Update(item); await _db.SaveChangesAsync(); } }
现象:当Vergadering没有GekoppeldeVergadering(关联会议)时,该方法运行正常;但当Vergadering存在关联会议时,更新其对应的VergaderingAgendaItem会触发错误:
违反PRIMARY KEY约束'PK_VergaderingVergadering',无法插入重复键值(4,3)。
疑问:明明只调用了VergaderingAgendaItem.Update(),为什么EF Core会尝试操作VergaderingVergadering表中的关联数据?
原因
EF Core的Update方法会递归处理传入实体的所有关联实体,并根据主键值将它们的状态标记为Modified(主键有值)或Added(主键无值)。
你的场景中,传入的VergaderingAgendaItem实例大概率附带了关联的Vergadering实体,而该Vergadering又包含了已存在的GekoppeldeVergadering关联关系。由于当前DbContext并未跟踪这些关联的Vergadering实体,EF Core会误判这些关联为新数据,进而尝试向VergaderingVergadering中间表插入重复的主键记录,最终触发约束错误。
解决办法
有几种可行的方案:
仅更新需要修改的字段
不要直接传入完整实体执行Update,先从DbContext中查询目标实体,再修改指定字段:public async Task ChangeAfgerondStatusAsync(int agendaItemId, bool newAfgerondStatus) { using (var _db = _factory.CreateDbContext()) { var item = await _db.VergaderingAgendaItems.FindAsync(agendaItemId); if (item != null) { item.Afgerond = newAfgerondStatus; await _db.SaveChangesAsync(); } } }这种方式仅修改目标字段,不会触发关联实体的不必要操作。
分离关联实体
如果必须传入完整实体,先将其关联的Vergadering及嵌套关联关系与当前实体分离,再执行Update:public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item) { using (var _db = _factory.CreateDbContext()) { // 移除关联的Vergadering,避免EF Core处理它的关联关系 item.Vergadering = null; _db.VergaderingAgendaItems.Update(item); await _db.SaveChangesAsync(); } }手动设置实体状态
使用Attach方法将实体附加到DbContext,然后仅标记需要修改的属性为Modified:public async Task ChangeAfgerondStatusAsync(VergaderingAgendaItem item) { using (var _db = _factory.CreateDbContext()) { _db.VergaderingAgendaItems.Attach(item); // 只标记Afgerond字段为修改状态 _db.Entry(item).Property(x => x.Afgerond).IsModified = true; await _db.SaveChangesAsync(); } }这种方式精准控制要更新的字段,同时忽略关联实体的状态变化。
内容的提问来源于stack exchange,提问作者Jeremy

