EF多对多含自有实体保存抛出DbUpdateConcurrencyException
异常的核心原因是你为映射到独立数据表的从属实体(Owned Entity)DataPayload 额外定义了影子状态的独立主键Id,但没有为这个主键配置值生成规则。EF Core 插入从属实体时无法为这个没有公开属性的影子主键生成合法的Guid值,最终向PayloadDatas表插入数据时因为主键为空/非法导致操作失败,返回0行受影响,触发并发异常。
另外这个独立Id主键的配置本身属于冗余设计:GroupUser和DataPayload是一对一的从属关系,指向所有者的外键GroupUserId本身具备全局唯一性,完全可以作为PayloadDatas表的主键,不需要额外新增Id列。
二选一即可,优先推荐方案1,符合EF Core的从属实体设计惯例。
方案1:移除冗余Id配置(推荐)
直接删除DataPayload映射中多余的影子Id属性定义和对应主键配置,使用外键GroupUserId作为PayloadDatas表的主键即可,修改后的配置代码如下:
builder.OwnsOne(gu => gu.Data, navBuilder => { navBuilder.ToTable("PayloadDatas"); // 以外键作为从属表主键,符合一对一从属关系的设计 navBuilder.HasKey(d => d.GroupUserId); navBuilder.WithOwner().HasForeignKey(d => d.GroupUserId); });
修改后EF Core会自动跟踪实体插入顺序:先插入GroupUser记录,拿到Id后自动给关联的DataPayload的GroupUserId属性赋值,再插入PayloadDatas表记录,完全不会出现顺序识别错误的问题。
方案2:保留独立Id列,补充值生成配置
如果因为业务兼容等原因必须保留PayloadDatas表的独立Id主键,只需要为这个影子Id属性配置「新增时自动生成值」的规则即可,修改后的配置代码如下:
builder.OwnsOne(gu => gu.Data, navBuilder => { navBuilder.ToTable("PayloadDatas"); navBuilder.Property<Guid>("Id") .ValueGeneratedOnAdd(); // 配置插入时自动生成Guid值 navBuilder.HasKey("Id"); navBuilder.WithOwner().HasForeignKey(d => d.GroupUserId); });
配置后EF Core在插入DataPayload时会自动为影子Id属性生成合法的新Guid值,不会出现空主键插入失败的问题。
你在GroupUser构造函数中手动通过Guid.NewGuid()生成Id赋值给主键的写法没有问题,EF Core可以正常识别客户端生成的Guid主键值,你之前猜测的「EF无法识别两表插入顺序」的假设不成立,异常和这段逻辑无关。
内容的提问来源于stack exchange,提问作者jeancallisti

