EF Core+MySQL更新实体时偶发DbUpdateConcurrencyException异常求助
环境与表结构
- 技术栈:EF Core + MySQL数据库
- 表结构定义:
- ParentEntity:Id为自增主键
- ChildEntity:CId为自增主键,外键关联ParentEntity的Id
- SubChildEntity:SId为自增主键,外键关联ChildEntity的CId
异常现象
更新实体时偶发Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException异常,部分场景下更新能正常成功。
报错详情
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 数据库操作预期影响1行,但实际影响0行;自实体加载以来数据可能已被修改或删除。
调用栈信息:
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ThrowAggregateUpdateConcurrencyExceptionAsync(RelationalDataReader reader, Int32 commandIndex, Int32 expectedRowsAffected, Int32 rowsAffected, CancellationToken cancellationToken)
at Microsoft.EntityFrameworkCore.Update.AffectedCountModificationCommandBatch.ConsumeResultSetWithRowsAffectedOnlyAsync(Int32 commandIndex, RelationalDataReader reader, CancellationToken cancellationToken)
相关代码
var Entity = await _context.Parent .Include(x => x.Child) .ThenInclude(x => x.SubChild) .FirstOrDefaultAsync(x => x.Id == Id, cancellationToken); // 更新Parent、Child及SubChild实体的部分属性 _context.Parent.Update(Entity); var result = await _context.SaveChangesAsync(cancellationToken);
原因分析与解决方案
1. 并发修改导致数据冲突
加载实体后,若其他线程/请求修改了数据库中对应记录,EF Core执行更新时会因行数不匹配抛出异常。
- 解决方法:
- 启用乐观并发控制:给实体添加
RowVersion类型属性(MySQL可使用BINARY(8)或TIMESTAMP),EF Core会自动用该字段检测并发冲突。 - 捕获异常后重试更新:
try { var result = await _context.SaveChangesAsync(cancellationToken); } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { var dbValues = await entry.GetDatabaseValuesAsync(cancellationToken); if (dbValues == null) { // 处理实体已被删除的逻辑 continue; } // 合并最新数据与本地修改,或提示用户确认 entry.CurrentValues.SetValues(dbValues); } // 再次尝试保存 await _context.SaveChangesAsync(cancellationToken); }
- 启用乐观并发控制:给实体添加
2. Update方法误用触发无效更新
_context.Parent.Update(Entity)会将整个实体及关联的Child、SubChild标记为Modified,即使属性未变更。若关联实体已被数据库删除,更新时会因影响0行触发异常。
- 解决方法:
- 仅更新实际修改的属性,无需调用
Update方法(EF Core会自动跟踪属性变化):var Entity = await _context.Parent .Include(x => x.Child) .ThenInclude(x => x.SubChild) .FirstOrDefaultAsync(x => x.Id == Id, cancellationToken); // 直接修改属性 Entity.Title = "新标题"; Entity.Child.Content = "更新内容"; // 直接保存,EF Core自动提交变更 var result = await _context.SaveChangesAsync(cancellationToken); - 更新前检查关联实体是否存在,避免对已删除的子实体执行更新操作。
- 仅更新实际修改的属性,无需调用
3. MySQL主键/外键同步问题
高并发场景下,MySQL自增主键生成、外键约束延迟检查可能导致偶发的行影响数不匹配。
- 解决方法:
- 确保实体主键配置正确,使用
[Key]和[DatabaseGenerated(DatabaseGeneratedOption.Identity)]标记自增主键。 - 确认MySQL事务隔离级别为默认的
READ COMMITTED,避免脏读引发的冲突。
- 确保实体主键配置正确,使用
内容的提问来源于stack exchange,提问作者Anonymous

