Concurrency handling is a critical aspect of building scalable, reliable applications with Entity Framework Core. In multi-user environments such as ASP.NET Core web applications, multiple users may attempt to update the same data simultaneously. Without proper concurrency control, this can result in data loss, inconsistent records, or unexpected overwrites. Entity Framework Core provides built-in mechanisms to manage concurrency effectively and maintain data integrity.
This article explains optimistic concurrency in EF Core, how concurrency tokens work, how to implement row versioning, and best practices for handling concurrency conflicts in production systems.
Understanding Concurrency in EF Core
Concurrency occurs when two or more users access and modify the same data simultaneously. There are two main types of concurrency control in database systems:
Optimistic Concurrency
Pessimistic Concurrency
Entity Framework Core primarily supports optimistic concurrency. In this model, EF Core assumes that conflicts are rare and checks for changes only when saving data.
If another user modifies the record after it is loaded, EF Core throws a DbUpdateConcurrencyException during SaveChanges().
What Is Optimistic Concurrency?
Optimistic concurrency works by comparing the original values with the current database values at the time of the update. If the values differ, a concurrency conflict is detected.
EF Core implements this using a concurrency token. A concurrency token is a property that tracks changes to a row.
Implementing Concurrency Using RowVersion
The most common way to handle concurrency in SQL Server with EF Core is by using a RowVersion column.
Step 1: Add a RowVersion Property
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
The [Timestamp] attribute marks RowVersion as a concurrency token.
Step 2: Configure in Fluent API (Optional)
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property(p => p.RowVersion)
.IsRowVersion();
}
Step 3: Update Database
When using migrations, EF Core creates a rowversion column in SQL Server.
During update, EF Core generates SQL similar to:
UPDATE Products
SET Name = @Name, Price = @Price
WHERE Id = @Id AND RowVersion = @OriginalRowVersion;
If no rows are affected, EF Core throws DbUpdateConcurrencyException.
Handling DbUpdateConcurrencyException
You must handle concurrency exceptions gracefully in application code.
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Product)
{
var proposedValues = entry.CurrentValues;
var databaseValues = await entry.GetDatabaseValuesAsync();
if (databaseValues == null)
{
Console.WriteLine("Entity was deleted by another user.");
}
else
{
entry.OriginalValues.SetValues(databaseValues);
}
}
}
}
You can choose different conflict resolution strategies:
Client Wins (overwrite database values)
Database Wins (reload database values)
Merge Changes (manual resolution)
Using Concurrency Tokens Without RowVersion
Instead of RowVersion, you can mark any property as a concurrency token.
modelBuilder.Entity<Product>()
.Property(p => p.Price)
.IsConcurrencyToken();
In this case, EF Core compares the original Price value during update.
However, using RowVersion is recommended for SQL Server because it is automatically managed by the database engine.
Concurrency in ASP.NET Core Web Applications
In web applications, concurrency issues commonly occur when:
Two users edit the same form simultaneously
Admin dashboards modify shared records
Background jobs update the same data
To handle this properly:
Include RowVersion in your ViewModel
Send it back in hidden fields
Validate concurrency during update
Example ViewModel:
public class ProductViewModel
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public byte[] RowVersion { get; set; }
}
This ensures EF Core can detect if the record has changed between read and update.
Best Practices for Handling Concurrency in EF Core
Prefer optimistic concurrency for web applications
Use RowVersion with SQL Server
Always handle DbUpdateConcurrencyException
Provide user-friendly conflict messages
Log concurrency conflicts for monitoring
Avoid long-running transactions
Proper concurrency handling ensures database consistency while maintaining application scalability.
Summary
Handling concurrency in Entity Framework Core is essential for maintaining data integrity in multi-user applications. EF Core uses optimistic concurrency by default and detects conflicts through concurrency tokens such as RowVersion. When a conflict occurs, DbUpdateConcurrencyException is thrown, allowing developers to resolve it using strategies like client wins, database wins, or manual merging. By implementing RowVersion correctly, handling exceptions gracefully, and following best practices in ASP.NET Core applications, developers can build reliable and high-performance systems that prevent accidental data overwrites and ensure consistent database operations.