In this lesson, you will learn how to implement robust resolution logic when two users attempt to modify the same database record simultaneously using Entity Framework Core.
The Problem: Last-In Wins
By default, EF Core follows a "last-in wins" approach. If User A and User B both load the same record, modify it, and save, User B will silently overwrite User A’s changes. To prevent this, we use a concurrency token—typically a [Timestamp] or [ConcurrencyCheck] attribute on a property—that forces EF Core to verify the database value matches the version we originally loaded. When it doesn't match, EF Core throws a DbUpdateConcurrencyException.
Implementing Resolution Logic
To handle this, we wrap our SaveChangesAsync() call in a try-catch block. When the exception is caught, we access ex.Entries to inspect the conflicting data and decide how to resolve the clash.
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)
{
// The record was deleted by someone else
throw new Exception("Record already deleted.");
}
// Logic: Keep the database values, or merge them?
// Here, we refresh the entry with the latest database values
entry.OriginalValues.SetValues(databaseValues);
}
}
Choosing a Strategy
When a conflict occurs, you generally have three options:
- Client Wins: Overwrite the database with your changes (rarely recommended).
- Database Wins: Refresh your local object with the latest database values and notify the user their changes were bypassed.
- Merge: Programmatically compare the
proposedValuesanddatabaseValuesto keep non-conflicting changes from both users.
By catching the exception, you move from a silent failure to a controlled workflow where you can either automate a merge or prompt the user to manually resolve the differences.
Key Takeaway: Always wrap your save operations in a concurrency-aware try-catch block to gracefully handle data collisions instead of letting them overwrite your users' work.