问题
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.
回答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