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:
// TransactionScope is from the System.Transactions namespace using (TransactionScope tx = new TransactionScope()) { service1.MyMethod(); service2.MyMethod(); tx.Complete(); }
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:
- Configure the service binding to support transaction flow (on both the client and service side). Example:
<system .serviceModel> <services> <service ... > <endpoint ... bindingConfiguration="TransactionalWsHttp"> ... </endpoint> ... </service> </services> <bindings> <wshttpbinding> <binding name="TransactionalWsHttp" transactionFlow="true"></binding> </wshttpbinding> </bindings> </system>
- Mark the methods that should be enlisted in the distributed transaction with the TransactionFlow attribute. Do this on the service contract interface:
[TransactionFlow(TransactionFlowOption.Allowed)] void SaveMyObject(Foobar object);
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.
- Mark the method implementation with the OperationBehavior attribute like this:
[OperationBehavior(TransactionScopeRequired=true)] public void SaveMyObject(Foobar object) { ... }
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:
- Don’t close the NHibernate ISession explicitly when using the above pattern as NHibernate will do that itself when the distributed transaction completes or is rolled back. If you do, then you will get an exception later when the transaction tries to do it.
- Don’t create transactions explicitly, let System.Transactions do that for you.
- If you get this error:
Network access for Distributed Transaction Manager (MSDTC) has been disabled. Please enable DTC for network access in the security configuration for MSDTC using the Component Services Administrative tool.
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
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
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
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
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 to 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 then I would suspect that 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
Hi,Emil.
I have many problems in my project use nhibernate in wcf service.
I get errors like this:The transaction has aborted. I do like you said in you article above.
You saied:Don’t close the NHibernate ISession explicitly .Can I understand like this:don’t use sesson.Close() and session.Clear().
Can you give me a hand for my project? If you have any ideas ,you can send an email ,and we can have a talk about it .
thank you !
/Mary
@Mary
Hi Mary,
Too bad you’re having problems. I’m afraid it’s very difficult to know what is causing them without having a look at the code but I would start by putting ISession.Close() back and see what happens. ISession.Clear() should not come into play, all that method does is to remove all objects from the session.
The reason I don’t do Close() is that I leave that to the distributed transactions. Do you use that as well? If you do, then removing them to see if that helps.
Also, which version of NHibernate are you using? My article describes version 2.1.1, I don’t know how later versions handle transactions. It may have changed.
Hope this helps,
Emil
Hi Emil, I have a problem and maybe you can help me on it….
I need to use NHibernate to do distribucted transaction with 2 different databases, and they could be fisically on the same server or in distinct servers. I want to do some operations in the first base, some operations on the second, and after all ok then I´ll commit both databases, or rollback if anyone fails.
Can you give me a clue in how to solve this problem?
Thanks in advance,
Rudolf Gütlich
Hi @Rudolf !
Right, so you don’t have WCF involved? That’s no problem, what you need is to not use NHibernate’s transaction handling but instead use TransactionScope as in the first code block in the blog post above. NHibernate should pick up the distributed transaction automatically.
Note that you probably need to open the session (and this database connection) *inside* the transaction scope), at least that’s the case in earlier NHibernate versions.
And of course you need to enable MSDTC on all involved servers (application server and the database servers), open the relevant firewall ports, etc. But that stuff should be relatively easy to google.
Hope this helps. Feel free to post back about your progress, success or not.
Cheers,
Emil
Hi Emil, first of all thanks for your quick and great reply!´
I´ll try it asap, and I´ll let you know about the progress.
I have another question that maybe you can help: I am mapping one database with NHibernate, but I need to map other dbs whose table names match.
I believe that I can use the auto-import=”false” attribute (from ) , but I am not sure of that. Any ideas?
Thanks,
Rudolf Gütlich
First of all, I don’t think you can share a configuration/SessionFactory between different databases. An NHibernate session wraps an normal ADO.Net IDbConnection and that is always connected to an individual database.
So to solve the problem of having different databases (possibly on different servers) you have to use separate configurations (separate files or created in code). This of course also means that objects in one configuration cannot refer to objects in the other, the models must be completely separate. If that is what you want, then I’d say you’re doing something wrong anyway 😉
If you have two separate models, then the problem of indicating the correct database is moot.
Thanks for this excellent post.
However, if for example we want to create a company and then create an employee, the creation of the profile employee on the company id from the first call. This is the point at which we have an issue. The call to create an employee loads the company and then adds the new employee to the company. Each call will create a new NHibernate session and therefore the 2nd call has no knowledge of the company that is still yet to be persisted.
What’s the recommended approach for overcoming this? I’ve done some reading and we could use a session per conversation model by using a NHibernate.Burrow. From my understanding Burrow allows for the NHibernate Session to be stored within the ASP.Net session, however if using ASP.Net session with WCF then this cannot be used across calls. I thought about serializing NHibernate session to disk so that it can be used across calls, but this feels a bit messy.
Any input would be appreciated
Thanks
James
Hi @James Harper ,
I understand your problem and I think that you should have a look at your way of managing the NHibernate session, which is what you’re suggesting yourself. There are many ways to do that but I can’t actually point you in a direction right now since it been quite a while since I worked with WCF and NHibernate. I remember one method I have come across, though, which was using StructureMap to create session instances with different scopes.
However, you should be really careful to not associate the NHibernate session with a long-running ASP.Net session. Be sure to check that the session is closed at the end of your conversation or you’ll get some nasty memory leaks. I have experienced this and similar problems and they can be really painful to pin down. Another team at work had a bug like this in production code for a large customer, in a national-wide application, and it took them a week to track it down. That wasn’t a very pleasant week…
Perhaps you’re better off to merge your two calls into one, i.e. CreateCompanyAndEmployees(model) where model contains both company info and a list of employees to create. That way you can use per-call session scope and avoid the problems altogether. It problably also makes for a better service API, it’s generally recommended to avoid very CRUD-like API’s in SOA architectures, but of course that depends on the context…
Good luck!
/Emil
Hi,
Came up with a solution for this in the end. We are caching the NHibernate session between calls using .Net 4.0 MemoryCache. This only happens if the call is part of a transaction.
The UnitOfWork is created on a per request basis but the session is effectively passed between calls. I think this could closely be mirrored to a session per conversation/tranaction design rather than a session per request.
Hope this helps anyone who had a similar issue
Thanks
James
Hi Emil,
Sorry, just read your post. From what you are saying, this may need to rework. I’ll take another look and consider the memory leaks. The work I was doing was more for proof of concept, so not something that’s going straight to production anyway.
Thanks for your information. It was very useful.
Regards
James
actually session.Flush before commit is not needed,
unless you have a distributed transaction…
with DTC if you have dirty objects in nhibernate session, and you don’t call session.Flush, then transactionScope.Complete() will fail…