left-icon

NHibernate Succinctly®
by Ricardo Peres

Previous
Chapter

of
A
A
A

CHAPTER 10

Using NHibernate in Web Contexts

Using NHibernate in Web Contexts


ASP.NET Web Forms

NHibernate is a general purpose persistence framework. It can be used in almost any .NET environment you can think of (but probably excluding Silverlight). However, in the case of ASP.NET web applications, there are some things that you might want to know to help your development process.

First, keep in mind that you will only ever need one session factory per application. Feel free to store it in a static variable; for example, in the Global class.

Second, one pattern that is common in web development is Open Session In View, which basically states that we should open a single session for each request, store it in an easy-to-find location, and keep it for the duration of the request, after which we can dispose of it. It is advisable that you follow this pattern. Let’s see exactly how to do this.

NHibernate has the concept of the current session context. We are responsible for creating a session and binding it to this session context so that we can retrieve it later—even from different contexts (classes and methods). The session context implementation is specified by configuration, XML, or loquacious, and NHibernate includes the following implementations:

NHibernate Session Context Storage

Name

Purpose

CallSessionContext/call

The session is stored in the .NET Remoting CallContext class

ThreadStaticSessionContext/thread_static

The session is stored in a ThreadStatic variable

WcfOperationSessionContext/wcf_operation

The session is stored in the WCF OperationContext instance

WebSessionContext/web

The session is stored in the current HttpContext

Of course, for web applications, you would normally use the WebSessionContext. Here’s how to set it up in XML and loquacious configuration:

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">

  <session-factory>

    <!-- … -->

    <property name="current_session_context_class">web</property>

  </session-factory>

</hibernate-configuration>

Configuration cfg = BuildConfiguration()

.DataBaseIntegration(db =>

{

  //…

})

.CurrentSessionContext<WebSessionContext>();

To properly implement the Open Session In View pattern, we need to hook up the BeginRequest, EndRequest, and Error HttpApplication events. This can be done in a module (IHttpModule) or in the Global class. Here’s how it should look:

public CurrentSessionContext SessionFactory { get; set; }  

protected void OnBeginRequest(Object sender, EventArgs e)

{

  ISession session = this.SessionFactory.OpenSession();

  session.BeginTransaction();

               

  CurrentSessionContext.Bind(session);

}

 

protected void OnEndRequest(Object sender, EventArgs e)

{

  this.DisposeOfSession(true);

}

 

protected void OnError(Object sender, EventArgs e)

{

  this.DisposeOfSession(false);

}

 

protected void DisposeOfSession(Boolean commit)

{

  ISession session = CurrentSessionContext.Unbind(this.SessionFactory);

 

  if (session != null)

  {            

    if ((session.Transaction.IsActive == true) && (session.Transaction.WasCommitted == false

     && (session.Transaction.WasRolledBack == false))

    {

      if (commit == true)

      {

        session.Transaction.Commit();

      }

      else

      {

        session.Transaction.Rollback();

      }

 

      session.Transaction.Dispose();

    }

 

    session.Dispose();

  }

}


Tip: Add a reference to the NHibernate.Context namespace.

Here’s what it does:

  1. When the BeginRequest event is raised, a session is created and stored in the current session context (CurrentSessionContext.Bind()), and a transaction is started.
  2. When the EndRequest is raised, the session is retrieved (CurrentSessionContext.Unbind()) and disposed of, and the transaction is committed if it wasn’t already.
  3. If an Error occurs, the session is also disposed of and the active transaction is rolled back.

At any point, you can get the current session from the ISessionFactory.GetCurrentContext() method, but you need to have a pointer to the session factory for that:

ISession session = sessionFactory.GetCurrentSession();

If at any point in your code you need to cause the current transaction to roll back, just call the Rollback method of the ITransaction instance:

sessionFactory.GetCurrentSession().Rollback();

The code in the EndRequest handler will detect that the session was already rolled back and it won’t do it again.

ASP.NET MVC

ASP.NET MVC uses an attribute-based approach for injecting cross-cutting behavior before and after action methods execute. This is generally referred to as Aspect-Oriented Programming (AOP) and, in MVC, it is called a filter. With this mechanism, you can easily start a session for the request and wrap each method in a transaction.

The following is the code for a simple filter that starts an NHibernate session (if it wasn’t already started) and a transaction at the beginning of an action method and, after it finishes, commits the transaction and disposes of the session:

public class TransactionAttribute : ActionFilterAttributeIExceptionFilter

{

  public override void OnActionExecuting(ActionExecutingContext filterContext)

  {

    ISessionFactory sessionFactory = DependencyResolver.Current.GetService<ISessionFactory>();

  

   if (CurrentSessionContext.HasBind(sessionFactory) == false)

    {

      CurrentSessionContext.Bind(sessionFactory.OpenSession());

    }

    if ((sessionFactory.GetCurrentSession().Transaction.IsActive == false))

    {

      sessionFactory.GetCurrentSession().BeginTransaction();

    }

  }

 

  public override void OnActionExecuted(ActionExecutedContext filterContext)

  {

    ISessionFactory sessionFactory = DependencyResolver.Current.GetService<ISessionFactory>();

    if ((sessionFactory.GetCurrentSession().Transaction.IsActive == true)

     && (sessionFactory.GetCurrentSession().Transaction.WasCommitted == false

     && (sessionFactory.GetCurrentSession().Transaction.WasRolledBack== false))

    {

      sessionFactory.GetCurrentSession().Transaction.Commit();

    }

    CurrentSessionContext.Unbind(sessionFactory).Dispose();

  }

 

  public void OnException(ExceptionContext filterContext)

  {

    ISessionFactory sessionFactory = DependencyResolver.Current.GetService<ISessionFactory>();

 

    if ((sessionFactory.GetCurrentSession().Transaction.IsActive == true

    && (sessionFactory.GetCurrentSession().Transaction.WasCommitted == false

    && (sessionFactory.GetCurrentSession().Transaction.WasRolledBack== false))

    {

      sessionFactory.GetCurrentSection().Transaction.Rollback();

    }

    CurrentSessionContext.Unbind(sessionFactory).Dispose();

  }

}

Tip: This example assumes that you have registered the ISessionFactory with the DependencyResolver.

WCF Web Services

When you use NHibernate to retrieve data in a web service, you must be aware of the following: If you use lazy loading for some property, reference or collection, and you leave the web method without explicitly loading everything, when your entity is serialized it will most likely not have access to the session from which it came. This is because it should have been disposed and so an exception will occur. Take the following code as an example of a bad implementation:

[OperationContract]

public IEnumerable<Product> GetProducts()

{

  using (ISession session = this.sessionFactory.OpenSession())

  {

    return (session.Query<Product>().ToList());

  }

}

If the Product entity is configured with any lazy properties, it will crash as soon as it is serialized because, by then, its originating session is closed.

A better implementation relies on a Data Transfer Object (DTO) that represents the data that we need to send, which typically is a subset or a transformation of the data exposed by the entity class:

[OperationContract]

public IEnumerable<Product> GetProducts()

{

  using (ISession session = this.sessionFactory.OpenSession())

  {

    return (session.Query<Product>().Select(x => new ProductDTO

    { Name = x.Name, Price = x.Price, Orders = x.OrderCount }).ToList());

  }

}

You can also use the same session context as you would normally use in a web application (the WCF session context is complex to set up and really doesn’t introduce anything special), and also stick with the Open Session In View; you just have to make sure your WCF service is in ASP.NET Compatibility Mode (see http://msdn.microsoft.com/en-us/library/ms752234.aspx).

Scroll To Top
Disclaimer
DISCLAIMER: Web reader is currently in beta. Please report any issues through our support system. PDF and Kindle format files are also available for download.

Previous

Next



You are one step away from downloading ebooks from the Succinctly® series premier collection!
A confirmation has been sent to your email address. Please check and confirm your email subscription to complete the download.