Handling automatic update of concurrency tokens in Entity Framework Core
| Petru Rebeja
Introduction
If your application uses Entity Framework Core and has a business logic that is of medium or higher complexity, at some point, you may want to bake into that business logic some mechanism for handling concurrency when persisting the data into the database.
If your software uses SQL Server as the database management system then things are really simple. All you need to do in to implement the following steps:
- Create a
RowVersion
property in your entities, - Configure that property as a concurrency token in the entity configuration, and
- Run the migration scripts.
Once all of the above are done the concurrency management works out of the box.
However, in any other circumstances the things get complicated. This post is about one of those other circumstances, namely when the database management system is Oracle Server, and the version property is an integer value.
Automatic update of concurrency tokens
The hint on how to approach such use-case lies buried in the documentation, behind a simple link, which contains the keyword for the actual solution: use an interceptor when saving changes to automatically update the value of the property that holds the concurrency token. And since that documentation suffers from too much obscurity for my taste, below are the steps on how to achieve the same goal.
Prerequisites
Let's say we have an entity named Person
in our code-base with the following structure:
public class Person
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Version { get; set; }
}
where we want the property Version
to act as a concurrency token which will be updated automatically each time the data of the entity is updated.
If we have more than one entity for which we want to implement the automatic update of concurrency tokens we can extract an interface to define the concurrency token property:
public interface IConcurrencyAwareEntity
{
int Version { get; set; }
}
After defining the interface, we use it to mark all desired entities as being concurrency aware:
public class Person : IConcurrencyAwareEntity
{
public long Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public int Version { get; set; }
}
Entity configuration
We need to tell Entity Framework Core to enable the concurrency checking mechanism for the Person
entity by specifying that the Version
property is a concurrency token in the entity configuration:
internal class PersonEntityConfiguration
: IEntityTypeConfiguration<Person>
{
public void Configure(EntityTypeBuilder<Person> builder)
{
// ...
builder.Property(p => p.Version)
.HasColumnName("VERSION")
.HasColumnType("NUMBER")
.IsConcurrencyToken();
}
}
Interceptor implementation
With the entity properly configured, it is time to implement the interceptor that will increment the value of the Version
property automatically when the entity is saved into the database.
To do so, we need to create a class that derives from the SaveChangesInterceptor
class. This class will override the SavingChanges
, and SavingChangesAsync
methods in order to:
- iterate through all updated or inserted items from the change tracker of the database context,
- for each entity, check if it implements the
IConcurrencyAwareEntity
interface - if yes — increment the
Version
property.
internal class ConcurrencyEntitySaveChangesInterceptor
: SaveChangesInterceptor
{
public override InterceptionResult<int> SavingChanges(
DbContextEventData eventData,
InterceptionResult<int> result)
{
this.IncrementVersionOfConcurrencyAwareEntities(
eventData.Context);
return base.SavingChanges(eventData, result);
}
public override ValueTask<InterceptionResult<int>>
SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
this.IncrementVersionOfConcurrencyAwareEntities(
eventData.Context);
return base.SavingChangesAsync(
eventData,
result,
cancellationToken);
}
private void IncrementVersionOfConcurrencyAwareEntities(
DbContext context)
{
foreach(var entry in GetEntriesToUpdate(context))
{
var entity = entry.Entity as IConcurrencyAwareEntity;
if(entry.State is EntityState.Added)
{
entity.Version = 1;
continue;
}
var version = entry.Property(nameof(entity.Version));
version.OriginalValue = entity.Version;
entity.Version++;
}
}
private IEnumerable<EntityEntry> GetEntriesToUpdate(
DbContext context) =>
context.ChangeTracker.Entries()
.Where(e => e.Entity is IConcurrencyAwareEntity)
.Where(e => e.State == EntityState.Added ||
e.State == EntityState.Modified);
}
While the method GetEntriesToUpdate()
from above is self-explanatory, the method IncrementVersionOfConcurrencyAwareEntities()
requires a bit of commentary on what it does to each EntityEntry
instance.
If the entity does not exist in the database (i. e. when the corresponding entry has the state EntityState.Added
) we just need to set the value of the property Version
to 1, and nothing else because when the entity will be persisted Entity Framework Core will issue an INSERT
statement.
However, when an entity that has concurrency handling enabled needs to be persisted, and it exists in the database already, when issuing the UPDATE
statement Entity Framework Core will include in the WHERE
clause a condition on the value of the concurrency token alongside the primary key of the entity. In the case of the Person
entity declared above, the UPDATE
statement would look like this:
update persons
set first_name = 'John',
last_name = 'Doe',
version = 3
where id = 42
and version = 2
The value 2
in the example above is the value that the Version
property had when the entity was loaded from the database. This is the reason why the method IncrementVersionOfConcurrencyAwareEntities
contains these two lines:
var version = entry.Property(nameof(entity.Version));
version.OriginalValue = entity.Version;
These lines signal Entity Framework Core that it should update the row that has the value of the Version
property equal to the value it had before incrementing it.
Add the interceptor to the DbContext
Now that we have an interceptor to automatically increment the value of the Version
property, all we need to do is to add it to the database context. This is done quite easily:
- Register your interceptor class in the dependency injection framework
-
Inject the interceptor into your
DbContext
classpublic PersonsDbContext( ConcurrencyEntitySaveChangesInterceptor interceptor) { this.interceptor = interceptor; }
-
Add the interceptor to the context class in
OnConfiguring
methodprotected override void OnConfiguring( DbContextOptionsBuilder optionsBuilder) { base.OnConfiguring(optionsBuilder); optionsBuilder.AddInterceptors(this.interceptor); }
Once the interceptor is registered, you should see the value of the Version
property increase automatically each time an entity marked with IConcurrencyAwareEntity
interface is updated.
Concurency handling
At this point concurrency handling kicks-in out of the box thanks to the two aforementioned lines from the IncrementVersionOfConcurrencyAwareEntities
method:
var version = entry.Property(nameof(entity.Version));
version.OriginalValue = entity.Version;
The lines above encapsulate the essence of the concurrency handling mechanism. They signal Entity Framework (in a bit of a contrived way admittedly) that we want to update the entity if and only if the entity in the database has the value of the concurrency token (Version
property in our case) equal to the original value of the property when the entity was loaded, i.e. the value of the Version
property before incrementing it.
If there is no row in the database table that satisfies the condition above, then no rows are updated, and subsequently Entity Framework Core throws a DbUpdateConcurrencyException
which, as its name tells, signals that the row we are trying to update was changed in the meanwhile.
Conclusion
Entity Framework Core has a built-in mechanism for handling concurrency that works great out of the box when the application uses the default types for concurency tokens, and SQL Server as the database management system. This mechanism relies on comparing the values of the concurrency token for each database operation; if the value supplied by the application is not the same as the value from the database this is considered to be a concurrency conflict.
But once you stray off the beaten path, things become more complicated as this post shows while trying to provide a guide on how to automatically update the value of the concurrency tokens in non-default scenarios. Fortunately, the concurrency handling mechanism from Entity Framework Core is designed to accommodate a wide range of use-cases, and with a slight adjustment through a custom interceptor that updates the value of concurrency tokens when saving entities to the database, we can ensure the conflict handling mechanism operates as originally intended
Comments
Comments powered by Disqus