You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
abp/docs/en/Concurrency-Check.md

148 lines
5.2 KiB

## 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](https://www.martinfowler.com/eaaCatalog/pessimisticOfflineLock.html).
### Usage
#### `IHasConcurrencyStamp` Interface
To enable **concurrency control** to your entity class, you should implement the `IHasConcurrencyStamp` interface, directly or indirectly.
```csharp
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 Framework automatically sets a unique value to the **ConcurrencyStamp** property.
* While a record is **updating**, ABP Framework 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.
**Example: Applying Concurrency Control for the Book Entity**
Implement the `IHasConcurrencyStamp` interface for your entity:
```csharp
public class Book : Entity<Guid>, IHasConcurrencyStamp
{
public string ConcurrencyStamp { get; set; }
//...
}
```
Also, implement your output and update the DTO classes from the `IHasConcurrencyStamp` interface:
```csharp
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:
```csharp
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 ...
await BookRepository.UpdateAsync(book);
}
}
```
* 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](./Entities.md#aggregateroot-class) 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](#base-classes):
```csharp
public class Book : FullAuditedAggregateRoot<Guid>
{
//...
}
```
Then, you can implement your output and update the DTO classes from the `IHasConcurrencyStamp` interface:
```csharp
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:
```csharp
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 ...
await BookRepository.UpdateAsync(book);
}
}
```
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 Framework handle it for you.
ABP Framework shows a user-friendly error message as in the image below, if you don't handle the exception manually.
![Optimistic Concurrency](./images/optimistic-concurrency.png)