基于DbUpdateConcurrencyException实现乐观并发:为何覆盖原始值可行?
为什么
entry.OriginalValues.SetValues(entry.GetDatabaseValues())能解决EF并发冲突? 嘿,这个问题问到点子上了!我当初刚处理EF并发场景的时候,也对着这段代码琢磨了好久,现在给你拆解清楚其中的逻辑:
先搞懂EF的并发检测逻辑
EF(包括EF Core)判断并发冲突的核心逻辑其实很直白:
- 当你从数据库读取实体时,EF会把读取那一刻的数据库值存到实体条目的
OriginalValues里,把它当成更新的基准线。 - 当你调用
SaveChanges()时,EF会生成带WHERE条件的更新SQL,用OriginalValues里的字段去匹配数据库中的记录。 - 如果数据库里的对应记录已经被其他操作修改,导致
WHERE条件匹配不到任何行,EF就会抛出DbUpdateConcurrencyException——这就是并发冲突的信号。
这段代码为什么能解决并发问题?
当捕获到并发异常后,这段代码做了最关键的一步:更新EF的更新基准线:
ex.Entries.Single()拿到引发冲突的那个实体条目(假设一次只更新一个实体,所以用Single)。entry.GetDatabaseValues()获取数据库中该实体的最新真实值——也就是被其他人修改后的状态。entry.OriginalValues.SetValues(...)把这个最新值覆盖到OriginalValues里,相当于把EF的更新基准从「你第一次读取的旧快照」改成「数据库现在的真实状态」。
这样当你再次调用SaveChanges()时,EF生成的WHERE条件会用更新后的OriginalValues去匹配数据库,这时候只要在你处理冲突的这段时间里没有新的修改,更新就能成功。本质上是把你的更新操作「重新对齐」到数据库的最新状态,避免再次触发并发检测失败。
覆盖原始值的合理性
你可能会疑惑:为什么要覆盖掉原来的OriginalValues?原因太直接了:
- 原来的
OriginalValues已经完全过时无效了,它对应的是你读取数据那一刻的状态,而现在数据库里的状态已经被其他操作修改了,这个旧基准已经不能用来做有效的并发检测了。 - 如果不更新
OriginalValues,你重试SaveChanges()的时候,EF还是会用旧的基准去匹配数据库,依然会触发DbUpdateConcurrencyException,冲突根本解决不了。 - 当然,这一步只是解决了「让更新操作能重新发起」的基础问题,通常你还需要结合业务逻辑:比如把用户当前的修改(
CurrentValues)合并到最新的数据库值上,或者提示用户确认是否覆盖,再执行后续的保存操作。
举个接地气的例子:
你读取了一条商品记录,价格是100(
OriginalValues=100),然后你想改成120。这时候另一个操作把价格改成了110,你调用SaveChanges()时抛出并发异常。
执行这段代码后,OriginalValues被改成了110,然后你可以把CurrentValues的价格改成120(或者根据业务做合并),再调用SaveChanges()——这时候EF会用WHERE 价格=110去匹配,只要没人再改,就能成功更新到120。
内容的提问来源于stack exchange,提问作者HTTP 418
相关产品推荐
相关产品推荐

