C# tutorials > Frameworks and Libraries > Entity Framework Core (EF Core) > Concurrency control (optimistic, pessimistic)
Concurrency control (optimistic, pessimistic)
This tutorial explores concurrency control in Entity Framework Core (EF Core), focusing on optimistic and pessimistic approaches. We'll delve into their implementation, use cases, and considerations when building multi-user applications.
Introduction to Concurrency Control
Concurrency control is crucial when multiple users or processes access and modify the same data simultaneously. Without proper handling, this can lead to data corruption and inconsistencies. EF Core provides mechanisms to manage concurrency and ensure data integrity. Two primary strategies are optimistic concurrency and pessimistic concurrency.
Optimistic Concurrency
Optimistic concurrency assumes that conflicts are rare. Instead of locking data during editing, it checks for modifications before applying changes. This approach improves performance and scalability, but requires handling potential conflicts.
Implementing Optimistic Concurrency with Row Versioning
A common method for optimistic concurrency is using a row version
or timestamp
column. This column is automatically updated each time the row is modified. When updating a row, the application checks if the current row version
matches the original version. If they don't match, it indicates that another user has modified the row in the meantime.
Defining the Row Version Property
In your EF Core entity, add a property with the [Timestamp]
attribute. This attribute tells EF Core to use this property for concurrency conflict detection. The property is typically a byte[]
(row version) or a DateTime
.
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
Updating Data with Concurrency Handling
When you call The SaveChanges()
, EF Core checks if the row version
of the entity being updated matches the row version
in the database. If they don't match, a DbUpdateConcurrencyException
is thrown.DbUpdateConcurrencyException
provides information about the conflict, allowing you to retrieve the current database values and resolve the conflict appropriately. Common strategies include:
try
{
_context.Products.Update(product);
_context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
var entry = ex.Entries.Single();
var databaseValues = entry.GetDatabaseValues();
if (databaseValues == null)
{
// The record was deleted.
Console.WriteLine("Record was deleted by another user.");
}
else
{
var databaseValuesAsObject = databaseValues.ToObject();
Console.WriteLine("Concurrency conflict detected:");
// Handle the conflict (e.g., display the database values to the user).
}
}
Pessimistic Concurrency
Pessimistic concurrency assumes that conflicts are likely. It locks the data being accessed to prevent other users from modifying it until the lock is released. This approach guarantees data consistency but can negatively impact performance and scalability due to locking.
Implementing Pessimistic Concurrency with SQL Server Hints
EF Core doesn't provide built-in support for pessimistic locking. You typically achieve pessimistic concurrency by using database-specific locking mechanisms. In SQL Server, this is often done using table hints like The Important: Pessimistic locking can lead to performance bottlenecks if not implemented carefully. Keep transactions short and only lock the necessary rows.UPDLOCK
and ROWLOCK
.UPDLOCK
hint prevents other transactions from acquiring update locks on the row. ROWLOCK
specifies that row-level locks should be used. This approach requires careful transaction management to avoid deadlocks.
using (var transaction = _context.Database.BeginTransaction())
{
var product = _context.Products
.FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK, ROWLOCK) WHERE ProductId = {0}", productId)
.FirstOrDefault();
if (product != null)
{
product.Price = newPrice;
_context.SaveChanges();
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
When to Use Optimistic vs. Pessimistic Concurrency
Pros and Cons of Optimistic Concurrency
Pros:
Cons:
Pros and Cons of Pessimistic Concurrency
Pros:
Cons:
Best Practices
Real-Life Use Case Section
Scenario: E-commerce Product Inventory Imagine an e-commerce website where multiple users can purchase the same product simultaneously. If the inventory count is low, concurrent purchases could lead to overselling. Optimistic Concurrency: The website could use optimistic concurrency with a row version on the Pessimistic Concurrency (Less Common): Alternatively, the website could use pessimistic concurrency by locking the product record when a user adds it to their cart. This would prevent other users from purchasing the same product until the first user completes the purchase or cancels the order. However, this approach could lead to a poor user experience if the purchase process takes a long time.Products
table. When a user adds an item to their cart, the application records the current row version
of the product. Before completing the purchase, the application checks if the row version
in the database still matches the recorded version. If they match, the purchase is allowed, and the inventory is decremented. If they don't match, it means another user has purchased the product in the meantime, and the user is informed that the product is no longer available.
Interview Tip
When discussing concurrency control in interviews, demonstrate your understanding of both optimistic and pessimistic approaches, their trade-offs, and practical scenarios where each approach is appropriate. Be prepared to discuss implementation details, potential challenges, and best practices.
Alternatives
While row versioning and pessimistic locking are common, other approaches to concurrency management exist:
Memory footprint
Optimistic Concurrency: The memory footprint is minimal. It only involves storing the row version value, which is typically a small byte array or DateTime value. Pessimistic Concurrency: Pessimistic concurrency can have a greater impact on memory due to the database engine needing to maintain locks for longer periods, potentially increasing memory usage and resource contention on the database server.
FAQ
-
What happens if I don't handle the DbUpdateConcurrencyException?
If you don't handle theDbUpdateConcurrencyException
, your application will crash, and the changes will not be saved. It's crucial to catch this exception and implement appropriate conflict resolution logic. -
Can I use optimistic concurrency with detached entities?
Yes, you can use optimistic concurrency with detached entities. When attaching the entity back to the context, ensure that you set the original value of therow version
property so that EF Core can detect concurrency conflicts. -
Is pessimistic locking always a bad idea?
No, pessimistic locking is not always a bad idea. It's appropriate in situations where data integrity is paramount and conflicts are highly likely. However, it's essential to use it judiciously and keep transactions short to minimize performance impact.