如何在Entity Framework中触发DbUpdateConcurrencyException?Web农场适配测试
解决EF 6中DbUpdateConcurrencyException触发失败的问题
为什么嵌套Parallel循环无法触发异常
EF的DbContext不是线程安全的,在并行循环中共享同一个上下文实例会导致状态跟踪混乱,无法正确模拟真实的并发场景。这种情况下的错误通常是线程安全问题而非预期的并发冲突异常。
正确触发DbUpdateConcurrencyException的步骤
1. 配置实体的并发令牌
首先确保你的实体已配置并发令牌(对应SQL Server的ROWVERSION类型):
public class YourEntity { public int Id { get; set; } public string Data { get; set; } [Timestamp] public byte[] RowVersion { get; set; } // 并发令牌字段 }
通过[Timestamp]特性标记该字段后,EF会在更新时自动对比数据库中的RowVersion值,不一致则抛出DbUpdateConcurrencyException。
2. 使用独立DbContext实例模拟并发
每个并发操作必须使用独立的DbContext,模拟Web农场中不同请求的独立上下文:
var targetEntityId = 1; // 待更新的实体ID // 启动两个并行任务,每个任务持有独立上下文 var task1 = Task.Run(async () => { using (var context = new YourDbContext()) { var entity = await context.YourEntities.FindAsync(targetEntityId); entity.Data = "更新自任务1"; await Task.Delay(1000); // 延迟确保另一个任务已获取初始实体 await context.SaveChangesAsync(); } }); var task2 = Task.Run(async () => { using (var context = new YourDbContext()) { var entity = await context.YourEntities.FindAsync(targetEntityId); entity.Data = "更新自任务2"; await Task.Delay(1000); try { await context.SaveChangesAsync(); } catch (DbUpdateConcurrencyException ex) { Console.WriteLine("并发冲突触发成功:" + ex.Message); // 处理逻辑示例:获取数据库最新值并决定后续操作 var entry = ex.Entries.Single(); var dbValues = await entry.GetDatabaseValuesAsync(); if (dbValues != null) { entry.OriginalValues.SetValues(dbValues); // 可选:重试保存或合并更改 // await context.SaveChangesAsync(); } } } }); await Task.WhenAll(task1, task2);
3. 关键注意事项
- 每个线程/任务用独立DbContext:这是模拟Web农场多节点并发的核心,每个请求都有独立的上下文实例。
- 延迟操作确保并发条件:通过
Task.Delay让两个任务都先获取到实体的初始版本,再执行更新,保证第二个任务保存时数据库中的RowVersion已被第一个任务修改。 - 正确处理异常:捕获
DbUpdateConcurrencyException后,可通过entry.GetDatabaseValues()获取最新数据,再选择覆盖、合并或终止操作。
替代测试方案:多进程模拟
如果单进程并行任务仍无法触发异常,可以启动多个独立的控制台应用实例,同时对同一实体执行更新操作,更贴近Web农场的真实分布式场景。
内容的提问来源于stack exchange,提问作者Thomas Ayoub
相关产品推荐
相关产品推荐

