# 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`
- **Domain:** dotnet
- **Category:** data_error
- **Verification:** ai_generated
- **Fix Rate:** 80%

## 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.

## Version Compatibility

| Version | Status | Introduced | Deprecated |
|---------|--------|------------|------------|
| 6.0 | active | — | — |
| 7.0 | active | — | — |
| 8.0 | active | — | — |
| 9.0 | active | — | — |

## Workarounds

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.** (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.
   ```
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.** (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.
   ```

## Dead Ends

- **** — 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. (70% fail)
- **** — Retrying the entire transaction without refreshing the entity from the database will still use the stale concurrency token, causing the same error repeatedly. (80% fail)
