This is a first article in a series where I’m going to try and explain how the Cuyahoga web site framework uses NHibernate for object persistence. The versions being used in this article series are 0.7.0.1 for Cuyahoga and 0.7 for NHibernate.
Configuration
So let’s start with the NHibernate configuration. This is the place where mappings between objects and the database live at run time. In Cuyahoga, this configuration is encapsulated with the Cuyahoga.Core.Service.SessionFactory class. This class serves as a wrapper for the NHibernate Configuration and SessionFactory classes. It is designed as a singleton because building the configuration is expensive and all application threads can safely use the same instance. At creation time the configuration for the Cuyahoga core classes is built and an ISessionFactory instance is created:
Configuration config = new Configuration();
this._nhibernateConfiguration = config.AddAssembly(this.GetType().Assembly);
this._nhibernateFactory = this._nhibernateConfiguration.BuildSessionFactory();
Since the core domain classes are in the same assembly as the SessionFactory, we can add them all at once to the configuration by simply adding the assembly (this.GetType().Assembly). Note that the Cuyahoga SessionFactory keeps a reference to the NHibernate configuration. The docs say that this one is to be discarded, but we need it for adding the mappings of the separate Cuyahoga modules.
Most modules have their own mappings but they are not loaded until they are first requested, so it’s impossible to build the configuration for the core classes and the modules all at once at creation time. The RegisterPersistentClass() method makes it possible for modules to still join the mapping party:
/// <summary>
/// Add a class to the NHibernate mappings.
/// If the class already is mapped, nothing will happen.
/// </summary>
/// <param name="type"></param>
public void RegisterPersistentClass(Type type)
{
if (this._nhibernateConfiguration.GetClassMapping(type) == null)
{
// Class isn't mapped yet, so do it now.
this._nhibernateConfiguration.AddClass(type);
this._classesAdded = true;
}
}
Note that the NHibernate SessionFactory isn’t recreated immediately. Modules may add multiple classes to the configuration in one go but they also have the responsibility to call the Rebuild() method when done.
Session management
After configuration we have everything in place for the real action. With the GetSession() method, an open NHibernate session can be obtained to do the persistence of objects.
Cuyahoga doesn’t use ‘raw’ NHibernate sessions though for persisting the core objects, but has a façade class Cuyahoga.Core.Service.CoreRepository that hides almost all NHibernate specific things for the rest of Cuyahoga. This class contains generic methods for retrieving, saving, updating and deleting objects of a specific type and some methods for more specific operations (like filtering and sorting). Internally, CoreRepository uses a NHibernate session that is obtained from the SessionFactory.
The session management follows the pattern that is called ‘Session per request’. At the beginning of each request, a single NHibernate session is created that is used during the entire ASP.NET page lifecycle. Normally this is done by storing the session in the HttpContext.Current.Items collection. Cuyahoga does it a little different by storing an instance of the CoreRepository class, but conceptually this is more or less the same.
There is a dedicated HttpModule that deals with storing the CoreRepository to the request: Cuyahoga.Web.Util.NHSessionModule. There is a BeginRequest event handler that creates the CoreRepository instance and an EndRequest event handler that closes the session:
private void Context_BeginRequest(object sender, EventArgs e)
{
// Create the repository for Core objects and add it to the current HttpContext.
CoreRepository cr = new CoreRepository(true);
HttpContext.Current.Items.Add("CoreRepository", cr);
}
private void Context_EndRequest(object sender, EventArgs e)
{
// Close the NHibernate session.
if (HttpContext.Current.Items["CoreRepository"] != null)
{
CoreRepository cr = (CoreRepository)HttpContext.Current.Items["CoreRepository"];
cr.CloseSession();
}
}
You could also put this in the Global.asax, but that’s a matter of personal preferences. I just happen to like HttpModules.
Now, the ASP.NET pages and user controls in Cuyahoga can access the CoreRepository at any time during the page lifecycle.
The CoreRepository doesn’t have to offer much functionality for the modules, so they have to manage the persistence themselves. Examples of this can be found in the classes that inherit from Cuyahoga.Core.Domain.ModuleBase like Cuyahoga.Modules.Articles.ArticleModule or Cuyahoga.Modules.StaticHtml.StaticHtmlModule.
It’s important that the modules use the same session as the Cuyahoga.Web.UI.PageEngine class that builds the pages. They can obtain that session from the CoreRepository that is in the Context.Items collection. Currently however, they raise an event when they need a session (OnNHSessionRequired) that is handled by the PageEngine. This is a little heritance from earlier days when Cuyahoga didn’t use the Context.Items to store the session. To be refactored…;).