Отправка сообщения NServiceBus внутри TransactionScope

Я пытаюсь использовать NHibernate для сохранения в базе данных в той же транзакции, что и отправка сообщения на шине из приложения MVC:

public void DoSomethingToEntity(Guid id)
{
    var session = _sessionFactory.OpenSession();
    CurrentSessionContext.Bind(session);

    using (var transactionScope = new TransactionScope())
    {
        var myEntity = _session.Get(id);
        myEntity.DoSomething();
        _session.Save(myEntity);
        _bus.Send(myMessage);
        transactionScope.Complete();
    }

    session.Dispose();
}

В конфигурации .MsmqTransport() устанавливается с .IsTransactional(true).

Если я делаю это внутри обработчика сообщений (который завернут в свою собственную транзакцию, поэтому TransactionScope не нужен). Затем все работает так, как ожидалось, и если я включаю исключение, оба сбой.

Однако, если я делаю это внутри своей собственной транзакции в приложении MVC, я получаю следующую ошибку после транзакцииScope.Complete() при выходе из блока использования.:

'Операция недействительна для текущего состояния призыва.'

Трассировка стека:  в System.Transactions.EnlistmentState.InternalIndoubt(InternalEnlistment)  в System.Transactions.VolatileDemultiplexer.BroadcastInDoubt(VolatileEnlistmentSet & volatiles)  в System.Transactions.TransactionStatePromotedIndoubt.EnterState(InternalTransaction tx)  в System.Transactions.TransactionStatePromotedBase.InDoubtFromEnlistment(InternalTransaction tx)  в System.Transactions.DurableEnlistmentDelegated.InDoubt(InternalEnlistment, Exception e)  в System.Transactions.SinglePhaseEnlistment.InDoubt(исключение e)  в System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment)  в System.Transactions.TransactionStateDelegatedCommitting.EnterState(InternalTransaction tx)  в System.Transactions.TransactionStateDelegated.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState)  в System.Transactions.CommittableTransaction.Commit()  в System.Transactions.TransactionScope.InternalDispose()  в System.Transactions.TransactionScope.Dispose()  в HumanResources.Application.Implementations.HolidayService.Book(запрос BookHolidayRequest) в C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.Application\Implementations\HolidayService.cs: строка 76  в HumanResources.UI.Controllers.HolidayController.BookUpdate(BookHolidayViewModel viewModel) в C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.UI\Controllers\HolidayController.cs: строка 82  на lambda_method (Closure, ControllerBase, Object [])  в System.Web.Mvc.ActionMethodDispatcher.Execute(контроллер ControllerBase, параметры Object [])  в System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary 2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary 2 параметра)  в System.Web.Mvc.ControllerActionInvoker. < > c_DisplayClass15.b_12()  в System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(фильтр IActionFilter, ActionExecutingContext preContext, продолжение Func`1)

Последнее редактирование:

Этот код работает:

public void DoSomethingToEntity(Guid id)
{
    var session = _sessionFactory.OpenSession();
    CurrentSessionContext.Bind(session);

    using (var transactionScope = new TransactionScope())
    {
        var myEntity = _session.Get(id);
        _bus.Send(myMessage);
        transactionScope.Complete();
    }

    session.Dispose();
}

Этот код создает ошибку:

public void DoSomethingToEntity(Guid id)
{
    var session = _sessionFactory.OpenSession();
    CurrentSessionContext.Bind(session);

    using (var transactionScope = new TransactionScope())
    {
        var myEntity = _session.Get(id);
        myEntity.AnyField = "a new value";
        _bus.Send(myMessage);
        transactionScope.Complete();
    }

    session.Dispose();
}

Обратите внимание, что я не сохраняю объект в любом примере. Разница во втором примере: я модифицирую сущность, которую я получил от NHibernate. Это 100% воспроизводимое.

Ответ 1

Это может быть не связано, но вам все равно придется вызывать _session.Flush(), прежде чем совершать TransactionScope, даже если для режима очистки сеанса установлено значение Commit - это работает только для транзакций NH.

Ответ 2

Насколько я могу судить, нет способа получить уведомление, когда создается новое System.Transactions.Transaction, и, глядя на код в NHibernate, у него нет никакого кода, чтобы иметь дело с ситуацией, когда TransactionScope создается ПОСЛЕ создания сеанса.

Когда вы создаете сеанс, он попытается записаться в текущую транзакцию, и если ее нет, сеанс не будет завершен в транзакции. Я подозреваю, что это то, что заставляет транзакцию терпеть неудачу при фиксации.

Я бы предложил создать сеанс INSIDE TransactionScope - также проверить, вызываете ли вы сеанс .BeginTransaction где-то перед TransactionScope.