Managing Concurrency in EF Core: Optimistic vs Pessimistic Locking
Concurrency control is a critical aspect of application development, especially when dealing with multi-user environments. In Entity Framework (EF) Core, managing concurrency becomes essential to ensure data consistency across transactions. In this article, we will dive into two primary concurrency management strategies—Optimistic Locking and Pessimistic Locking—explaining how each works, when to use them, and how they are implemented in EF Core.
What is Optimistic Locking?
Optimistic Locking assumes that most operations will not conflict, so no locks are held on the data until it's time to save changes. This strategy works well in high-concurrency environments where conflicts are rare, allowing transactions to proceed without waiting on locks.
How Optimistic Locking Works
In EF Core, optimistic locking is usually achieved by adding a concurrency token to the entity, such as a version or timestamp field. When an update is made, EF Core checks this token to ensure no other transaction has modified the data since it was last read. If a conflict is detected, a DbUpdateConcurrencyException is thrown, indicating that the update cannot proceed.
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Optimistic concurrency token
[Timestamp]
public byte[] RowVersion { get; set; }
}In this example, the RowVersion property acts as the concurrency token. When an update occurs, EF Core checks this property to detect changes made by other transactions.
Here's how you might handle optimistic locking in practice:
try
{
var product = dbContext.Products.Find(productId);
product.Price = newPrice;
// Save changes with optimistic concurrency check
dbContext.SaveChanges();
}
catch (DbUpdateConcurrencyException)
{
// Handle the concurrency conflict
Console.WriteLine("Concurrency conflict detected!");
}Ideal Scenarios for Optimistic Locking
Optimistic locking is best suited for scenarios where:
Conflicts are rare.
High concurrency is expected, and performance is a priority.
The application can tolerate occasional retry logic in case of conflicts.
What is Pessimistic Locking?
Pessimistic Locking takes a more cautious approach, assuming that conflicts are likely. When data is read, it is locked immediately, preventing other transactions from modifying it until the lock is released. This approach ensures strict data consistency but can lead to performance bottlenecks in high-concurrency scenarios.
How Pessimistic Locking Works
In EF Core, pessimistic locking is typically implemented using raw SQL queries with locking hints like UPDLOCK and ROWLOCK. This ensures that the data is locked for the duration of the transaction, preventing other operations from reading or writing to it.
using (var transaction = dbContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
using (var transaction = dbContext.Database.BeginTransaction(System.Data.IsolationLevel.Serializable))
{
// Execute raw SQL with pessimistic locking
var product = dbContext.Products
.FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK, ROWLOCK) WHERE Id = {0}", productId)
.SingleOrDefault();
// Modify the product
product.Price = newPrice;
// Save changes
dbContext.SaveChanges();
// Commit the transaction
transaction.Commit();
}In this example, the UPDLOCK and ROWLOCK hints are used to lock the selected row until the transaction is complete.
Ideal Scenarios for Pessimistic Locking
Pessimistic locking is most appropriate when:
Data integrity is critical, and conflicts must be avoided at all costs.
You can afford the performance overhead that comes with locking resources.
Transactions are short-lived, reducing the risk of deadlocks.
Understanding Which Strategy to Use
The choice between optimistic and pessimistic locking depends on the specific requirements of your application. Optimistic locking is generally the preferred option when:
You expect minimal conflicts between transactions.
High performance and scalability are crucial.
On the other hand, pessimistic locking is better suited for situations where:
Conflicts are frequent.
Strict data integrity is essential, and conflicts must be prevented proactively.
Conclusion
In EF Core, both optimistic and pessimistic locking offer distinct advantages depending on your application's concurrency needs. By understanding the trade-offs between performance and data integrity, you can select the right locking strategy to ensure smooth and reliable operation, even in complex, multi-user environments.
Make sure to choose the right strategy based on your application's requirements. Optimistic locking is usually a better fit for systems with high concurrency and rare conflicts, while pessimistic locking is ideal for environments where conflicts are more common and data integrity is paramount.


