Sending NServiceBus message inside TransactionScope

时间秒杀一切 提交于 2019-12-11 08:29:49

问题


I am trying to use NHibernate to save to a database in the same transaction as sending a message on the bus from inside an MVC application:

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();
}

In the configuration, .MsmqTransport() is set with .IsTransactional(true).

If I do this inside a message handler (which is wrapped in its own transaction so does not need the TransactionScope) Then it all works as expected, and if I include an exception, both fail.

However, if I do it inside my own transaction in an MVC application, I get the following error after transactionScope.Complete() when leaving the using block.:

'The operation is not valid for the current state of the enlistment.'

Stack Trace: at System.Transactions.EnlistmentState.InternalIndoubt(InternalEnlistment enlistment) at System.Transactions.VolatileDemultiplexer.BroadcastInDoubt(VolatileEnlistmentSet& volatiles) at System.Transactions.TransactionStatePromotedIndoubt.EnterState(InternalTransaction tx) at System.Transactions.TransactionStatePromotedBase.InDoubtFromEnlistment(InternalTransaction tx) at System.Transactions.DurableEnlistmentDelegated.InDoubt(InternalEnlistment enlistment, Exception e) at System.Transactions.SinglePhaseEnlistment.InDoubt(Exception e) at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment) at System.Transactions.TransactionStateDelegatedCommitting.EnterState(InternalTransaction tx) at System.Transactions.TransactionStateDelegated.BeginCommit(InternalTransaction tx, Boolean asyncCommit, AsyncCallback asyncCallback, Object asyncState) at System.Transactions.CommittableTransaction.Commit() at System.Transactions.TransactionScope.InternalDispose() at System.Transactions.TransactionScope.Dispose() at HumanResources.Application.Implementations.HolidayService.Book(BookHolidayRequest request) in C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.Application\Implementations\HolidayService.cs:line 76 at HumanResources.UI.Controllers.HolidayController.BookUpdate(BookHolidayViewModel viewModel) in C:\Users\paul.davies\Documents\GitHub\EdaCalendarExample\HumanResources.UI\Controllers\HolidayController.cs:line 82 at lambda_method(Closure , ControllerBase , Object[] ) at System.Web.Mvc.ActionMethodDispatcher.Execute(ControllerBase controller, Object[] parameters) at System.Web.Mvc.ReflectedActionDescriptor.Execute(ControllerContext controllerContext, IDictionary2 parameters) at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethod(ControllerContext controllerContext, ActionDescriptor actionDescriptor, IDictionary2 parameters) at System.Web.Mvc.ControllerActionInvoker.<>c_DisplayClass15.b_12() at System.Web.Mvc.ControllerActionInvoker.InvokeActionMethodFilter(IActionFilter filter, ActionExecutingContext preContext, Func`1 continuation)

Latest Edit:

This code works:

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();
}

This code creates the error:

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();
}

Note that I am not saving th entity in either example. The difference is in the second example, I am modifying the entity I have got from NHibernate. This is 100% reproducable.


回答1:


This may not be related but you still have to call _session.Flush() before committing a TransactionScope even if the session flush mode is set to Commit - that only works for NH provided transactions.




回答2:


As far as I can tell there is no way of being notified when a new System.Transactions.Transaction is created, and looking at the code in NHibernate it doesn't seem to have any code to deal with the situation where the TransactionScope is created AFTER creating the session.

When you create the session, it will try to enlist in the current Transaction, and if there isn't one then the session won't enlist in the transaction. I suspect that this is what's causing the transaction to fail on commit.

I would suggest creating the session INSIDE the TransactionScope - also check whether you are calling session.BeginTransaction somewhere before the TransactionScope.



来源:https://stackoverflow.com/questions/12494943/sending-nservicebus-message-inside-transactionscope

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!