Logo

NHibernate

The object-relational mapper for .NET

NHibernate Bootstrapper: UnitOfWork and SessionPerRequest

This is the first sample in a series of blogs about NHibernate in ASP.Net environment. This version gives a user new to NHibernate the absolute minimum pieces to start a well architected application. A number of the pieces necessary for an enterprise-capable application are not yet included, but the foundation has been laid. Source code and starter solution described in this post are available at NHibernateBootstrap Download.

The key pieces in this sample are demonstrations of the correct UnitOfWork pattern, using the NHibernate ISession based object with its transaction support. A DataAccessObject / Repository is also introduced that uses features from both styles of implementations and is also intended to work with Query Objects. The Web.Config file includes a starter configuration for NHibernate, using the LinFu proxy. The connection between the web application and NHibernate demonstrates a best practice, where the Session Per Request pattern is initialized in an IHttpModule - RequestHttpModule.cs. This is an implementation base on the ManagedWebSessionContext, and shows how to bind and unbind the session correctly.

This web application sample is not a best practices sample for ASP.Net development, but future blogs will add features, especially where they take advantage of an NHibernate implementation. Some of the implementation of the Person page is not following normal Microsoft recommended methods, and appear to be more complex than necessary. These changes are part of a further foundation and will allow the page to be transformed later where more than 95% of the logic normally encapsulated in the page behind code becomes testable. Initially there will be some references to NHibernate in the Web Application project, but this is not a recommended practice - later modifications will move these references so that they are in the BusinessServices project.

The one other item included in this sample is the Unit Test project. This test project connects to NHibernate using the ThreadStaticSessionContext. Each Test Fixture initializes its own ISession object and uses the same session object for all tests in that fixture. The app.config file for the test project, which configures the NHibernate support, is located in the bin/debug directory.

Solution Architecture

The Bootstrapper uses a multi-project structure and a library at the solution level to hold all the files:

  • NHibernate Bootstrap Solution

    • SharedLibs - a folder containing all external reference dll's and associated files

    • BootstrapperUnitTests Project - holds unit tests

    • Infrastructure Project - holds high level Interface and class files that may be used in multiple projects in the solution

    • DomainModel Project - holds the object model for the solution

    • NHibernateDAO Project - the persistence layer, gets data into and out of the database based on the object model and holds the mapping files

    • DataServices Project - isolates the persistence layer from the rest of the application

    • BusinessServices Project - provides an integration of the business logic with the data and controls the flow of information to and from the presentation layer

    • WebNHibernate Project - provides a web based presentation layer

Jason Dentler has written a series of blogs about using NHibernate, and I am borrowing from his discussion on DAOs, Repositories or Query Objects. The basic setup of the persistence layer uses a DAO-type FindBy approach augmented with support for QueryObjects. It is not my intent to discuss this approach now, but to cover it in a later post.

The purpose of this post is to discuss the ISession object and how to use it directly as a UnitOfWork, without an additional wrapper. The second purpose is to present a recommended solution for the SessionPerRequest architecture for an ASP.Net application.

SessionPerRequest Implementation

The SessionPerRequest implementation is recommended as a best practice to ensure that NHibernate is the most responsive and utilizes the least possible resources in the Web Server environment. The NHibernate.Context.ICurrentSessionContext is discussed in the reference document: NHibernate - Relational Persistence for Idiomatic .NET. The appropriate choices for a web application are either ManagedWebSessionContext or the WebSessionContext. (@Fabio: Can you explain the difference between the 2 - I have reviewed the code and see that ManagedWebSessionContext supports a GetExistingSession() - Does the WebSessionContext need to be explicitly bound and unbound in each request?)

Numerous examples of using ManagedWebSessionContext use the Global.asax to connect the NHibernate context to the web server. This is a bad practice in that it means the web application must have a direct reference to NHibernate.Context in order to work. It is instead recommended that an IHttpModule be created, and that the binding module be in the same project where the NHibernate reference is - in this sample that would be the NHibernateDOA project. This serves to isolate NHibernate from the web application entirely. All that is required is that the web application bin directory have a copy of the NHibernate dlls.

The code for the IHttpModule is shown below:

RequestHttpModule.cs
  1. using System;
  2. using System.Web;
  3. using NHibernate;
  4. using NHibernate.Context;
  5.  
  6. namespace NHibernateDAO
  7. {
  8.     public class RequestHttpModule : IHttpModule
  9.     {
  10.         public void Dispose()
  11.         {
  12.         }
  13.  
  14.         public void Init(HttpApplication context)
  15.         {
  16.             context.BeginRequest += new EventHandler(context_BeginRequest);
  17.             context.EndRequest += new EventHandler(context_EndRequest);
  18.         }
  19.  
  20.         public void context_BeginRequest(Object sender, EventArgs e)
  21.         {
  22.             ManagedWebSessionContext.Bind(HttpContext.Current, SessionManager.SessionFactory.OpenSession());
  23.         }
  24.  
  25.         public void context_EndRequest(Object sender, EventArgs e)
  26.         {
  27.             ISession session = ManagedWebSessionContext.Unbind(
  28.                 HttpContext.Current, SessionManager.SessionFactory);
  29.  
  30.             if (session != null)
  31.             {
  32.                 if (session.Transaction != null && session.Transaction.IsActive)
  33.                 {
  34.                     session.Transaction.Rollback();
  35.                 }
  36.                 else
  37.                     session.Flush();
  38.  
  39.                 session.Close();
  40.             }
  41.         }
  42.     }
  43. }
 

The use of the ManagedWebSessionContext is fairly straight forward. It is important to note that this file has a reference to System.Web, which should be the only reference to this library outside of the the WebApplication itself. It is undesirable to reference the System.Web library external to the web application project as it will make testing of those components with that reference very difficult.

The context_EndRequest also contains a Rollback call, which will ensure that any pending transactions are terminated without writing to the database if your application has an unhandled error.

In order for the custom IHttpModule to be active in the web server it must be registered in the Web.Config file:

Register RequestHttpModule
  1.   <httpModules>
  2.     <add name="RequestHttpModule" type="NHibernateDAO.RequestHttpModule, NHibernateDAO"/>
  3.   </httpModules>
  4.  
  5. </system.web>
  6.  
  7. <system.webServer>
  8.   <modules runAllManagedModulesForAllRequests="true">
  9.     <add name="RequestHttpModule" type="NHibernateDAO.RequestHttpModule, NHibernateDAO"/>
  10.   </modules>
  11. </system.webServer>

I have included the IHttpModule reference twice. The first time is for configurations of the web server using Classic Mode and the second is for IIS 7 Integrated Pipeline. See http://msdn.microsoft.com/en-us/library/ms227673.aspx

The final item necessary to make this work is an addition to the NHibernate configuration file. It is necessary to add the property

NHibernate ContextSession
  1. <property name="current_session_context_class">NHibernate.Context.ManagedWebSessionContext, NHibernate</property>
 

This is shown below in the Web.Config described in the Minimal Configuration section.

ISession UnitOfWork and Transactions

The proper management of database connections is a key component of ensuring a highly responsive web application in ASP.Net. http://nhforge.org/doc/nh/en/index.html#transactions gives some guidance, but does not provide any code samples or say what the recommended practice should be. This lack of direction is partly due to the possible scope of applications for NHibernate in general. However, in an ASP.Net environment the rules are fairly simple. First, you should SessionPerRequest as detailed above. Second, all database access should be wrapped in a transaction and that transaction should be committed when the connection to the database can be released. There is an almost infinite variation of implementations for these requirements in samples on the Internet. Invariably, programmers try to encapsulate the ISession object and transaction in a UnitOfWork class. While many of these samples are not incorrect in there implementation, some are, and many do not provide any additional functionality or save lines of code, when compared to a proper implementation of the bare ISession object. 

First some sample GenericDAO code (again, thanks to Jason Dentler, who provided the basic pattern). The declaration for the class:

Declaration for GenericDAOImp
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using DomainModel;
  6. using NHibernate;
  7.  
  8. namespace NHibernateDAO
  9. {
  10.     public class GenericDAOImpl<TEntity> : IRead<TEntity>, ISave<TEntity> where TEntity : Entity
  11.     {
  12.         public GenericDAOImpl(ISession Session)
  13.         {
  14.             m_Session = Session;
  15.         }
  16.  
  17.         protected readonly ISession m_Session;

A DAO implementation for NHibernate will usually have a GetByID, and this is no exception:

Function GetByID
  1. public TEntity GetByID(Guid ID)
  2. {
  3.     if (!m_Session.Transaction.IsActive)
  4.     {
  5.         TEntity retval;
  6.         using (var tx = m_Session.BeginTransaction())
  7.         {
  8.             retval = m_Session.Get<TEntity>(ID);
  9.             tx.Commit();
  10.             return retval;
  11.         }
  12.     }
  13.     else
  14.     {
  15.         return m_Session.Get<TEntity>(ID);
  16.     }
  17. }

The less normal GetBy query mechanism, is more normally part of a repository architecture, but makes for some very flexible code here:

GetBy Query Functions
  1. public IList<TEntity> GetByCriteria(ICriteria criteria)
  2. {
  3.     if (!m_Session.Transaction.IsActive)
  4.     {
  5.         IList<TEntity> retval;
  6.         using (var tx = m_Session.BeginTransaction())
  7.         {
  8.             retval = criteria.List<TEntity>();
  9.             tx.Commit();
  10.             return retval;
  11.         }
  12.     }
  13.     else
  14.     {
  15.         return criteria.List<TEntity>();
  16.     }
  17. }
  18.  
  19. public IList<TEntity> GetByQueryable(IQueryable<TEntity> queryable)
  20. {
  21.     if (!m_Session.Transaction.IsActive)
  22.     {
  23.         IList<TEntity> retval;
  24.         using (var tx = m_Session.BeginTransaction())
  25.         {
  26.             retval = queryable.ToList<TEntity>();
  27.             tx.Commit();
  28.             return retval;
  29.         }
  30.     }
  31.     else
  32.     {
  33.         return queryable.ToList<TEntity>();
  34.     }
  35. }

The Save function is the last part of the GenericDAOImpl class (note that there is no Delete function, as this capability is only exposed in the specific DAO classes actually requiring that function, rather than as a generic capability):

Save Function
  1. public TEntity Save(TEntity entity)
  2. {
  3.     if (!m_Session.Transaction.IsActive)
  4.     {
  5.         using (var tx = m_Session.BeginTransaction())
  6.         {
  7.             m_Session.SaveOrUpdate(entity);
  8.             tx.Commit();
  9.         }
  10.     }
  11.     else
  12.     {
  13.         m_Session.SaveOrUpdate(entity);
  14.     }
  15.     return entity;
  16. }

 

Each method in the GenericDAO class has 2 paths: one where the caller has wrapped the call in a transaction, and the other where no transaction was specified in the call. (Note: It is not necessary to check m_session.Transaction != null as the transaction will never be null if the ISession instance is valid, and the ISession instance must be valid to use the GenericDAO implementation.) It is left as an exercise for the reader to build a function to query using HQL.

An example test is shown below, first without an explicit transaction and then with a transaction:

PersonSaveTestWithoutTX
  1. [Test]
  2. public void PersonSaveTestWithoutTx()
  3. {
  4.     m_session = SessionManager.SessionFactory.GetCurrentSession();
  5.     Person _person = new Person();
  6.     _person.FirstName = "John";
  7.     _person.LastName = "Davidson";
  8.     _person.Email = "jwdavidson@gmail.com";
  9.     _person.UserID = "jwd";
  10.     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);
  11.     Person newPerson = daoPerson.Save(_person);
  12.  
  13.     Assert.AreEqual(_person, newPerson);
  14.     _person = null;
  15.     newPerson = null;
  16.     daoPerson = null;
  17.     m_session = null;
  18. }
 
Now with a transaction included in the test method:
 
PersonSaveTestWithTX
  1. [Test]
  2. public void PersonSaveTestWithTx()
  3. {
  4.     m_session = SessionManager.SessionFactory.GetCurrentSession();
  5.     Person _person = new Person();
  6.     _person.FirstName = "John";
  7.     _person.LastName = "Davidson";
  8.     _person.Email = "jwdavidson@gmail.com";
  9.     _person.UserID = "jwd";
  10.     PersonDAOImpl daoPerson = new PersonDAOImpl(m_session);
  11.     
  12.     using (var tx = m_session.BeginTransaction())
  13.     {
  14.         Person newPerson = daoPerson.Save(_person);
  15.         tx.Commit();
  16.         Assert.AreEqual(_person, newPerson);
  17.     }
  18.  
  19.     _person = null;
  20.     daoPerson = null;
  21.     m_session = null;
  22. }
 

There are 2 additional lines of code in the sample with a direct call to the transaction. Any other UnitOfWork implementation cannot improve on this and still work correctly.

Minimal NHibernate Configuration

NHibernate needs a program to read the configuration and initialize the SessionFactory. This is called the SessionManager.cs is shown below the configuration and unit test setup code.

The web.config file holds the configuration details as shown below:

NHibernate in Web.Config
  1. <configSections>
  2.   <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler,NHibernate"/>
  3. </configSections>
  4.   <hibernate-configurationxmlns="urn:nhibernate-configuration-2.2" >
  5.   <session-factory name="NHibernate.Bootstrapper">
  6.     <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
  7.     <property name="connection.connection_string">
  8.       Data Source=.\SQLEXPRESS;Initial Catalog=NHibernateBootstrapper;Integrated Security=True;Pooling=True
  9.     </property>
  10.     <property name="show_sql">false</property>
  11.     <property name="adonet.batch_size">10</property>
  12.     <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
  13.     <property name="use_outer_join">true</property>
  14.     <property name="command_timeout">60</property>
  15.     <property name="query.substitutions">true 1, false 0, yes 'Y', no 'N'</property>
  16.     <property name="proxyfactory.factory_class">NHibernate.ByteCode.LinFu.ProxyFactoryFactory, NHibernate.ByteCode.LinFu</property>
  17.     <property name="current_session_context_class">NHibernate.Context.ManagedWebSessionContext, NHibernate</property>
  18.     <property name="generate_statistics">false</property>
  19.   </session-factory>
  20. </hibernate-configuration>
 

The Unit Test project has a similar configuration, but it is stored in an app.config file in the bin directory with some minor changes. The show_sql property is set to true, causing the sql to be output in the NUnit console display. The second change is that the property current_session_context_class is set to ThreadStaticSessionContext. In this case the current context needs it own setup:

Setup / Teardown for UnitTest
  1. private ISession m_session;
  2.  
  3. [TestFixtureSetUp]
  4. public void TestFixtureSetup()
  5. {
  6.     var session = SessionManager.SessionFactory.OpenSession();
  7.     CallSessionContext.Bind(session);
  8. }
  9.  
  10. [TestFixtureTearDown]
  11. public void TestFixtureTeardown()
  12. {
  13.     var session = CallSessionContext.Unbind(SessionManager.SessionFactory);
  14.     if (session != null)
  15.     {
  16.         if (session.Transaction != null && session.Transaction.IsActive)
  17.         {
  18.             session.Transaction.Rollback();
  19.         }
  20.         else
  21.             session.Flush();
  22.  
  23.         session.Close();
  24.     }
  25.  
  26.     session.Dispose();
  27. }

I waited to show the SessionManager.cs code, as it was necessary to understand how the Unit Tests work with the NHibernate DAO classes, in a nearly transparent manner. This SessionManager.cs was adapted from a blog post by Petter Wiggle. The reference post titled ‘NHibernate Session handling in ASP.NET – the easy way’ is one of the examples of how not to implement SessionPerRequest as it uses the Global.asax file for initialization, rather than correctly using an IHttpModule. Now for the code (split into 3 section to ensure that it will print correctly):

SessionManager.cs (Part 1)
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using NHibernate;
  7. using NHibernate.Cfg;
  8.  
  9. namespace NHibernateDAO
  10. {
  11.     public sealed class SessionManager
  12.     {
  13.         private readonly ISessionFactory sessionFactory;
  14.         public static ISessionFactory SessionFactory
  15.         {
  16.             get { return Instance.sessionFactory; }
  17.         }
  18.  
  19.         private ISessionFactory GetSessionFactory()
  20.         {
  21.             return sessionFactory;
  22.         }
  23.  
  24.         public static SessionManager Instance
  25.         {
  26.             get { return NestedSessionManager.sessionManager; }
  27.         }
  28.  
  29.         public static ISession OpenSession()
  30.         {
  31.             return Instance.GetSessionFactory().OpenSession();
  32.         }
SessionMnager.cs (Part 2)
  1. private SessionManager()
  2. {
  3.     if (sessionFactory == null)
  4.     {
  5.         Configuration configuration;
  6.         if (AppDomain.CurrentDomain.BaseDirectory.Contains("UnitTest"))
  7.         {
  8.             configuration = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app.config"));
  9.             log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "app.config")));
  10.         }
  11.         else
  12.         {
  13.             configuration = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config"));
  14.             log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config")));
  15.         }
  16.         //Configuration configuration = new Configuration().Configure();
  17.         if (configuration == null)
  18.         {
  19.             throw new InvalidOperationException("NHibernate configuration is null.");
  20.         }
  21.         else
  22.         {
  23.             configuration.AddAssembly("NHibernateDAO");
  24.             sessionFactory = configuration.BuildSessionFactory();
  25.             if (sessionFactory == null)
  26.                 throw new InvalidOperationException("Call to BuildSessionFactory() returned null.");
  27.         }
  28.     }
  29. }
SessionManager.cs (Part 3)
  1.         class NestedSessionManager
  2.         {
  3.             internal static readonly SessionManager sessionManager = new SessionManager();
  4.         }
  5.     }
  6. }

The SessionManager gets its configuration data from the Unit Test project if "UnitTest" is in the application directory name, otherwise it performs a normal configuration and uses the web.config for the Web Application. It is that easy.

Application Domain Setup

The map file is one method to notify NHibernate of the class to table mapping. Other methods are ConfORM or FluentNHibernate, but the map file was the first mechanism. Remember to set the Build Action for your map files to Embedded Resource so that they are included in the assembly file to be read by the SessionManager in the initialization process.

Person.hbm.xml
  1. <?xml version="1.0" encoding="utf-8" ?>
  2. <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel"
  3.     namespace="DomainModel.Person">
  4.  
  5.   <class name="Person" table="Person" >
  6.     <id name="Id" column="Person_ID">
  7.       <generator class="guid.comb"/>
  8.     </id>
  9.     <property name="FirstName" column="First_Name" />
  10.     <property name="LastName" column="Last_Name" />
  11.     <property name="Email" column="Email" />
  12.     <property name="UserID" column="User_ID"/>
  13.   </class>
  14. </hibernate-mapping>

 

Here you can see the table diagram for the Person table in the SQL Server database.

Person

The class diagram for the Person class shows how the map file uses both the table and the class definition to make NHibernate able to persist an instance of the Person class. Also note that the Person Class inherits from the Entity class.

PersonCls

I have borrowed the Entity class from the implementation of Jason Dentler, described earlier. This Entity class is slightly different from a number of Id providers used by NHibernate persistence classes in that it makes the setter for the Id property ‘protected’, rather than ‘public’. This means that the application code is not able to directly set the Id of a class instance, but that it can only be set by the DAO implementation. As a result the application is forced to use Data Transfer Objects (dto) to communicate from the UI to the persistence classes.

Entity.cs
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5.  
  6. namespace DomainModel
  7. {
  8.     public abstract class Entity
  9.     {
  10.         private Guid m_ID;
  11.  
  12.         public virtual Guid Id
  13.         {
  14.             get { return m_ID; }
  15.             protected set { m_ID = value; }
  16.         }
  17.     }
  18. }

 

The PersonDto class diagram is shown below. The PersonID property maps to the Person.Id property of the persistence class, with all other properties lining up as expected.

PersonDtoCls

The next 3 code snippets show how a dto is used to move data from a persistence class to a dto and then use that dto to populate a DataGrid. The function Get_PersonData fills a persistence class from the database using the ICriteria based GetByCriteria query and then uses a LINQ to Object function to fill the dto. Then the dto is passed to the DataGrid and then it is data bound in the method Fill_gvPerson.

Function Get_PersonData()
  1. public IList<PersonDto> Get_PersonData()
  2. {
  3.     IList<PersonDto> retVal = null;
  4.  
  5.     if (m_session == null)
  6.         m_session = SessionManager.SessionFactory.GetCurrentSession();
  7.  
  8.     ICriteria crit = m_session.CreateCriteria(typeof(Person));
  9.     PersonDAOImpl dao = new PersonDAOImpl(m_session);
  10.  
  11.     IList<Person> people = dao.GetByCriteria(crit);
  12.     retVal = (from person in people
  13.               select new PersonDto
  14.                   {
  15.                       PersonID = person.Id,
  16.                       FirstName = person.FirstName,
  17.                       LastName = person.LastName,
  18.                       Email = person.Email,
  19.                       UserID = person.UserID
  20.                   }).ToList<PersonDto>();
  21.     crit = null;
  22.     dao = null;
  23.     people = null;
  24.  
  25.     return retVal;
  26. }

 

Calling GetPersonData()
  1. if (!Page.IsPostBack)
  2. {
  3.     IList<PersonDto> gvData = Get_PersonData();
  4.     Fill_gvPerson(gvData);
  5. }

 

Fill Grid with DTO Data
  1. public void Fill_gvPerson(IList<PersonDto> data)
  2. {
  3.     gvPerson.DataSource = data;
  4.     gvPerson.DataBind();
  5. }

 

The method _view_OnSaveEditPerson demonstrates the necessary steps to get data out of a dto and then to persist the user inputs. First it checks the value of txtPersonIdValue to determine if the data is from an instance that exists in the persistence layer, or is it a new instance – indicated by an empty value. Next the persistence class Person instance is fill using a GetByID function from the DAO implementation and then the data from the form fields is copy to the Person instance. Finally the Person instance is now saved.

 

 

 

 

 

 

 

 

 

 

 

Save Entity from Form Data
  1. public void _view_OnSaveEditPerson()
  2. {
  3.  
  4.     if (m_session == null)
  5.         m_session = SessionManager.SessionFactory.GetCurrentSession();
  6.  
  7.     Guid editId = new Guid();
  8.     Person editPers = new Person();
  9.  
  10.     if (!string.IsNullOrEmpty(txtPersonIdValue))
  11.         editId = new Guid(txtPersonIdValue);
  12.  
  13.     PersonDAOImpl dao = new PersonDAOImpl(m_session);
  14.     using (var tx = m_session.BeginTransaction())
  15.     {
  16.         if (editId.ToString().Length == 36)
  17.             editPers = dao.GetByID(editId);
  18.         editPers.FirstName = txtFirstNameValue;
  19.         editPers.LastName = txtLastNameValue;
  20.         editPers.Email = txtEmailValue;
  21.         editPers.UserID = txtUserIdValue;
  22.  
  23.         editPers = dao.Save(editPers);
  24.         tx.Commit();
  25.     }
  26.  
  27.     editPers = null;
  28.     dao = null;
  29.     _view_OnRefreshPersonGrid();
  30.     _view_OnClearEditPerson();
  31. }

Next Steps

While the sample solution is useful, it is definitely not enterprise ready. The main problem with this solution is that the Web Application has a knowledge of the persistence layer. The next step is to factor these calls to the BusinessServices project. Doing that will reduce the interdependencies and will increase the overall testability of the full application, as it is almost impossible to properly unit test code in the Web Application itself. Additionally further decoupling can be achieved by ensuring that all calls to the persistence layer are done through the DataServices layer.


Posted Sun, 11 July 2010 12:17:00 PM by jwdavidson
Filed under:

comments powered by Disqus
© NHibernate Community 2024