- Posted by liammclennan on April 20, 2009
UPDATE: Don't use my SessionPerRequest filter. Apparently NHibernate should always have an explicit transaction. For the record, Sharp Architecture disagrees so maybe the jury is still out?
Session-per-request is an ORM pattern that means a new session is created for each request and terminated at the end of the request. I have also implemented a transaction-per-request option, by which I mean that a transaction is created for each request and committed (or rolled back) at the end of the request. Using transaction-per-request I can read, insert update and delete as much as I like during the action execution and at the end NHibernate will synchronize the changes to the database.
Because I am a big fan of simplicity I have chosen to implement these patterns using plain Asp.Net MVC action filters. This is actually not a bad approach because the choice between readonly or persistent session must be made for each request anyway, therefore there is no point hiding this decision.
Here is my Session-Per-Request implementation:
/// <summary>Use this filter for read-only actions.</summary>
public class SessionPerRequest : ActionFilterAttribute
{
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// use this method instead of OnResultExecuted if the session does not
// need to be open for the view rendering.
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
NHibernateHelper.DisposeSession();
}
}
This filter simply handles the disposal of the session at the end of the request. I do not have to explicitly open the session because this is handled elsewhere the first time that the session is accessed. This filter can be used like this:
[SessionPerRequest]
public ActionResult Index()
{
// readonly code here
return View();
}
For actions that need to persist state changes a different filter is required. The TransactionPerRequest filter opens a transaction before the action is executed and commits the transaction when the view has been rendered.
/// <summary>
/// Use this filter for actions that need to persist state.
/// </summary>
public class TransactionPerRequest : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
NHibernateHelper.BeginTransaction();
}
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// use this method instead of OnResultExecuted if the transaction / session
// does not need be open for the view rendering.
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception == null)
NHibernateHelper.CommitTransaction();
NHibernateHelper.DisposeSession();
}
}
Usage of TransactionPerRequest is the same as for SessionPerRequest. Both of these action filters defer NHibernate access to a class called NHibernateHelper. NHibernate is a simple wrapper that manages access to an ISessionFactory singleton, and instances of ISession.
public class NHibernateHelper
{
private static ISessionFactory sessionFactory;
/// <summary>
/// SessionFactory is static because it is expensive to create and is therefore at application scope.
/// The property exists to provide 'instantiate on first use' behaviour.
/// </summary>
private static ISessionFactory SessionFactory
{
get
{
if (sessionFactory == null)
{
sessionFactory = new Configuration().Configure().AddAssembly(typeof(Models.Store).Assembly).BuildSessionFactory();
}
return sessionFactory;
}
}
public static ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
public static void DisposeSession()
{
var session = GetCurrentSession();
session.Close();
session.Dispose();
}
public static void BeginTransaction()
{
GetCurrentSession().BeginTransaction();
}
public static void CommitTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Commit();
}
public static void RollbackTransaction()
{
var session = GetCurrentSession();
if (session.Transaction.IsActive)
session.Transaction.Rollback();
}
}
The GetCurrentSession method ensures that an existing session is returned if one exists, otherwise a new session is created. By using NHibernate's CurrentSessionContext singleton the scoping of sessions is controlled by the NHibernate current_session_context_class property. In my web application I configure the property like this:
web
In my test project I will change the value of the current_session_context_class property to call so that NHibernateHelper can function without a HttpContext.
I am sure there are better ways to do what I am trying to achieve, so if you see any areas for improvement please leave a comment.