diff --git a/docs/en/Concurrency-Check.md b/docs/en/Concurrency-Check.md index e22afe82b8..0258c56754 100644 --- a/docs/en/Concurrency-Check.md +++ b/docs/en/Concurrency-Check.md @@ -1,3 +1,147 @@ ## Concurrency Check -TODO \ No newline at end of file +### Introduction + +Concurrency Check (also known as **Concurrency Control**) refers to specific mechanisms used to ensure data consistency in presence of concurrent changes (multiple processes or 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** the record. + + * 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 has a just simple property named `ConcurrencyStamp`. +* While a new record **creating**, if the entity implements the `IHasConcurrencyStamp` interface, ABP Framework sets a unique value to the **ConcurrencyStamp** property automatically. +* While a record **updating**, ABP Framework compares the **ConcurrencyStamp** property of the entity with the provided **ConcurrencyStamp** value by the user and if the values are matched, updates the **ConcurrencyStamp** property with the new unique value automatically. 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, IHasConcurrencyStamp +{ + public string ConcurrencyStamp { get; set; } + + //... +} +``` + +Also, implement your output and update DTO classes from the `IHasConcurrencyStamp` interface: + +```csharp +public class BookDto : EntityDto, 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 like as below: + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + //... + + public virtual async Task 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, 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` +- `CreationAuditedAggregateRoot`, `CreationAuditedAggregateRoot` +- `AuditedAggregateRoot`, `AuditedAggregateRoot` +- `FullAuditedAggregateRoot`, `FullAuditedAggregateRoot` + +**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 +{ + //... +} +``` + +Then, you can implement your output and update DTO classes from the `IHasConcurrencyStamp` interface: + +```csharp +public class BookDto : EntityDto, 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 like as below: + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + //... + + public virtual async Task 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, 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 like in the image below, if you don't handle the exception manually. + +![Optimistic Concurrency](./images/optimistic-concurrency.png) \ No newline at end of file diff --git a/docs/en/images/optimistic-concurrency.png b/docs/en/images/optimistic-concurrency.png new file mode 100644 index 0000000000..209bc82460 Binary files /dev/null and b/docs/en/images/optimistic-concurrency.png differ