I recently started working on a new project in which we wanted to use WCF services that utilized NHibernate for database work. We also wanted those services to support distributed transactions so that several calls to one or more service would be done within the same client transaction. This is possible thanks to functionality in the System.Transactions namespace and in WCF which supports transaction flowing and of course the Distributed Transaction Coordinator in the operating system (see MSDN for more info on the DTC).
Note: The code below has been tested on NHibernate 2.1.1, Windows XP and .Net 4 Beta 2. Older versions of the .Net Framework should also work, but not necessarily older versions of NHibernate. I believe distributed transaction support was introduced in 2.1.0, but it may or may not work in similar ways to what is described here in older versions since the ADO.Net supports the System.Transactions namespace.
The goal is to write code like this on the WCF client side:
If all goes well, the results of both service calls are comitted to the database. If the call to service2 fails and we get an exception so that tx.Complete() is never executed, then all database updates are rolled back are rolled back and nothing is persisted, even if service1 is hosted in another process or on another machine.
Note also that we're not limited to database updates, any resource that supports transactions and knows about System.Transactions will be able to roll back updates.
For the above to work, we have to do several things:
That setting dictates that the client's transaction will be used, if there is one. If there isn't, a new one will be created automatically for the method call and it will be auto-committed when the method returns.
You can also use TransactionFlowOption.Mandatory to require the client to supply the transaction. If it doesn't then WCF will throw an exception.
This will supply the method with the transaction (the other settings are not enough, they simply indicate that WCF should be configured to be prepared supply the transaction and this setting says that the method really wants it).
This is actually all that is required! NHibernate will now detect if there is a so called ambient transaction (to do this yourself, look at the System.Transactions.Transaction.Current static property, if it's non-null there there is a transaction) and will enlist its session in it. When the transaction completes, then the saved data will be comitted to the database. If there is an exception so that transaction is never completed then all data will be rolled back.
Important notes:
then you have to do as it says and enable network access in the DTC on the server where the WCF service is hosted (and also on the database server I would assume, but I haven't actually checked). Here's an instruction on how to do that:
Network access for Distributed Transaction Manager (MSDTC) has been disabled
I think this is really cool stuff. Not only does it simplify transaction management in NHibernate, it also allows us to write much more robust distributed service-oriented application with very little effort. You also get support in the operating system, for example för statistics:
I haven't tried with other databases than SQL Server but as NHibernate seems to support System.Transactions it is possible that it works with other DB systems as well. If you have any experience with that, please leave a comment
I will continue to update this post if I do more findings on this subject. When I google about this there wasn't much information on this subject so hopefully this post will help others with the same needs.
/Emil
[powered by WordPress.]
jour·nal n. A personal record of occurrences, experiences, and reflections kept on a regular basis; a diary.
| M | T | W | T | F | S | S |
|---|---|---|---|---|---|---|
| « Jun | ||||||
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | |||
41 queries. 0.727 seconds
December 10th, 2009 at 18:15
Hi, nice article.
I have a question and maybe you can give me a hand which i would really appreciate.
All documentation on NHIBERNATE says one would have to call flush method before commit. Using this pattern i dont have to call it and looking at NHProf it just works, transactions are committed and data is saved. Has something changed regarding support for system transactions that you are aware of that could make the call to flush unnecesary?.
The documentation does say that explicit calls to flush arent necessary if using the NHibernate Itransaction API maybe that is the case, maybe now System.Transactions integrates better with NHibernate transaction API.
Thanks
December 12th, 2009 at 20:57
Hi Ernesto,
I'm actually not aware of the need to flush before comitting transactions. If you have a reference to some docs with that information it would be interesting to have a look at.
It seems strange to me that we should have to worry about flushing since it should be a trivial thing to do for NHibernate itself before committing, but I might of course be missing something.
When I think about it, I might have had to do explicit flushing in earlier NHibernate projects I've been involved in (I have used NHibernate since early 2005) but I would think that issues like that have been ironed out by now. But if I'm wrong, I'd really like to know about it
/Emil
March 12th, 2010 at 11:18
Hi, I´ve been trying to accomplish the same thing as you have, but the TransactionScope is not honored.
I get no exceptions, bit the changes I male gets saved to the database even though I dont call .Complete() on the TranactionScope.
I´m interested in how you are hosting your wcf-services and how NHibernate is setup. Would it be possible to have a look at the code where you are doing this?
/martin
March 23rd, 2010 at 22:39
Hi Martin,
I'm afraid I can't give you the source code, but one tip is to check carefully how you manage your session and transactions in code. As noted in the post, don't close the session. And don't use explicit NHibernate transactions, which you probably don't as that is whole point of the above...
In your service, you can also check the System.Transactions.Transaction.Current static property sto see if you indeed have a distributed transaction. If it's null, then you don't have an ambient transaction at all (distributed or not).
If non-null, check its TransactionInformation.LocalIdentifier and TransactionInformation.DistributedIdentifier properties. Those values should give an indication of whether you have a distributed transaction or not. If you don't the I would suspect the the binding configuration doesn't flow transactions (must be configured on both client and server). Or maybe you're missing some of the attributes I mention in the post.
Good luck!
/Emil