(Yet another) nHibernate sessionmanager for ASP.NET (MVC) | Peter van Ooijen

:

Working with a nHibernate session in an asp.net web application is a subject to many a blog post, forum question or overflowing stack. Having already made a contibution before, I am nevertheless going to add another go at it combining all I’ve met and trying to find a way to meet the objections to the different approaches.

The problem

Accessing a database via nHibernate takes four steps.

  1. Create a sessionfactory. 
  2. Open a session.
  3. Access data
  4. Close the session

Creating a sessionfactory is a very costly process, don’t do this to often. Opening an ADO connection results in an open connection to the database. Database connections are a costly external resource. Despite session pooling connections should be closed as soon as possible. An nHibernate session wraps up the ADO connection; it handles the opening and closing. Creating an nHibernate session is fast and will not be costly until the moment the ADO connection is opened. Nevertheless this is not a license to spill.

Strategies

Trying to balance to cost of creating a sessionfactory and the waste of creating sessions which will not be used I’ve found several ways to manage the session.

  1. Do it yourself.  Create the factory whenever the app needs data and fiddle on from there optimized to the occasion. Needless to say that’s not going to work unless you have a very simple app.
  2. An action in a (MVC) controller. Using an attribute on the action a factory and session are created on the start of an action and disposed when the action is finished. This might look appealing but is a very poisonous recipe (pun intended). After the action is finished the page will be rendered. In case there are any lazy properties on the page they cannot be read because the session has already closed.
  3. The Web-HttP request. At the start of the request the session is opened, at the end it closed. This is by far the favored solution by almost everybody. The down side is that, in the default implementation, a session is created on every request. Also when no database access will take place, even when requesting an image or the like.

Objectives

The session manager presented here is controlled by web requests and has some extra’s

  • Only create a factory and  session when one is actually needed.
  • Can also be used in a non web environment. Like Visual studio to run in an unit test.

The first extra is done by making the session a lazy property. The second one by setting the NH-session context based on the app’s context.

The (a)  solution

So far for the theory, here’s the code. It’s using fluent nHibernate for setting the configuration. What else ?

public class SessionManager

{

    private static ISessionFactory Factory { get; set; }

    public static string ConnectionString { get; set; }

 

    static SessionManager()

    {

        ConnectionString = @”Data Source=Bunocephalus;Initial Catalog=Epos4;Integrated Security=true”;

 

    }

 

    private static ISessionFactory GetFactory<T>() where T : ICurrentSessionContext

    {

        return Fluently.Configure().

            Database(MsSqlConfiguration.MsSql2008.

 

#if DEBUG

                            ShowSql().

#endif

                            ConnectionString(c => c.Is(ConnectionString))).

            Mappings(m => m.FluentMappings.AddFromAssembly(Assembly.GetExecutingAssembly())).

             CurrentSessionContext<T>().

            BuildSessionFactory();

    }

 

 

    public static ISession CurrentSession

    {

        get

        {

            if (Factory == null)

                Factory = HttpContext.Current != null

                                ? GetFactory<WebSessionContext>()

                                : GetFactory<ThreadStaticSessionContext>();

            if (CurrentSessionContext.HasBind(Factory))

                return Factory.GetCurrentSession();

            ISession session = Factory.OpenSession();

            CurrentSessionContext.Bind(session);

            return session;

        }

    }

 

    public static void CloseSession()

    {

        if (Factory == null)

            return;

        if (CurrentSessionContext.HasBind(Factory))

        {

            ISession session = CurrentSessionContext.Unbind(Factory);

            session.Close();

        }

    }

 

    public static void CommitSession(ISession session)

    {

        try

        {

            session.Transaction.Commit();

        }

        catch (Exception)

        {

            session.Transaction.Rollback();

            throw;

        }

    }

}

 

A short walkthrough:

The public property CurrentSession is the only thing your other code is interested in. It will be used in your repositories.

public virtual T Get(int id)

{

    var session = SessionManager.CurrentSession;

    var domainObject = session.Get<T>(id);

    return domainObject;

}

 

Which should speak for itself

The factory is a private member. CurrentSession checks if the factory is already created. When not it passes a type parameter, which depends on the context, to the GetFactory method which actually instantiates the factory. In case the manager is running in a web context the factory is given a WebSessionContext else a ThreadStaticContext. The latter working well when running a test. A sql2008 database is assumed with the table mappings in the same assembly. I leave it up to you to make this configurable. In case you really need it Smile

The actual session is managed by nHibernate itself in the sessioncontext.  CurrentSession checks that to try to get the session. When none is available it creates one and binds it to the context. Now all code in one webrequest will share one and the same session.

When closing the session the code should check for a factory. Perhaps the current webrequest never did any database access and thus never opened a session. Close session unbinds the session from the context and closes it.

In several solutions, including the one in the original version of this post, the sessionmanager handles (part of) transaction management. The most extreme version starts a transaction when opening the session and commits that when closing the session. Which could look nice at first sight. But it will fire back at you. We have several developers working on this project, not everybody that familiar with nHibernate. The things nHibernate (tries to) persist implicitly can be quite surprising, resulting in baffled co-workers. Making persisting object explicit in transactions is also good from an organizational point of view. Which  strategy to follow is beyond the scope of this post. The CommitSession helper method handles the core of that.

Some checks in this code might seem superfluous. But this way it does survive some very nasty crashes doing heavy ajax stuff in VS always clearing all database connections. The last thing you need there is a conflict with your sql server.

Note this part in configuring the factory

#if DEBUG

ShowSql().

#endif

This results in all nHibernate generated sql to show up in your test.

NHunit

The full picture

The database part of the solution has two projects. One are the repositories, the other one is this nHibernatemanager. Which contains

  • The sessionmanager we discussed
  • An HttpModule to load the manager in a website
  • The mappings of the domain model
  • A validator, not discussed here

Solution

The HttpModule to wrap up the manager:

public class CurrentSessionWebModule : IHttpModule

{

    public void Init(HttpApplication context)

    {

        SessionManager.ConnectionString = ConfigurationManager.ConnectionStrings[“EposDB”].ConnectionString;

        context.EndRequest += (sender, e) => SessionManager.CloseSession();

    }

 

    public void Dispose()

    {

 

    }

}

Closing the session is hooked into EndRequest. In the “usual” solution opening the session is hooked into the BeginRequest. Here this is not needed, the session will be opened on demand by any code actually needing a session.

Loading is configured in web.config.

<httpModules>

    <add name =CurrentSessionWebModule type=Epos4.Database.NhibernateManager.CurrentSessionWebModule, NhibernateSessionManager, Version=1.0.0.0, Culture=neutral/> 

</httpModules>

 

This is the only mentioning of the manager needed by the website. To get to data it uses the repositories, the repositories reference the actual code of the manager.

That’s it. We have pushed the management of a session away into one specific corner, have a session at our disposal when needed and are not hindered by a performance or resource penalty when not needing a session. Nothing original, just works like a charm. And any remarks or suggestions are more than welcome.

<update>Your comments have been used to update this post where it was plain wrong. The original version assumed an nHibernate session always opens a connection to the database. It does not. Thank you for your feedback</update>