使用ASP.NET Web API 2+EF6结合LocalDB时触发DbUpdateConcurrencyException
解决EF6调用SaveChanges()时的DbUpdateConcurrencyException异常
我之前在做ASP.NET Web API 2 + EF6的项目时,也碰到过LocalDB环境下SaveChanges抛出这个并发异常的情况,给你整理几个最可能的原因和解决思路:
1. 检查实体的并发令牌配置
这个异常本质是EF检测到你要操作的记录,在读取到修改的间隙里状态发生了变化——但本地开发大概率不是真的多用户并发,而是实体的并发配置出了问题。
- 如果你的实体类里不小心加了
[Timestamp]或者[ConcurrencyCheck]特性,EF会默认把这些字段当成并发令牌,每次更新都会对比字段值,不匹配就会抛异常。如果不需要并发控制,可以直接去掉这些特性; - 如果确实需要并发控制,那要确保更新时拿到最新的令牌值,或者在捕获异常后重新读取实体再尝试更新。
举个错误配置的实体示例:
public class UserData { public int Id { get; set; } public string Content { get; set; } // 误加的Timestamp特性,导致每次更新都要校验这个值 [Timestamp] public byte[] RowVersion { get; set; } }
2. 确认实体状态是否正确
Web API接收前端数据后,很容易出现EF对实体状态识别错误的情况:
- 新建实体:要确保数据库里主键是自增(Identity)配置,并且你没有手动给主键赋值——如果手动设置了主键,EF可能会把它当成已存在的实体去更新,结果数据库里找不到就触发异常;
- 更新实体:不要直接把前端传来的实体Attach到DbContext,正确的做法是先从DbContext查询出数据库中已有的实体,再把前端的属性值赋值过去,最后调用SaveChanges。
正确的更新逻辑示例:
public IHttpActionResult UpdateData(int id, UserData model) { var existingData = db.UserDatas.Find(id); if (existingData == null) { return NotFound(); } // 手动赋值或者用AutoMapper做属性映射 existingData.Content = model.Content; // 其他属性赋值... db.SaveChanges(); return Ok(existingData); }
3. 排查LocalDB的文件或连接问题
LocalDB的数据库文件如果被其他进程锁定(比如同时开着SSMS连接数据库),或者文件权限不足,也可能间接触发这个异常:
- 先关闭所有可能连接LocalDB的工具(比如SSMS、Visual Studio的服务器资源管理器),再重新测试;
- 检查LocalDB数据库文件所在文件夹的权限,确保当前用户有读写权限。
4. 异常捕获与重试逻辑(针对真实并发场景)
如果你的接口确实存在多用户并发操作的可能,可以在捕获异常后重新读取数据再尝试更新:
public bool SaveWithRetry() { const int maxRetryTimes = 3; int retryCount = 0; while (retryCount < maxRetryTimes) { try { db.SaveChanges(); return true; } catch (DbUpdateConcurrencyException ex) { retryCount++; var entry = ex.Entries.Single(); // 从数据库加载当前最新的实体数据 var dbValues = entry.GetDatabaseValues(); if (dbValues == null) { // 实体已被其他操作删除 throw new InvalidOperationException("目标数据已被删除"); } // 将实体的原始值更新为数据库最新值,准备重试 entry.OriginalValues.SetValues(dbValues); } } throw new InvalidOperationException("重试次数过多,保存失败"); }
建议你先从第2点开始排查,Web API里实体状态处理不当是触发这个异常最常见的原因。
内容的提问来源于stack exchange,提问作者mu88
相关产品推荐
相关产品推荐

