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, IDictionary
2 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.