mirror of https://github.com/abpframework/abp
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.
148 lines
5.2 KiB
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.
|
|
|
|

|