Можно ли создать 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 на сервере вместе с распределенными транзакциями, подробности см. В этом сообщении в блоге.

Другие вопросы по тегам