Concurrency Check

Introduction

Concurrency Check (also known as Concurrency Control) refers to specific mechanisms used to ensure data consistency in the presence of concurrent changes (multiple processes, users access or change the same data in a database at the same time).

There are two commonly used concurrency control mechanisms/approaches:

  • Optimistic Concurrency Control: Optimistic Concurrency Control allows multiple users to attempt to update the same record without informing the users that others are also attempting to update it.

    • If a user successfully updates the record, the other users need to get the latest changes for the current record to be able to make changes.
    • ABP's concurrency check system uses the Optimistic Concurrency Control.
  • Pessimistic Concurrency Control: Pessimistic Concurrency Control prevents simultaneous updates to records and uses a locking mechanism. For more information please see here.

Usage

IHasConcurrencyStamp Interface

To enable concurrency control to your entity class, you should implement the IHasConcurrencyStamp interface, directly or indirectly.

public interface IHasConcurrencyStamp 
{
    public string ConcurrencyStamp { get; set; }
}
  • It is the base interface for concurrency control and only has a simple property named ConcurrencyStamp.
  • While a new record is creating, if the entity implements the IHasConcurrencyStamp interface, ABP automatically sets a unique value to the ConcurrencyStamp property.
  • While a record is updating, ABP compares the ConcurrencyStamp property of the entity with the provided ConcurrencyStamp value by the user and if the values match, it automatically updates the ConcurrencyStamp property with the new unique value. If there is a mismatch, AbpDbConcurrencyException is thrown.

If there is a unit of work, you need to call the SaveChangesAsync method to get the generated ConcurrencyStamp when creating or updating.

Example: Applying Concurrency Control for the Book Entity

Implement the IHasConcurrencyStamp interface for your entity:

public class Book : Entity<Guid>, IHasConcurrencyStamp
{
    public string ConcurrencyStamp { get; set; }
        
    //...
}

Also, implement your output and update the DTO classes from the IHasConcurrencyStamp interface:

public class BookDto : EntityDto<Guid>, IHasConcurrencyStamp 
{
    //...

    public string ConcurrencyStamp { get; set; }
}

public class UpdateBookDto : IHasConcurrencyStamp 
{
    //...

    public string ConcurrencyStamp { get; set; }
}

Set the ConcurrencyStamp input value to the entity in the UpdateAsync method of your application service as below:

public class BookAppService : ApplicationService, IBookAppService 
{
    //...

    public virtual async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input) 
    {
        var book = await BookRepository.GetAsync(id);

        book.ConcurrencyStamp = input.ConcurrencyStamp;

        //set other input values to the entity ...
        //use autoSave: true to get the latest ConcurrencyStamp
        await BookRepository.UpdateAsync(book, autoSave: true);
    }
}
  • After that, when multiple users try to update the same record at the same time, the concurrency stamp mismatch occurs and AbpDbConcurrencyException is thrown.

Base Classes

Aggregate Root entity classes already implement the IHasConcurrencyStamp interface. So, if you are deriving from one of these base classes, you don't need to manually implement the IHasConcurrencyStamp interface:

  • AggregateRoot, AggregateRoot<TKey>
  • CreationAuditedAggregateRoot, CreationAuditedAggregateRoot<TKey>
  • AuditedAggregateRoot, AuditedAggregateRoot<TKey>
  • FullAuditedAggregateRoot, FullAuditedAggregateRoot<TKey>

Example: Applying Concurrency Control for the Book Entity

You can inherit your entity from one of the base classes:

public class Book : FullAuditedAggregateRoot<Guid>
{
    //...
}

Then, you can implement your output and update the DTO classes from the IHasConcurrencyStamp interface:

public class BookDto : EntityDto<Guid>, IHasConcurrencyStamp 
{
    //...

    public string ConcurrencyStamp { get; set; }
}

public class UpdateBookDto : IHasConcurrencyStamp 
{
    //...

    public string ConcurrencyStamp { get; set; }
}

Set the ConcurrencyStamp input value to the entity in the UpdateAsync method of your application service as below:

public class BookAppService : ApplicationService, IBookAppService 
{
    //...

    public virtual async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input) 
    {
        var book = await BookRepository.GetAsync(id);

        book.ConcurrencyStamp = input.ConcurrencyStamp;

        //set other input values to the entity ...
        //use autoSave: true to get the latest ConcurrencyStamp
        await BookRepository.UpdateAsync(book, autoSave: true);
    }
}

After that, when multiple users try to update the same record at the same time, the concurrency stamp mismatch occurs and AbpDbConcurrencyException is thrown. You can either handle the exception manually or let the ABP handle it for you.

ABP shows a user-friendly error message as in the image below, if you don't handle the exception manually.

Optimistic Concurrency

Contributors


Last updated: August 19, 2024 Edit this page on GitHub

Was this page helpful?

Please make a selection.

To help us improve, please share your reason for the negative feedback in the field below.

Please enter a note.

Thank you for your valuable feedback!

Please note that although we cannot respond to feedback, our team will use your comments to improve the experience.

In this document
Community Talks

Building Modular Monolith Applications Using .NET and ABP Framework

17 Oct, 17:00
Online
Watch the Event
Mastering ABP Framework Book
Mastering ABP Framework

This book will help you gain a complete understanding of the framework and modern web application development techniques.

Learn More