Course contentsShow
← C#
Lesson 1079 of 1,90810. Entity Framework Core ModelingFree lesson

Optimistic Concurrency Exceptions

Implement robust conflict resolution logic for concurrent data updates.

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:

  1. Client Wins: Overwrite the database with your changes (rarely recommended).
  2. Database Wins: Refresh your local object with the latest database values and notify the user their changes were bypassed.
  3. Merge: Programmatically compare the proposedValues and databaseValues to 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.