(Yet another) nHibernate sessionmanager for ASP.NET (MVC)

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>

This entry was posted in ASP.NET, Data. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://erasmusrios.webnode.com/ AaronRiggs

    Opening a session does not necessarily result in opening a db connection. NHibernate only opens them when necessary, and quickly returns them to the pool. By the way great information.

  • Abc

    Could you please post some working sample project code
    which includes mvc and fluent nhibernate and the above session manager

  • http://boxbinary.com Alex Norcliffe

    Looks like Disqus thought I put in an “ISessionFactory” html tag which it nicely auto-closed for me…

  • http://boxbinary.com Alex Norcliffe

    Nice idea; I have a couple of comments:

    - The CurrentSession property implementation is not thread-safe. For example, it would be safer to have the Factory property’s getter handle the lazy instantiation of an underlying field; it’s one of the reasons to go with a Property rather than a “dumb” field. You can have a backing field of type Lazy which will handle the ‘laziness’ for you in a thread-safe manner.

    - Re the discussion around “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.” I’m glad you didn’t opt for that :) There are arguments for having your ISessionFactory survive as a singleton for the life of the application so that it can sufficiently manage your second-level cache.

  • Anonymous

    Every request creates a session.
    This is not a problem but a design choice. 
    To be more precise:
    Every request creates (not more than) ONE session

  • Harry McIntyre

    sorry i should have said “every-request-creates-a-session problem…”

  • Jesse Williamson

    The fact that your UI is unaware of it’s eventual communication with the DB does negate the fact that it is happening. The ultimate outcome here is that the UI is generating queries on the DB, whether it knows it doesn’t mean it isn’t happening. IMO, yes, you should only be communicating to your UI via viewmodels.

  • Anonymous

    Yes, having Viewmodels would solve that. Does an architecture really need ViewModels for every part of the UI to be good (enough..) ? It depends…

  • http://thomasfreudenberg.com Thomas Freudenberg

    > A good architecture is important but you cannot ignore the inner workings of the parts.

    I second Jesse’s point.
    A good architecture would only transfer ViewModels to the presentaion layer, not the objects it retrieved from the repository. 

  • Pingback: The Morning Brew - Chris Alcock » The Morning Brew #946

  • Anonymous

    Just creating the factory in a static constructor could make sense. As long as the connection string is set. But.. setting the connection string would fire off the static constructor first. Which would use the default value for the connection string.

    I can use as many repositories as I like. All of them use the same session as they all use the same manager. It’s one request, one session. For reading and writing. Which is my transaction strategy. All updates within one request either get committed or all fail in a rollback. All data is read in the same session.

  • Ramon Smits

    Why don’t you just create the factory in a static constructor? Your code now checks if Factory is set each time you call CurrentSession or CloseSession which just should always be initialized. I agree with the other comments in that this results in that you can just have one repository which is pretty limited. Created sessions is very very cheap! It is until you start the transaction that it becomes costly but you should always use the session within a transaction for both reads and writes so how does your transaction strategy looks like? 

  • Anonymous

    ?? I’m not sure if I understand this.
    Yes, coupling a session to an action could result in multiple sessions while only one was needed. The factory is lazy already. It’s not created until the first session is opened. The problem with an action bound lifetime of a session is it closing to early. What am I missing ?

  • Harry McIntyre

    A nifty little solution to the every-action-creates-a-session problem is to register a lazy dynamicproxy instance of ISessionFactory which doesn’t actually make a SessionFactory until one of its members is called

  • Anonymous

    My UI is pulling a BO from a repository. Where and how the repo got that is unknown to the UI, it’s looks like a plain POCO with which it can do nice things. Like displaying Invoice.Customer.MainContact.Birthday. The BO is actually a NH proxy which will fire lazy loading when the birthday was requested. Is my UI speaking to the DB ? Not that it is aware of. But in fact it does, behind the scenes, deep in the layers.
    The code is well layered. The UI only references the repositories, the repositories reference NH and NH does the talking to the DB. The UI has no references to NH nor the NH-manager. Except for the web.config which loads the HTPmodule, which takes care of an available NH session so the BO’s will not lose their magic.
    BO’s managed by NH are nice, but they can turn to lame ducks without an open session underneath. The lazy loading argument may sound weak but it is exactly what went wrong in the code of my co-worker which had used this approach. A good architecture is important but you cannot ignore the inner workings of the parts.

  • Jesse Williamson

    You made the argument that a session per action is bad idea because any lazy fields won’t be able to be populated because the session is closed. Doesn’t that mean your UI layer is speaking to the DB? I would think this should be avoided (don’t you end up in N+1 scenarios this way?). There may be other arguments against a sesttion per action approach, but I think the lazy loading argument is weak because, IMO, having your UI layer interact with the DB in that way should be avoided.

  • Anonymous

    @Rich: This is one (NH) session per request. One size fits all.
    You want multiple factories each with it’s own connection string. Which would boil down to paramerizing (and renaming) CurrentSession and wrapping up a collection of factories instead of just one.
    @Graham,Jason. I could have known NH would (also) handle that for me. So the NH session has a lower cost. But still needs to e managed.
    @Gareth. The UI is not talking to NH at all and has no references. It uses repositories to get/set BO’s. The repositories use NH. The repo’s reference the manager. When running the (web) app al these dll’s will be loaded. The web-config part only hooks the management of the session in the web-request life cycle.

  • Jason Meckley

    As Graham stated, a session != db connection. a session may use 0-N connections depending on how NH is configured. the default connection setting is auto which will open a connection when you call begin transaction and close the connection when commit/rollback is call (it may be when the tx is disposed, not sure since i always dispose right after commit/rollback).

    in any case, open a session and closing a session doesn’t create/open/close a db connection.

  • Gareth

    Shouldn’t you be splitting your app into layers, i.e UI, business objects, data access, and only talking to the nhibernate / data access layer from your business objects, instead of the web page talking directly to nhibernate?

  • Graham

    Opening a session does not necessarily result in opening a db connection. NHibernate only opens them when necessary, and quickly returns them to the pool.

  • RichB

    Have you thought about support for multiple sessions per request?
    For example, you may use a different ISession for report queries (read uncommitted isolation) than you do for the rest of your application, but most requests may need to access both sessions to complete the request.
    Alternatively, it’s good practice to separate (bounded context) your domain plumbing from security-related plumbing such as login/logoff/session management. With each bounded context, you would have different SQL logins for defense in depth and thus different ISessions.

    Your singleton approach appears to prevent the implementation of these best practices.