In this lesson, you will learn how to programmatically resolve DbUpdateConcurrencyException by choosing whether to preserve your local changes or adopt the values currently residing in the database.
When using optimistic concurrency (typically with a [Timestamp] or RowVersion property), EF Core prevents you from accidentally overwriting changes made by another user. When a conflict occurs, the database throws an exception, and you are left to decide how to proceed.
Choosing Your Strategy
When you catch a DbUpdateConcurrencyException, the Entries property of the exception contains the entities that failed to update. You can resolve the conflict by overwriting the database values with your current values (Client Wins) or reloading the entity from the database to discard your changes (Database Wins).
Here is how you implement these strategies:
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
var proposedValues = entry.CurrentValues;
var databaseValues = await entry.GetDatabaseValuesAsync();
if (databaseValues == null)
{
throw new Exception("The entity was deleted by another user.");
}
// STRATEGY 1: Client Wins
// Update the original values to match the database's current version
// so the next SaveChanges call succeeds without conflict.
entry.OriginalValues.SetValues(databaseValues);
// STRATEGY 2: Database Wins
// Simply refresh the entity, discarding your local changes.
// await entry.ReloadAsync();
}
await context.SaveChangesAsync();
}
Which Strategy to Use?
- Client Wins: Use this when the user’s intent is the source of truth, regardless of intermediate background updates (e.g., a "Last-write-wins" scenario).
- Database Wins: Use this when the data in the database is critical and the user’s local state is considered stale or obsolete.
In more complex applications, you might prompt the user to merge these differences manually. By manipulating the OriginalValues and CurrentValues collections, you gain full control over the reconciliation process, ensuring data integrity without losing the context of the conflict.
Key Takeaway: Use entry.OriginalValues.SetValues() to force your changes over the database, or entry.ReloadAsync() to discard your changes and synchronize with the database state.