Published articles on other web sites*

Published articles on other web sites*

(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 a session is fast but 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.

Strategies




Trying to balance to cost of creating a sessionfactory and the cost of an open database connection 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 intented). 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 opened 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 opens a 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();

session.BeginTransaction();

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.

The manager always opens a transaction on a session. Making persisting object an explicit operation. The CommitSession helper method handles the core of that. Using transactions is good, even nicer because nHibernate takes care of them. Making persisting object explicit is also good from an organizational point of view. We have several developers working on this project, not everybody that familiar with nHibernate. The things nHibernate (tries to) persist implicitly, when not using transactions, can be quite surprising.

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.

No comments:

Post a Comment

Related Posts Plugin for WordPress, Blogger...