How
to handle concurrency in Entity Framework Core
Problem
Concurrency handling refers
to the technique of detecting
and resolving concurrency conflicts. It ensures safety, integrity and consistency of your data. Concurrency conflicts happen when two or more users
try to access the same resource concurrently, i.e., at the same point of
time. Entity Framework is an Object Relational Mapper (ORM) from Microsoft and
Entity Framework Core is the version of Entity Framework that runs on .NET Core. How do
we handle concurrency violations in Entity Framework Core? Check out this tip
to find a solution.
Solution
You can detect concurrency
violations in EF Core in two
different ways. These include configuring the properties of the entities
as concurrency tokens or by adding a "row version" property in the
entity classes. We'll examine both one by one.
Pessimistic
and optimistic concurrency
Concurrency conflicts can be
of two types: pessimistic concurrency and optimistic concurrency. The
Pessimistic concurrency technique applies an explicit lock on the shared resource, i.e., an
explicit lock is performed on the record that is edited concurrently. On the contrary, in Optimistic
concurrency handling technique, the last saved record wins - there is no lock
on the shared resource. Entity Framework Core provides built-in support
for optimistic concurrency. So, EF Core enables multiple processes or users to
make changes to the same piece of data independently without the overhead of
synchronization.
Detecting Conflicts Using Concurrency
Tokens
To enable optimistic
concurrency in Entity Framework Core, you can take advantage of the ConcurrencyCheck attribute. Assume that an
update or delete operation is performed on an entity that has the
ConcurrencyCheck attribute set on one or more of the properties of the entity.
The EF Core runtime compares the value of the concurrency token, i.e., the
value of the property of the entity that has the ConcurrencyCheck attribute set
against the original value of the property that was read by EF Core. While
comparing these values if there is a match the operation is performed
successfully. If these values don't match, i.e., there is a concurrency conflict due to concurrent access to
the same entity, the EF Core runtime reports the concurrency violation
due to a conflicting operation on the entity by another user and the
transaction is aborted.
The following code snippet
shows how the ConcurrencyCheck attribute can be applied on a property of an
entity class.
public class Employee
{
public int Id { get; set; }
[ConcurrencyCheck]
public string Code { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
Alternatively, you can configure the
properties using the IsConcurrencyToken method as shown below.
public class PayrollDataContext : DbContext
{
public DbSet<Employee> Employees { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Employee>()
.Property(a => a.Code).IsConcurrencyToken();
}
}
Detecting Conflicts By Adding A Rowversion Property
The other technique for
detecting concurrency is using a row version on the entity. In this technique a
column is added to the database table to store the version stamp of the record.
Accordingly, a row version property is included in the model class as well.
This version stamp column in the database table is increased each time data is
inserted or modified. Let's now understand how concurrency conflicts occur. Assume
that there are two users, namely UserA and UserB. Now suppose UserA and UserB
have read a row of data (a record to be more precise) each from the database
table. The row version value for both these users will be the same. Now suppose
UserB has submitted his changes to the database. In doing so, the row version
value will be increased. When UserA subsequently tries to modify the same
record, the row version value read earlier will not match with the row version
value in the database. Subsequently when you try to submit the changes to the
database, the EF Core runtime will throw a DbUpdateConcurrencyException.
It should be noted that a
property should be of type byte array to be mapped to a row version column. If
you would want concurrency checks in multiple entities in your application, you
can use a base entity class instead. The following code snippet illustrates how
a base entity class can be created with two properties – Id and RowVersion
having the data types as int and Timestamp respectively.
public class EntityBase
{
public int Id { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
Once the base entity class has been
created; you can have the other entities in your application extend this class
as shown in the code snippet given below.
public class Employee : EntityBase
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
}
Resolving Data Concurrency Conflicts
There are three types of
values that are tracked by EF Core – these will help you resolve concurrency
conflicts. These values include the following:
- Current values
- this represents the current values that the application would write to
the database
- Original
values - this represents the values that were initially retrieved from the
database
- Database
values - this represents the values that are stored in the database
Refer to the code snippet
below that shows how concurrency conflicts in EF Core can be resolved.
using (var dbContext = new PayrollDataContext())
{
Employee employee = dbContext.Employees.Find(1);
employee.Address = "Hyderabad, Telengana, INDIA";
try
{
dbContext.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
ex.Entries.Single().Reload();
dbContext.SaveChanges();
}
}
The SaveChanges method is called inside a
try-catch block so that runtime exceptions can be handled. If a concurrency
conflict occurs (due to concurrent updates to the same record) at the time when
the SaveChanges method is called on the DbContext instance, an exception of
type DbUpdateConcurrencyException will be thrown. Note that the Entries
property of the DbUpdateConcurrencyException instance would provide you a list
of the DbEntityEntry instances that correspond to the entities that
could not be updated during a call to the SaveChanges method.
The Reload method in the
catch block updates the current values of the entity in memory with those in
the database. Once the updated data has been read into the entity, the
SaveChanges method is called on the data context again to persist the changes
to the database.
Next Steps
- Although EF
Core provides support for optimistic concurrency control, it's up to you
to decide the type of concurrency control mechanism to be implemented for
your application.
- The choice
between pessimistic or optimistic concurrency control is an important
decision you need to take - there should be a balance between performance,
scalability and data availability.