EF6中通过终止操作处理DbUpdateConcurrencyException及事务疑问
EF6乐观并发与事务处理解答
核心结论
- 如果是同一个DbContext下的多CRUD操作:仅用
SaveChanges()就足够,EF会自动将这些操作包装在隐式事务中,冲突时本次SaveChanges()内的所有操作自动回滚。 - 如果是跨多个DbContext的操作(比如你代码里的
context1和context2):必须使用显式事务(BeginTransaction或TransactionScope),否则已成功提交的操作无法回滚,达不到"终止所有操作"的需求。
关于DbUpdateConcurrencyException的回滚逻辑
- 当单次
SaveChanges()抛出该异常时,本次调用中包含的所有操作都会自动回滚——因为EF的隐式事务会保证原子性,要么全成功,要么全失败。 - 但如果是之前已经独立调用过
SaveChanges()的操作(比如你代码里context1.SaveChanges()已经成功提交),这部分数据已经写入数据库,不会自动回滚。这也是你当前代码的问题所在:context1的修改已经生效,就算context2冲突,也没法撤销context1的更改。
针对你需求的实现方案
你的目标是冲突时终止所有操作(包括已执行的),所以需要把所有相关操作纳入同一个事务中,以下是两种常见场景的实现:
场景1:所有操作在同一个DbContext中
这是最推荐的方式,无需手动管理事务,EF自动处理原子性:
using (var context = new YourDbContext()) { try { // 批量执行所有CRUD操作 var user1 = context.Purchases.First(); user1.Amount = Convert.ToDecimal(7.99); var user2 = context.Purchases.First(); user2.Amount = Convert.ToDecimal(10.99); // 一次提交所有操作 context.SaveChanges(); MessageBox.Show("Update Succeeded!"); } catch (DbUpdateConcurrencyException) { // 本次SaveChanges的所有操作已自动回滚,直接提示用户 MessageBox.Show("并发冲突,所有操作已终止!"); } }
场景2:必须使用多个DbContext
此时需要用显式事务绑定多个上下文,这里用TransactionScope(EF6支持):
using (var transactionScope = new TransactionScope()) { try { using (var context1 = new YourDbContext()) { var user1 = context1.Purchases.First(); user1.Amount = Convert.ToDecimal(7.99); context1.SaveChanges(); } using (var context2 = new YourDbContext()) { var user2 = context2.Purchases.First(); user2.Amount = Convert.ToDecimal(10.99); context2.SaveChanges(); } // 所有操作成功后,提交事务 transactionScope.Complete(); MessageBox.Show("Update Succeeded!"); } catch (DbUpdateConcurrencyException) { // 未调用Complete,事务自动回滚所有操作 MessageBox.Show("并发冲突,所有操作已回滚!"); } }
对你当前代码的调整建议
你现在的代码中context1和context2的操作是独立提交的,不符合"冲突时终止所有操作"的要求。必须把它们合并到同一个事务中,才能保证任意一个操作失败时,全部操作都回滚。
内容的提问来源于stack exchange,提问作者xProgrammerx
相关产品推荐
相关产品推荐

