Logo

NHibernate

The object-relational mapper for .NET

Effective NHibernate Session management for web apps

In this post I’ll describe a mechanism to manage nhibernate session following the widely known patter “session-per-request”.

Introduction

The session-per-request pattern is very well defined and widely used; as follows

A single Session and a single database transaction implement the processing of a particular request event (for example, a Http request in a web application).

What do we have currently?

The first thing you will notice when talking about nhibernate session management is a little interface inside NHibernate;

public interface ICurrentSessionContext
{
    ISession CurrentSession();
}

The only purpose of the implementors is to store and retrieve the current session from somewhere. This class is used by the SessionFactory of nhibernate when calling the method GetCurrentSession().

There are lot of implementations of ICurrentSessionContext but for web the two more important are:

  • WebSessionContext inside NHibernate (namespace NHibernate.Context)
  • WebSessionContext inside uNhAddIns.Web (namespace Session.Easier)

They are pretty similar but the one inside uNhAddins supports multi-session-factory scenarios.

Where do we init & store the session?

We have an httpmodule in uNhAddins which add handler for the BeginRequest as follows:

Untitleddrawing (2)

The problem

Although the afore mentioned handler does not open a session and a transaction for images or JavaScript files there might be some request to pages that will not talk with the persistence and we don’t want a OpenSession/BeginTransaction there.

Ayende already talked about this in his blog. But the problem is that he only wrote about the Session which is really light to instantiate. The problem is how do we handle the scope of the transaction which is not so light?

Alternative solutions

There are currently three solutions for this problem:

  • Use BeginRequest/EndRequest to open/close the session. Handle the transaction with AOP – attributes. I am not sure but I think this is the case for the AutoTransaction facility of castle.
  • To use Asp.Net MVC ActionFilters to Open/Close the session and the transaction.
  • There is a third which is an antipattern; using implicit transactions for most of the cases and explicit for some others.

The main problem with these approaches is that you need to explicitly put something to say that some piece of code will use a session/transaction. Even if you do it with AOP!

My new solution

My new solution is to store a Lazy<ISession> per request instead of an ISession. Initialize in the first usage and finalize in the EndRequest – only if it was used/opened.

The implementation I’ll show also support multi-session factories.

The ICurrentSessionContext looks as follows:

public class LazySessionContext : ICurrentSessionContext
{
    private readonly ISessionFactoryImplementor factory;
    private const string CurrentSessionContextKey = "NHibernateCurrentSession";

    public LazySessionContext(ISessionFactoryImplementor factory)
    {
        this.factory = factory;
    }

    /// <summary>
    /// Retrieve the current session for the session factory.
    /// </summary>
    /// <returns></returns>
    public ISession CurrentSession()
    {
        Lazy<ISession> initializer;
        var currentSessionFactoryMap = GetCurrentFactoryMap();
        if(currentSessionFactoryMap == null || 
            !currentSessionFactoryMap.TryGetValue(factory, out initializer))
        {
            return null;
        }
        return initializer.Value;
    }

    /// <summary>
    /// Bind a new sessionInitializer to the context of the sessionFactory.
    /// </summary>
    /// <param name="sessionInitializer"></param>
    /// <param name="sessionFactory"></param>
    public static void Bind(Lazy<ISession> sessionInitializer, ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        map[sessionFactory] = sessionInitializer;
    }

    /// <summary>
    /// Unbind the current session of the session factory.
    /// </summary>
    /// <param name="sessionFactory"></param>
    /// <returns></returns>
    public static ISession UnBind(ISessionFactory sessionFactory)
    {
        var map = GetCurrentFactoryMap();
        var sessionInitializer = map[sessionFactory];
        map[sessionFactory] = null;
        if(sessionInitializer == null || !sessionInitializer.IsValueCreated) return null;
        return sessionInitializer.Value;
    }

    /// <summary>
    /// Provides the CurrentMap of SessionFactories.
    /// If there is no map create/store and return a new one.
    /// </summary>
    /// <returns></returns>
    private static IDictionary<ISessionFactory, Lazy<ISession>> GetCurrentFactoryMap()
    {
        var currentFactoryMap = (IDictionary<ISessionFactory,Lazy<ISession>>)
                                HttpContext.Current.Items[CurrentSessionContextKey];
        if(currentFactoryMap == null)
        {
            currentFactoryMap = new Dictionary<ISessionFactory, Lazy<ISession>>();
            HttpContext.Current.Items[CurrentSessionContextKey] = currentFactoryMap;
        }
        return currentFactoryMap;
    }
}

The new HttpModule is:

public class NHibernateSessionModule : IHttpModule
{
    private HttpApplication app;
    private ISessionFactoryProvider sfp;

    public void Init(HttpApplication context)
    {
        app = context;
        sfp = (ISessionFactoryProvider) 
                  context.Application[SessionFactoryProvider.Key];
        context.BeginRequest += ContextBeginRequest;
        context.EndRequest += ContextEndRequest;
    }

    private void ContextBeginRequest(object sender, EventArgs e)
    {
        foreach (var sf in sfp.GetSessionFactories())
        {
            var localFactory = sf;
            LazySessionContext.Bind(
                new Lazy<ISession>(() => BeginSession(localFactory)), 
                sf);
        }
    }

    private static ISession BeginSession(ISessionFactory sf)
    {
        var session = sf.OpenSession();
        session.BeginTransaction();
        return session;
    }

    private void ContextEndRequest(object sender, EventArgs e)
    {
        foreach (var sf in sfp.GetSessionFactories())
        {
            var session = LazySessionContext.UnBind(sf);
            if (session == null) continue;
            EndSession(session);
        }
    }

    private static void EndSession(ISession session)
    {
        if(session.Transaction != null && session.Transaction.IsActive)
        {
            session.Transaction.Commit();
        }
        session.Dispose();
    }

    public void Dispose()
    {
        app.BeginRequest -= ContextBeginRequest;
        app.EndRequest -= ContextEndRequest;
    }
}

You can see here how we bind to the Lazy to the current context and the initializer. The BeginSession method initializes a session and a transaction.

The UnBind method returns a session only if the Lazy was initialized. If it returns something we properly commit the transaction and dispose the session.

The ISessionFactoryProvider is:

public interface ISessionFactoryProvider
{
    IEnumerable<ISessionFactory> GetSessionFactories();
}

and the SessionFactoryProvider is just an store for the constant:

public class SessionFactoryProvider
{
    public const string Key = "NHibernateSessionFactoryProvider";
}

I didn't write an implementation for ISessionFactoryProvider because I’m using castle typed factories.

The IWindsorInstaller for castle looks as follows:

public class NHibernateInstaller : IWindsorInstaller
{
    #region IWindsorInstaller Members

    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Component.For<ISessionFactory>()
                               .UsingFactoryMethod(k => BuildSessionFactory()));

        container.Register(Component.For<NHibernateSessionModule>());

        container.Register(Component.For<ISessionFactoryProvider>().AsFactory());
        
        container.Register(Component.For<IEnumerable<ISessionFactory>>()
                                    .UsingFactoryMethod(k => k.ResolveAll<ISessionFactory>()));

        HttpContext.Current.Application[SessionFactoryProvider.Key]
                        = container.Resolve<ISessionFactoryProvider>();
    }

    #endregion

    public ISessionFactory BuildSessionFactory()
    { 
        var config = new Configuration().Configure();
        //your code here :)
        return config.BuildSessionFactory();
    }
}

The only thing you have to do in NHibernate is to tell which is the CurrentSessionContextClass as follows:

configuration.Properties[Environment.CurrentSessionContextClass]
    = typeof (LazySessionContext).AssemblyQualifiedName;

Working with multiples session factories

When working with multiples session factories, the way to go is to name your components in the container:

container.Register(Component.For<ISessionFactory>()
                            .UsingFactoryMethod(...)
                            .Named("MySf1"));

Then you can start naming your Daos and explicitly overriden whenever they are injected:

container.Register(Component.For(typeof(IDao<>))
                            .ImplementedBy(typeof(Dao<>))
                            .ServiceOverrides(ServiceOverrides.ForKey("sessionFactory").Eq(MySf1))
                            .Named("SalesDao"));

container.Register(Component.For(typeof(IMyService<>))
                            .ImplementedBy(typeof(MyService<>))
                            .ServiceOverrides(ServiceOverrides.ForKey("dao").Eq(MyDao1)));

Another way is to have a factory like this one:

public interface IDaoFactory
{
     IDao<T> GetSalesDao();
     IDao<T> GetCMSDao();
}

This will work out of the box with castle typed factories, following the pattern Get[Dao component name]. With other containers you will have to implement the interface.

Remember also that NHibernate lets you name your session factory through the configuration, that is sometimes useful.

How to use the ISession from my code?

The same way I do since long time ago;

public class Dao<T> : IDao<T>
{
    private readonly ISessionFactory sessionFactory;

    public Dao(ISessionFactory sessionFactory)
    {
        this.sessionFactory = sessionFactory;
    }

    public void Save(T transient)
    {
        sessionFactory.GetCurrentSession().Save(transient);
    }

    //Other methods

Injecting the ISessionFactory instead an ISession has the following advantages:

  • It is very handy to use stateless session or a short lived session in some methods for some queries through OpenStatelessSession/OpenSession
  • The lifestyle of the Dao is not tied to the Session.  It could be even singleton.

Other session managements

In unhaddins there are various contexts;

  • PerThread
  • ConversationalPerThread
  • Web

And ways to hook such contexts;

  • In winforms; for use with the pattern Conversation per Business transaction.
  • In WCF; an OperationBehavior
  • In web an httpmodule

It is up to you to choose the combination. For instance when doing WCF that will run inside iis, you can use the operationbehavior + web. But when you do WCF out of IIS, you can use OperationBehavior + PerThread.

The important thing about this is that your DAOs are exactly the same despite the management you use.

Notes

-For non-.Net 4 projects; as Richard Birkby points out you can use the Lazy<T> inside Mono.

Finally

I hope you find this useful.

This code is not going to be in uNhAddins for now. You can copy&paste all the code from this gist.


Posted Wed, 02 March 2011 06:09:00 PM by jfromainello
Filed under: SessionFactory, Session, Asp.Net, ASP.NET MVC

comments powered by Disqus
© NHibernate Community 2024