mirror of https://github.com/abpframework/abp.git
committed by
GitHub
41 changed files with 249 additions and 95 deletions
@ -1,3 +1,147 @@ |
|||||
## Concurrency Check |
## Concurrency Check |
||||
|
|
||||
TODO |
### 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, for that purpose you can use the `SetConcurrencyStampIfNotNull` method 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.SetConcurrencyStampIfNotNull(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, for that purpose you can use the `SetConcurrencyStampIfNotNull` method 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.SetConcurrencyStampIfNotNull(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. |
||||
|
|
||||
|
 |
||||
|
|||||
|
After Width: | Height: | Size: 21 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue