We’ve been using NSB for a number of years with MSMQ transport and SQL persistence (MSSQL), with additional business data stored in that same SQL catalog. We’ve got a bunch of legacy code that uses NHibernate for modifications of that business data that we are happy with.
We are in the process of removing the MSMQ dependency for obvious reasons, and have chosen to go with SQL transport and put the queues into the one existing catalog.
One of the benefits of this would be removal of the dependency on MSDTC, however we’re having some trouble determining the right transaction mode to use to achieve that, in particular, getting NHibernate to play nicely with an existing connection.
I had initially assumed that we should use TransportTransactionMode.SendsAtomicWithReceive and WrapHandlersInATransactionScope, and we’ve setup our NHibernate code so we can open a session with an existing connection that we access via ISqlStorageSession (and we’ve followed the guidance around identical connection strings for transport and persistence for pooling). Unfortunately this doesn’t work; opening the NHibernate session causes Cannot enlist in the transaction because a local transaction is in progress on the connection. In the samples Entity Framework integration with SQL Persistence • Sql Persistence Samples • Particular Docs and SQL Server Transport and SQL Persistence • NServiceBus Samples • Particular Docs EF seems to provide the ability provide an existing connection and transaction, which is not something that NHibernate supports that I’m aware of.
Because we are just using the one transactional resource, I think we can still avoid MSDTC in TransactionScope mode because the transaction never needs to escalate. Only thing is that the MSSQL transaction has been started as DISTRIBUTED.
I’m looking for any advice on this situation and recommendations on path forward, even if that is just stick with TransactionScope.
TransactionScope just means it will use a TransactionScope object. We are opening/closing when invoking the pipeline and when the pipeline completes we do the same to delete the corresponding message row from the queue table.
It will only be promoted when required by there are overlapping connections.
In your last comment you would then be using TransactionScope and the transaction isn’t managed by SQL Transport via a SqlTransaction and also not by NHibernate its transaction abstraction. While not tested by me just now should work.
I think the problem is related to NHibernate and TransactionScope is it not?
In my first post I mentioned I was trying to use the combination of TransportTransactionMode.SendsAtomicWithReceive and WrapHandlersInATransactionScope (following guidance here Transport Transactions; Avoiding Partial Updates for transaction mode lower than TransactionScope). WrapHandlersInATransactionScope means I need to configure NHibernate for System.Transaction i.e. use AdoNetWithSystemTransactionFactory. But doing that means sharing the connection between transport and NHibernate doesn’t work because the connection that we configure the NHibernate session to use already has an explicit SqlTransaction (I assume due to Sql Persistence and TransportTransactionMode.SendsAtomicWithReceive?).
The WrapHandlersInATransactionScope feature sits between the transport and any message processing. Meaning that scope is created after any transaction required for the transport message fetch operation. It would prevent partial updates across any storage operation invoked somewhere in the pipeline but exclude the transport operation.
There is another solution and that is to not have the transport and any persistence share the same transaction and still get the same consistency by using the outbox feature:
However, then you have the issue that all persistence operations must share the same storage transaction to guard consistency.
That would make much sense for SQL transport unless your organization might consider to transition to another transport in the future.
So, yes, when using SendsAtomicWithReceive all storage commands must share the same connection object via the storage context. When using TransactionScope its the opposite, you should not use the storage context and rely on a TransactionScope and create your own connection and ensure connections do not have any overlap in their lifetimes.
But there is no problem with sharing the (storage context) connection under TransactionScope when all parts are using the same catalog on the same instance (i.e. sql transport, persistence and then business data all in one place)?