🔄 DbUpdateConcurrencyException Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException
❓ What is DbUpdateConcurrencyException?
DbUpdateConcurrencyException is thrown by Entity Framework Core when an optimistic concurrency conflict occurs during a SaveChanges or SaveChangesAsync call.
This happens when you attempt to update or delete an entity that has been modified or deleted in the database since it was loaded into the context.
⚠️ Typical Stack Trace
Microsoft.EntityFrameworkCore.DbUpdateConcurrencyException: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded.
This is a runtime exception that signals a data consistency issue. It is not a bug in EF Core but an indication that your application needs to handle concurrent edits gracefully.
🔍 Root Causes
The exception occurs when one of the following conditions is true:
Two users loaded the same entity, one saved changes, then the other tried to save an older version.
Another process deleted the record after it was loaded into the context.
No RowVersion or ConcurrencyCheck attribute is applied, so EF can't detect changes.
When using disconnected scenarios, the client may send an outdated row version.
A trigger modifies the row after EF’s update, causing the rowversion to change unexpectedly.
UseSqlServer without proper isolation level
Transaction
Snapshot isolation or other settings can affect concurrency detection.
🛠️ Step‑by‑Step Fixes
Apply these strategies to resolve concurrency issues:
Catch the exception and inspect the Entries property to see which entities caused the conflict.
Use ReloadAsync() or GetDatabaseValues() to get the latest values from the database.
Common strategies: client wins (overwrite), database wins (discard changes), or merge (combine).
Use Polly to retry the operation a few times with delays to give transient conflicts time to resolve.
In UI applications, inform the user that the data changed and ask them to review their changes.
✅ Quick Tip
Always include a row version (timestamp) column in your entities to enable EF to detect conflicts automatically.
⚖️ Optimistic Concurrency Explained
EF Core uses optimistic concurrency by default. It assumes that conflicts are rare, so it doesn't lock rows during reads. When saving, it checks if the row has changed since it was loaded.
To detect changes, EF uses a concurrency token – a property marked with [Timestamp] or [ConcurrencyCheck]. During update, EF includes the original value of this token in the SQL WHERE clause. If the token value doesn't match the database, the update affects zero rows and EF throws the exception.
💡 Note
Without a concurrency token, EF will still try to update by primary key only – but then you won't get a concurrency exception; instead, the last write wins (lost update problem).
⏱️ Configuring RowVersion (Timestamp)
In SQL Server, use a rowversion (formerly timestamp) column. In other databases, use a byte[] with IsRowVersion or IsConcurrencyToken.
Fluent API configuration
Data Annotations
When you retrieve an entity, the RowVersion is populated. When you update, you must send the same RowVersion back so EF can include it in the WHERE clause.
⚠️ Important for API scenarios
If you expose entities via REST APIs, you must send the RowVersion back to the server. Otherwise, EF will use the default value (all zeros) and the update will fail (0 rows affected).
🛡️ Handling DbUpdateConcurrencyException
When the exception occurs, you can use the Entries collection to get the affected entities and their current database values.
In a typical web API, you might return a 409 Conflict response with details, and let the client resolve the conflict.
🔁 Retry Logic with Polly
Transient concurrency conflicts can be resolved by retrying the entire transaction. Use Polly to implement a retry policy specifically for DbUpdateConcurrencyException.
In ASP.NET Core, you can register a retry policy with AddDbContext using EnableRetryOnFailure:
💡 Note
This built‑in retry handles transient errors (including concurrency) but may not be sufficient for all conflict scenarios. Combining with manual handling is recommended.
🌐 ASP.NET Core & API Design
In web APIs, you typically receive a DTO with the entity's data, including the row version. When a concurrency conflict occurs, you can return a 409 Conflict with the current database values so the client can decide.
🧪 Testing Concurrency Scenarios
To test concurrency, you can simulate two contexts modifying the same entity.
In unit tests, you can mock the DbContext or use an in‑memory database (though in‑memory doesn't enforce concurrency, so you'd need a real database or a custom test double).
🛡️ Prevention and Best Practices
Use RowVersion (byte[]) with [Timestamp] or IsRowVersion() for SQL Server.
In disconnected scenarios (REST, Blazor, etc.), ensure the client sends the original token.
Handle transient conflicts with Polly or built‑in EF retry logic.
In UI apps, show a conflict resolution dialog so users can decide how to merge changes.
If conflicts are frequent, consider using pessimistic locking (e.g., UPDLOCK), but this can reduce scalability.
Log details (entity, old/new values) to help diagnose frequent conflicts.
🏆 Pro Tip
For large scale applications, consider using ETag (HTTP) with row version to let clients know if their version is stale before they even try to update – this reduces unnecessary roundtrips.
0 Comments
thanks for your comments!