Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: 数据库操作预期影响 1 行,但实际影响了 0 行。自加载实体以来,数据可能已被修改或删除。
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: The database operation was expected to affect 1 row(s), but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
ID: dotnet/ef-core-savechanges-optimistic-concurrency
版本兼容性
| 版本 | 状态 | 引入 | 弃用 | 备注 |
|---|---|---|---|---|
| 6.0 | active | — | — | — |
| 7.0 | active | — | — | — |
| 8.0 | active | — | — | — |
| 9.0 | active | — | — | — |
根因分析
当另一个用户或进程在加载实体和调用 SaveChanges 之间修改或删除了同一行,并且并发令牌(如 RowVersion)不匹配时,会发生乐观并发冲突。
English
Optimistic concurrency conflict occurs when another user or process modifies or deletes the same row between the time the entity was loaded and SaveChanges is called, and the concurrency token (e.g., RowVersion) does not match.
官方文档
https://learn.microsoft.com/en-us/ef/core/saving/concurrency解决方案
-
Implement a retry loop that catches DbUpdateConcurrencyException, refreshes the entity from the database using 'await context.Entry(entity).ReloadAsync()', and then reapplies changes before calling SaveChanges again.
-
Use a database-first approach with a timestamp column: add a byte[] property annotated with '[Timestamp]' to the entity, and ensure all updates include the original timestamp value in the WHERE clause.
无效尝试
常见但无效的做法:
-
70% 失败
Disabling concurrency tokens by removing RowVersion properties allows overwrites without conflict detection, but can lead to lost updates and data corruption in multi-user scenarios.
-
80% 失败
Retrying the entire transaction without refreshing the entity from the database will still use the stale concurrency token, causing the same error repeatedly.