Concurrency Handling in Entity Framework

Concurrency refers to the situation where multiple users or processes attempt to access and modify the same data simultaneously. In applications that interact with databases, effective concurrency handling is crucial to prevent data corruption, lost updates, and inconsistent states.

Entity Framework provides several strategies to manage concurrency, allowing you to choose the approach that best suits your application's requirements.

Understanding Concurrency Conflicts

A concurrency conflict occurs when a change made by one user is about to be overwritten by another user who is working with an older version of the data. Entity Framework detects these conflicts when it attempts to save changes back to the database.

Concurrency Control Strategies

1. Optimistic Concurrency

Optimistic concurrency assumes that conflicts are rare. It doesn't lock data when it's read. Instead, it checks for conflicts when changes are submitted. If a conflict is detected, an exception is thrown, and the application can decide how to resolve it.

Detecting Concurrency Violations

Entity Framework typically uses a version number or timestamp column in the database to detect changes. When you query an entity, EF retrieves its current version. When you save changes, EF compares the version of the entity in memory with the version in the database.

Marking Properties for Concurrency Check

You can mark properties (like a `Version` or `Timestamp` column) in your entity models to be part of the concurrency check using the ConcurrencyCheck attribute or by configuring it via Fluent API.


using System.ComponentModel.DataAnnotations;

public class Product
{
    public int ProductId { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [ConcurrencyCheck]
    public byte[] RowVersion { get; set; } // Or int Timestamp, etc.
}
                

Using Fluent API:


modelBuilder.Entity<Product>()
            .Property(p => p.RowVersion)
            .IsConcurrencyToken();
                

Handling Concurrency Exceptions

When a DbUpdateConcurrencyException occurs, you can catch it and implement a resolution strategy.

Catching and Resolving Concurrency Exceptions


try
{
    _context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
    // Refresh the entity from the database
    var entry = ex.Entries.Single();
    var databaseValues = entry.GetDatabaseValues();

    if (databaseValues == null)
    {
        // The entity was deleted by another user.
        Console.WriteLine("The entity has been deleted by another user.");
    }
    else
    {
        var databaseEntity = (Product)databaseValues.ToObject();

        // Here you can decide how to resolve the conflict:
        // 1. Overwrite the database with current values (Client Wins)
        entry.OriginalValues.SetValues(databaseValues);
        _context.Entry(entry.Entity).State = EntityState.Modified;

        // 2. Reload original values and discard current changes (Database Wins)
        // entry.Reload();

        // 3. Merge changes manually (Custom Resolution)
        // For example, merge price updates:
        // entry.CurrentValues.SetValue("Price", databaseEntity.Price + 10);
        // entry.OriginalValues.SetValues(databaseValues);
        // _context.Entry(entry.Entity).State = EntityState.Modified;

        // Retry saving
        _context.SaveChanges();
    }
}
                

2. Pessimistic Concurrency

Pessimistic concurrency locks data when it's read, preventing other users from modifying it until the lock is released. This is typically achieved by using database-level locks.

Entity Framework doesn't directly manage pessimistic concurrency. You would typically implement this at the database level using transactions with appropriate isolation levels (e.g., Serializable or Repeatable Read) and locking mechanisms provided by your database system.

Note on Pessimistic Concurrency

Pessimistic concurrency can reduce concurrency and lead to performance issues if locks are held for too long. Optimistic concurrency is generally preferred in web applications where a large number of users might access data concurrently.

Concurrency Resolution Strategies

When a DbUpdateConcurrencyException is thrown, you have several options for resolution:

Tip for Handling Concurrent Deletes

If entry.GetDatabaseValues() returns null, it means the entity has been deleted from the database by another process. You should handle this scenario gracefully, perhaps by informing the user that the item is no longer available.

Best Practices