1
votes

When trying to debug something I found code that was effectively doing the following:

  1. Creating a TransactionScope
  2. Creating a Transaction (in this case an nHibernate tx, but not really important)
  3. Creating a second transaction (in this case a standard ADO.Net Tx)
  4. Committing the second transaction
  5. Calling Complete() on the Transaction scope
  6. Disposing the Transaction Scope.

Now - Creating a transaction and not committing is probably a bad idea anyway - especially when having (and that was the bug fix).

However when testing this - I tried various combinations of the above (committing all transactions, some transactions, no transactions (i.e. only TScope) committing the First, but not second, adding other transactions etc) and in all thesting I found that the following to be true:

Only when I failed to commit the first transaction AND the transaction scope became distributed, the Dispose of the TScope would fail with:

System.InvalidOperationException : The operation is not valid for the current state of the enlistment.

I am now curious and would like to know why this is the case?

1
I'm confused. The purpose of the TransactionScope is to have a single transaction, either a local/lightweight one, or a distributed one. Multiple transactions in a TransactionScope is not what it was designed to do and defeats the purpose of it, imho, which is to basically let the transaction be taken care of in the background by the TransactionScope. Do you mean multiple connections?Maarten
ok.. so.. Two points to further the above. The above doesnt seem quite true - any nHibernate transaction created and left unopened within the TScope causes issues. To clarify the confusion: We currently have a single nHibernate stack with no transaction scope. We now have the need to connect to another db. All examples I see seem to have: 1. open TS 2. Open NH Tx 3. perorm NH work 4 Commit nh Tx 5 do other DB wrk 6 complete/dispose TS Are you saing that if we use TransacionScope, we should not use the nH transaction at all? Additionally - we need to do other DB work within the NH trans.kenam
btw - the reason TScope seems to be what we want as we dont always need to do the "Other DB Work" - therefore TScope will kick it into a distributed transaction. Maybe using TScope only (and no NH Transaction) is the correct solution - as it will create a Transaction anyway?kenam
For best results you should ALWAYS use the NH transaction. If it occurs within a TransactionScope it will automatically enlist in that. Without the NH transaction you may get problems, since the NH transaction has more responsibilities than a simple BEGIN/ROLLBACK/COMMIT in the SQL.Oskar Berggren

1 Answers

1
votes

I suspect the problem you see is covered by one of these: https://nhibernate.jira.com/issues/?jql=project%20%3D%2010000%20AND%20labels%20%3D%20TransactionScope

I'm not entirely sure what happens but I've seen similar behaviour, e.g. if NH enlists in the ambient transaction, and the transaction later becomes distributed, calling TransactionScope.Complete() might hang for 20 seconds and then fail.

NH will try to enlist in a TransactionScope even if you don't use an NH transaction. In this case, NH will flush changes during the ambient transaction's Prepare() phase. It will do this on the db connection, but that has also enlisted in the transaction and will get its own Prepare() call. Unfortunately I haven't been able to figure out the exact problem, but I suspect what happens is that in some circumstances the db connections Prepare() will be called before NHibernate's Prepare(). The latter will try to continue to use the db connection, and it appears this causes some sort of deadlock.

Using a NH transaction and committing this before completing the transaction scope will make NH flush its changes before the underlying DB connection enters the prepare-phase.