Можно ли создать TransactionScope в пользовательском поведении службы WCF? (async, await, TransactionScopeAsyncFlowOption.Enabled)
TL;DR?
Скринкаст, объясняющий проблему: https://youtu.be/B-Q3T5KpiYk
проблема
При передаче транзакции от клиента в сервис Transaction.Current становится нулевым после ожидания вызова сервиса в сервис.
Если, конечно, вы не создадите новый TransactionScope в своем методе обслуживания следующим образом:
[OperationBehavior(TransactionScopeRequired = true)]
public async Task CallAsync()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
await _service.WriteAsync();
await _service.WriteAsync();
scope.Complete();
}
}
Почему TransactionScopeAsyncFlowOption не включен по умолчанию, я не знаю, но я не люблю повторяться, поэтому решил, что всегда буду создавать внутреннюю транзакцию с этой опцией, используя пользовательское поведение.
Проблема ОБНОВЛЕНИЕ
Это даже не должен быть вызов службы для обслуживания, ожидание локального асинхронного метода также обнуляет Transaction.Current. Прояснить на примере
[OperationBehavior(TransactionScopeRequired = true)]
public async Task CallAsync()
{
await WriteAsync();
// Transaction.Current is now null
await WriteAsync();
}
Попытка решения
Я создал инспектор сообщений, внедрив IDispatchMessageInspector и прикрепив его как поведение службы, код выполняется и все без проблем там, но это не имеет такого же эффекта, как объявление транзакции в методе службы.
public class TransactionScopeMessageInspector : IDispatchMessageInspector
{
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
var transactionMessage = (TransactionMessageProperty)OperationContext.Current.IncomingMessageProperties["TransactionMessageProperty"];
var scope = new TransactionScope(transactionMessage.Transaction, TransactionScopeAsyncFlowOption.Enabled);
return scope;
}
public void BeforeSendReply(ref Message reply, object correlationState)
{
var transaction = correlationState as TransactionScope;
if (transaction != null)
{
transaction.Complete();
transaction.Dispose();
}
}
}
глядя на идентификаторы при отладке, я вижу, что на самом деле это та же транзакция в инспекторе сообщений, что и в службе, но после первого вызова, т.е.
await _service_WriteAsync();
Транзакция. Текущий становится нулевым. То же самое, если не получить текущую транзакцию из OperationContext.Current в инспекторе сообщений, поэтому вряд ли проблема в этом.
Вопрос
Возможно ли это сделать? Похоже, что единственный способ - объявить TransactionScope в методе службы, то есть:
public async Task CallAsync()
{
var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled);
await _service.WriteAsync();
await _service.WriteAsync();
scope.Complete();
}
со следующим контрактом на обслуживание очевидно, что мы получаем исключение при втором вызове службы, если транзакция.current стала нулевой между ними
[OperationContract, TransactionFlow(TransactionFlowOption.Mandatory)]
Task WriteAsync();
1 ответ
Оказывается, нам не следует использовать ключевое слово async/await на сервере вместе с распределенными транзакциями, подробности см. В этом сообщении в блоге.