This week I've been working on a brownfield Castle-powered WCF service that was creating a separate NHibernate session on every call to a repository object.
Abusing NHibernate like this was playing all sorts of hell for our app (e.g. TransientObjectExceptions), and prevented us from using transactions that matched with a logical unit of work, so I set about refactoring it.
- One session per WCF operation
- One transaction per WCF operation
- Direct access to ISession in my services
- Rely on Castle facilities as much as possible
- No hand-rolled code to plug everything together
There are a plethora of blog posts out there to tackle this problem, but most of them require lots of hand-rolled code. Here are a couple of good ones -- they both create a custom WCF context extension to hold the NHibernate session, and initialize/dispose it via WCF behaviours:
- NHibernate Session Per Request Using Castles WcfFacility
- NHibernate's ISession, scoped for a single WCF-call
Option one: manual Session.BeginTransaction() / Commit()
The easiest way to do this is to register NHibernate's ISession in the container, with a per WCF operation lifestyle:
If you want a transaction, you have to manually open and commit it. (You don't need to worry about anything else because NHibernate's ITransaction rolls back automatically on dispose):
(Note of course we are using WindsorServiceHostFactory so Castle acts as a factory for our WCF services. And disclaimer: I am not advocating putting data access and persistence directly in your WCF services here; in reality ISession would more likely be injected into query objects and repositories each with a per WCF operation lifestyle (you can use this to check for lifestyle conflicts). It is just an example for this post.)
Anyway, that's pretty good, and allows a great deal of control. But developers must remember to use a transaction, or remember to flush the session, or else changes won't be saved to the database. How about some help from Castle here?
Option two: automatic [Transaction] wrapper
Castle's Automatic Transaction Facility allows you to decorate methods as [Transaction] and it will automatically wrap a transaction around it. IoC registration becomes simpler:
And using it:
What are we doing here?
- We decorate methods with [Transaction] (remember to make them virtual!) instead of manually opening/closing transactions. I put this attribute on the service method itself, but you could put it anywhere -- for example on a CQRS command handler, or domain event handler etc. Of course this requires that the class with the [Transactional] attribute is instantiated via Windsor so it can proxy it.
- Nothing in the NHibernateFacility needs to be registered per WCF operation lifestyle. I believe this is because NHibernateFacility uses the CallContextSessionStore by default, which in a WCF service happens to be scoped to the duration of a WCF operation.
- Callers must not dispose the session -- that will be done by castle after the transaction is commited. To discourage this I am using it as a method chain -- sessionManager.OpenSession().Save() etc.
- Inject ISessionManager, not ISession. The reason for this is related to transactions: NHibernateFacility must construct the session after the transaction is opened, otherwise it won't know to enlist it. (NHibernateFacility knows about ITransactionManger, but ITransactionManager doesn't know about NHibernateFacility). If your service depends on ISession, Castle will construct the session when MyWcfService and its dependencies are resolved (time of object creation) before the transaction has started (time of method dispatch). Using ISessionManager allows you to lazily construct the session after the transaction is opened.
- In fact, for this reason, ISession is not registered in the container at all -- it is only accessible via ISessionManager (which is automatically registered by the NHibernate Integration Facility).
This gives us an NHibernate session per WCF operation, with automatic transaction support, without the need for any additional code.
Update: there is one situation where this doesn't work -- if your WCF service returns a stream that invokes NHibernate, or otherwise causes NHibernate to load after the end of the method, this doesn't work. A workaround for these methods is simply to omit the [Transaction] attribute (hopefully you're following CQS and not writing the DB in your query!).
Update 2: See my follow up post with further discussion about this technique here.