dotnet data_error ai_generated true

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

Also available as: JSON · Markdown · 中文
80%Fix Rate
86%Confidence
1Evidence
2023-09-12First Seen

Version Compatibility

VersionStatusIntroducedDeprecatedNotes
6.0 active
7.0 active
8.0 active
9.0 active

Root Cause

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.

generic

中文

当另一个用户或进程在加载实体和调用 SaveChanges 之间修改或删除了同一行,并且并发令牌(如 RowVersion)不匹配时,会发生乐观并发冲突。

Official Documentation

https://learn.microsoft.com/en-us/ef/core/saving/concurrency

Workarounds

  1. 85% success 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.
    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.
  2. 80% success 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.
    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.

中文步骤

  1. 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.
  2. 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.

Dead Ends

Common approaches that don't work:

  1. 70% fail

    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.

  2. 80% fail

    Retrying the entire transaction without refreshing the entity from the database will still use the stale concurrency token, causing the same error repeatedly.