Saturday, April 17, 2010

NHibernate session management

When you start working with NHibernate, you will see code examples like this one:

int userID = 1234;
using( ISession session = sessionFactory.OpenSession() )
using( session.BeginTransaction() ) {
    User user = session.Get<User>(userID); 
    session.Transaction.Commit();
}

You get a session object, start a transaction and finally commit that. This is fine when you work in a simple one-layer application or you are OK with tight coupling. When you have multiple layers, or working with binding like in WPF, things get complicated.

Session management strategies

  1. Session per application
    Simplest approach. Just one session for the whole application, typically returned by a singleton.
    Problem: Async calls can interfere with each other, cache size grows, unrecoverable exceptions, stale data
  2. Session per screen
    New session for every form/view/… There are multiple concurrent sessions.
    Problem: You can keep a screen opened for a long time, so stale data is expectable.
  3. Session per data access
    Every single data access uses a new session object.
    Problem: Lazy loading won’t work, problem with 1st level cache…
  4. Session per use-case
    You create a new session for every use-case. There will be concurrent sessions.

Firs try: Following examples – Session per data access

I’ve created an application with the simplest solution possible. I followed the examples and whenever I tried to access data, I created a session and a transaction. Everything was fine, until I tried to employ lazy loading. It simply did not work. There was no open sessions the lazy loader proxy could have used.

Second try: Session per application

I modified the application. I created a singleton that returned a session object. Every single data access used this session. The application worked fine – for a while. I used SQLite as DBMS, and SQLite has not the best threading support. So I got exceptions randomly. Sometimes a control was empty, sometimes it was filled with data. Exceptions happened about 1 out of 15 tries. I suspected a threading issue and it seems it was.

Third try: Session per use-case

I had to implement a session per use-case (business transaction) solution to solve the problems above. A business transaction is a user-initiated action involving multiple complicated operations (steps). All operations (and the transaction itself) are either successful or unsuccessful. The state of the involved members are well-defined and consistent at the end of the transaction.

We have to be able to keep alive the NHiberanate ISession object over multiple operations. The solution is the unit of work pattern. It encapsulates the ISession object, so we can pass it around and implement the IDisposable pattern. The class implementing the unit of work pattern can expose ISession functionality. See the example UML class diagram below.

Illustration: Unit of Work 

This is an example of calling the UnitOfWork object instance:

using (UnitOfWork unitOfWork = database.CreateUnitOfWork())
{
    PropertiesRepository propertiesRepository = new PropertiesRepository(unitOfWork);
    properties = propertiesRepository.Current;
}

You can use a simple factory (it is the database object here) to create a new UnitOfWork instance. This instance should be passed to all the classes accessing NHibernate functionality. This is important, so design your data access and manipulating classes according to this constraint!

Link: Rich-client NHibernate session management
Link: Unit of Work