34

Can anyone give me a quick overview of using TransactionScope with NHibernate? Do I need to do anything special with the session/IEnlistmentNotification/etc. to get this to work? Are there any pitfalls that I should worry about? For example, can I replace all of my hibernate transactions:

var transaction = session.BeginTransaction();
try
{
    // code
    transaction.Commit();
}
catch (Exception)
{
    transaction.Rollback();
}

with this?:

using (var scope = new TransactionScope())
{
    // code
    scope.Complete();
}
Andy White
  • 86,444
  • 48
  • 176
  • 211

6 Answers6

19

I have been using nHibernate 2.1 for awhile, and after a few production issues and trying quite a few variations, we have settled on the following method, as per Avoiding Leaking Connections With NHibernate And TransactionScope:

        using (var scope = new TransactionScope(TransactionScopeOption.Required))
        {
            using (var session = sessionFactory.OpenSession())
            using (var transaction = session.BeginTransaction())
            {
                // do what you need to do with the session
                transaction.Commit();
            }
            scope.Complete();
        }

As we are using MSMQ and WCF so we had to use the ambient transaction.

We found that not using session.BeginTransaction() caused a connection leak. We also found that re-using a session after committing a transaction caused a race condition (nHibernate is not thread safe and DTSC Commits/Rollbacks occur on a background thread).

Iain
  • 10,814
  • 3
  • 36
  • 31
  • This doesn't appear to work, the internal transaction is commited before the parent scope is completed, this means id a failure on the parent scope occurs the transaction isnt rolled back – undefined May 18 '12 at 02:31
  • Are you sure? Have you tested it? From memory the internal transaction is not 'independent' from the parent scope. The transaction does not really commit until `scope.Complete()`. – Iain May 20 '12 at 01:06
  • Hey, thanks for the reply, I understand this post is pretty old. I would expect this to act as you have said however it doesn't seem to be the case. We have tested it, take a look at the tests i've done here: https://gist.github.com/2759471, (sorry didn't have time to simplify the sessionFactory stuff so its a bit long-winded) – undefined May 20 '12 at 21:32
  • At a glance, why is line 165 Assert 0 when line 162 has saved 1. Why is line 162 even there? – Iain May 21 '12 at 03:18
  • Sorry, removed that but forgot to update gist, the result is the same however – undefined May 21 '12 at 03:41
  • I just tested it and it works fine for me. I'm not sure what `SqlCeTestDatabase` is, but perhaps its support of transactions is not the same as SQL Server Express (what I am using). It might be better to start this as a new question. – Iain May 21 '12 at 05:41
  • I just [read this](http://msdn.microsoft.com/en-us/library/ms172400(v=sql.90).aspx), and perhaps the issue is ce doesn't support nested transactions, or distributed ones. – Iain May 21 '12 at 05:44
  • hmmm interesting, ill have to give it a whirl vs a real sql instance and see what happens. – undefined May 21 '12 at 06:17
  • I realise this was a while ago... any chance you can remember how the race condition caused by reusing the Session manifests itself? We have a hard to recreate problem when a "System.InvalidOperationException: Invalid attempt to call Read when reader is closed" exception is very occasionally thrown during a deferred read immediately following the TransactionScope. – TonE Feb 20 '13 at 15:55
  • 1
    I don't work on that code anymore, and my memory has faded. You error does look familiar. What I do remember is because the transaction commits on a background thread, by the time it does commit the main thread has reopened the session. The background thread then cleans up the session, and I think it killed the main thread with an error like yours, or a invalid transaction state error. This [question](http://stackoverflow.com/questions/8846210/nhibernate-failing-on-deferred-load-action) may be related. – Iain Feb 20 '13 at 22:51
  • [result of 4 hours of try/error and research] Please note that you MUST properly commit every NHibernate transaction in your code. Relying on Dispose() to clean up your read-only transactions is not enough. I failed to do so and when I didn't call TransactionScope.Complete() and expected everything to roll back, oddly the transactions were committed to the database. (NHibernate 4.1, SQL Server 2012) – Moritz Both May 11 '18 at 09:44
  • This question and answer are currently presented as authoritative, but are they out-of-date as of NHibernate 5.x? Current docs read: `Instead of using NHibernate ITransaction, TransactionScope can be used. Please do not use both simultaneously.` Reference: https://nhibernate.info/doc/nhibernate-reference/transactions.html#transactions-scopes – Remi Despres-Smyth Apr 04 '19 at 18:42
7

I've testing this out using varying vendors and it just works. If you don't have "scope.Complete()" then the transaction will roll back. You may need to have MSDTC running the machine(s) involved if there is more than one durable resource. In that case MSDTC will automatically detect the ambient ADO.NET transactions and manage the whole thing.

Adam Fyles
  • 6,030
  • 1
  • 23
  • 29
  • 3
    Wrong; by default TransactionScope uses the Lightweight Transaction Manager which Monitors promotion. From MSDN: As long as at most a single durable resource manager is involved, there is nothing wrong with letting the underlying resource (such as Microsoft SQL Server 2005) manage the transaction. In such case, the LTM does not need to actually manage the transaction at all—its role should be reduced to monitoring the transaction for a need for promotion. – Henrik Feb 17 '11 at 21:09
  • 1
    Besides this, SqlConnection-s (if that's your DB), are 1) pooled behind the scenes, and 2) you can open more than one from a single thread without promoting the transaction as long as they don't share the transaction. To promote the transaction means to start using MSDTC. – Henrik Feb 17 '11 at 21:12
6

The above works OK provided you are using a connection provider that supports the use of a Light-weight Transaction Manager, such as SQL Server 2005/2008.

If you are using SQL Server 7/2000 then all of your transactions will become Distributed Transactions even if you only hit one database/resource. This is probably not what you would want in most cases, and will be expensive performance wise.

So checkout if your connection provider and database server combination are suitable for use with TransactionScope.

user133599
  • 61
  • 1
  • 1
6

I believe you can replace NHibernate transactions with this as long as you respect some constraints as some have already said:

  • Use a reasonably recent NHibernate version (>=3.1).
  • The underlying ADO.NET data provider must support TransactionScope (ok for SQL-Server, Oracle >= 10 with ODP.NET).
  • Create your NH session within the TransactionScope to ensure it gets enlisted.
  • Handle NHibernate flushing manually. See also here and here.
  • Prepare for distributed transactions. If you create multiple sessions in one scope, the initially local transaction might get promoted to a distributed transaction. I saw this happen even with same connection strings on an Oracle 11.2 DB using ODAC 11.2.0.3.20. On SQL-Server 2008 R2 it did not promote. (BTW, one can see this by watching Transaction.Current.TransactionInformation.DistributedIdentifier which is null for local transactions.) While distributed transactions have some advantages, they are more expensive and it is some extra pain setting them up. (See here how to do this for Oracle). To be sure promotion never happens in Oracle, set "Promotable Transaction=local" in your connection string. This creates an exception if some code tries to do so.

I hope that's all ;-)

PS: I added the Oracle details because they where my concern and others might benefit.

Community
  • 1
  • 1
Piper
  • 851
  • 11
  • 14
1

Also, if you are using TransactionScope, upgrade to NHibernate 2.1. It is only with 2.1 that NH really gotten good integration with TransactionScope.

Rohit Agarwal
  • 4,269
  • 3
  • 27
  • 22
1

According to Fabio Maulo in comments related to NH-2107:

You can use TransactionScope and you should continue using NH's transaction too. Where you have read that the Usage of TransactionScope mean the avoid of usage of NH's transaction ?

I would have assumed that explicit usage of NHibernate's transactions not necessary, but aparently that's best practice

Chris J
  • 30,688
  • 6
  • 69
  • 111
emertechie
  • 3,607
  • 2
  • 22
  • 22
  • I'm still looking for an authoritative answer to that question. In NH 3.0., explicit usage does not seem necessary - except perhaps to make auto-flushing work correctly. – Jeff Sternal Mar 03 '11 at 23:57
  • It also causes issues with SQL Compact (attempts to create a nested transaction, which seems like a bug to me): http://stackoverflow.com/questions/8127735/how-can-i-use-transactionscope-with-sql-compact-4-0-and-nhibernate – Travis Nov 16 '11 at 00:15
  • I update here Andrew's link where this discussion took place: https://nhibernate.jira.com/browse/NH-2107 – C3PO Oct 25 '16 at 08:36
  • Don't think this is still the case (as of 2019-Apr-4). Current docs read: `Instead of using NHibernate ITransaction, TransactionScope can be used. Please do not use both simultaneously.` Reference: https://nhibernate.info/doc/nhibernate-reference/transactions.html#transactions-scopes – Remi Despres-Smyth Apr 04 '19 at 18:40