Использование TransactionScope для выполнения операций после завершения транзакции¶
В этом примере мы рассмотрим использование области выполнения транзакции для запуска кастомной логики после завершения транзакции.
Для этого можно использовать свойство TransactionScopeContext.Current
. Данное свойство возвращает текущий контекст области выполнения транзакции в случае, если данная область была открыта. Данное свойство доступно во всех расширениях, выполняющихся в транзакции (BeforeCommitTransaction
и AfterCommitTransaction
) а также в коде шаблонов бизнес-процессов и маршрутов. В иных местах область выполнения транзакции доступна в случае, если код выполняется внутри транзакции. Проверить это можно с помощью свойства TransactionScopeContext.HasCurrent
.
У объекта ITransactionScopeContext
есть следующие свойства:
-
Handlers
- список обработчиков, которые будут выполненые после завершения транзакции. Их выполнение идёт последовательно по списку. -
Info
- дополнительная информация, которая будет передана в каждый из обработчиков.
Каждый обработчик в качестве параметра получает объект контекста с типом ITransactionFinishedContext
, который содержит следующие свойства:
-
ValidationResult
- результат валидации выполнения транзакции. Можно использовать для получения информации о возникшей ошибке или же дописать в него иные сообщение валидации. -
Info
- дополнительная информация, которая была добавлена в контекст выполнения транзакции. -
IsCommited
- флаг показывает, был ли выполнен коммит транзакции. Если флаг не установлен, значит был выполнен откат транзакции. -
DbScope
- объект для взаимодействия с базой данных. -
CancellationToken
- объект, посредством которого можно отменить асинхронную задачу.
Пример использования TransactionScopeContext.Current
для снятия взятой ранее блокировки после завершения транзакции:
using System.Threading.Tasks;
using Tessa.Cards;
using Tessa.Cards.Extensions;
using Tessa.Cards.Extensions.Templates;
using Tessa.Extensions.Default.Shared;
using Tessa.Platform;
using Tessa.Platform.Data;
namespace Tessa.Extensions.Server
{
public sealed class ExampleStoreExtension : CardStoreExtension
{
#region Fields
private readonly ICardLockingStrategy cardLockingStrategy;
#endregion
#region Constructors
public ExampleStoreExtension(
ICardLockingStrategy cardLockingStrategy)
{
Check.ArgumentNotNull(cardLockingStrategy, nameof(cardLockingStrategy));
this.cardLockingStrategy = cardLockingStrategy;
}
#endregion
#region Base Overrides
public override async Task AfterBeginTransaction(ICardStoreExtensionContext context)
{
// В качестве примера будем брать блокировку на чтение карточки сателлита типового решения и снимать её после завершения транзакции.
// Получаем идентификатор карточки сателлита
var satelliteID = await CardSatelliteHelper.TryGetUniversalSatelliteIDAsync(
context.DbScope,
context.Request.Card.ID,
null,
DefaultCardTypes.KrSatelliteTypeID,
context.CancellationToken);
// Не выполняем никакую логику, если он не задан
if (satelliteID is null)
{
return;
}
bool isSuccess;
// Берём блокировку в отдельном подключении к базе, чтобы не заблокировать всю строку в Instances
await using (context.DbScope.CreateNew())
{
(isSuccess, _) = await this.cardLockingStrategy.ObtainReaderLockAsync(
satelliteID.Value,
context.ValidationResult,
context.CancellationToken);
}
if (!isSuccess)
{
return;
}
// Добавляем обработчик, который выполнится после завершения текущей транзакции и снимет блокировку на чтение для карточки сателлита.
TransactionScopeContext.Current.Handlers.Add(async ctx =>
{
// Не пробрасываем ctx.CancellationToken в метод, чтобы выполнить снятие блокировки, даже выполняется откат транзакции из-за OperationCancelledException.
await this.cardLockingStrategy.ReleaseReaderLockAsync(satelliteID.Value, CancellationToken.None);
});
}
#endregion
}
}