Introduction
NHibernate is quite a popular framework for abstracting and handling the persistence layer. Even today with Entity Framework, NHibernate is still the first choice for many developers due to its maturity and resourcefulness.
This article aims to provide a basic ASP.NET application which would serve as the basis for anyone who wishes to start the development from scratch. Here is provided this basic app with a small entity being used and a detailed explanation on how NHibernate was setup in the application.
Here are the steps one needs to execute in order to successfully setup NHibernate:
- Create a blank ASP.NET Project.
- Import the needed DLLs through NuGet.
- Create classes for creating and handling NHibernate sessions and operations.
- Configure NHibernate and Log4net in the Web.Config file. Optionally Log4net can log NHibernate and this will be demonstrated in this article.
- Configure NHibernate session in Global.asax.
- Create the mapping of Entities in the hbm.xml files along with their respective Value Object class files.
- Create a database with the settings declared in the downloadable application, in the Web.Config file. The Connection String for that is called
DatabaseConnectionString
and it can be found within the connectionStrings
tags.
After going through the topics above, a brief explanation of how to execute the application will be provided. Finally, this article will end with a small discussion about NHibernate, its strong and weak points.
A small observation has to be made here: the code was entirely developed using Microsoft Web Developer 2010.
Setting Up NHibernate
Create a Blank Project and Import Required DLLs
In order to start the development, one needs to create an empty ASP.NET Empty Web Application, import the required DLLs via NuGet, and create the necessary files. Since this is pretty straightforward and these steps are not the focus of this article, they will not be demonstrated here. The sample downloadable project in this article can be used as a base template for the initial development. Three observations, however, are relevant here:
- The imported DLLs via NuGet are:
- A reference to
System.Transactions
needs to be added via the following steps:
-
- In the Solution Explorer, in the
References
element, right-click with the mouse and select Add Reference...
- Select the tab .NET and select the item
System.Transactions
- Press OK and this reference will be imported to the project
- Whenever one is adding an hbm.xml file, for this sort of setup, it is very important that this file is set as Embedded Resource. This is imperative because by doing so, these resources can easily be found by the NHibernate framework. To do so, follow the steps below:
-
- In the Solution Explorer, right-click with the mouse on the file and click on Properties
- In the Build Action section, select Embedded Resource.
- Save this configuration by pressing ctrl-s.
Basic Classes for Handling NHibernate
Now that the project is setup, the classes for using NHibernate must be defined. They are NHibernateHelper
and SessionHelper
, both created in the NHibernate directory of the downloadable project.
Below is shown the NHibernateHelper
:
using System;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Context;
namespace WebAppBasicNHibernate.NHibernate
{
public class NHibernateHelper
{
private ISessionFactory _sessionFactory = null;
public ISessionFactory SessionFactory
{
get
{
if (_sessionFactory == null)
{
Configuration configuration = new Configuration();
configuration.Configure();
_sessionFactory = configuration.BuildSessionFactory();
}
return _sessionFactory;
}
}
public ISession OpenSession()
{
return SessionFactory.OpenSession();
}
public void CreateSession()
{
CurrentSessionContext.Bind(OpenSession());
}
public void CloseSession()
{
if (CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Unbind(SessionFactory).Dispose();
}
}
public ISession GetCurrentSession()
{
if (!CurrentSessionContext.HasBind(SessionFactory))
{
CurrentSessionContext.Bind(SessionFactory.OpenSession());
}
return SessionFactory.GetCurrentSession();
}
}
}
This class configures the NHibernate by accessing its data from the Web.Config file. This is done in the property SessionFactory
. Basically, using the Configuration.Configure()
parameterless method, the framework fetch the configuration data from the Web.Config file. Also observe that the session is stored in a static
ISessionFactory
object called _sessionFactory
. Whenever the SessionFactory
property is called again, if there is already a _sessionFactory
object, no new instance is created, thus the already existent one is used. Note here that the Singleton Design Pattern is implemented. This is done in this fashion because building the ISessionFactory
object is expensive, making the usage of a Singleton elegant.
The other methods in the NHibernateHelper
class simply manipulate the session. Next, see the SessionHelper
class.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using NHibernate;
namespace WebAppBasicNHibernate.NHibernate
{
public class SessionHelper
{
private NHibernateHelper _nHibernateHelper = null;
public SessionHelper()
{
_nHibernateHelper = new NHibernateHelper();
}
public ISession Current
{
get
{
return _nHibernateHelper.GetCurrentSession();
}
}
public void CreateSession()
{
_nHibernateHelper.CreateSession();
}
public void ClearSession()
{
Current.Clear();
}
public void OpenSession()
{
_nHibernateHelper.OpenSession();
}
public void CloseSession()
{
_nHibernateHelper.CloseSession();
}
}
}
This class serves only as a wrapper for the NHibernateHelper
. One may even choose not to implement it and use directly the NHibernateHelper
itself, however one may find this wrapper rather useful.
Configuring NHibernate Database Settings and Log4Net in Web.Config
After the basic things are setup, one needs to configure NHibernate database settings and optionally Log4net as well. Since the Web.Config file is fairly small, it will be fully displayed below:
="1.0"
-->
<configuration>
<configSections>
-->
<section name="hibernate-configuration"
type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
<section name="log4net"
type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>
<connectionStrings>
<add name="DatabaseConnectionString"
connectionString="User ID=eduardo;Password=eduardo;
Data Source=.\SQLExpress; Initial Catalog=NHibernateBasic" />
</connectionStrings>
-->
<log4net>
<logger name="WebAppBasicNHibernateLogger">
<level value="ALL" />
<appender-ref ref="LogFileAppender" />
</logger>
<logger name="NHibernate.SQL">
<level value="DEBUG" />
<appender-ref ref="LogFileAppender" />
</logger>
<appender name="LogFileAppender" type="log4net.Appender.RollingFileAppender">
<file type="log4net.Util.PatternString"
value="c:\LogWebAppBasicNHibernate\Log-" />
<appendToFile value="true" />
<rollingStyle value="Composite" />
<maxSizeRollBackups value="25" />
<maximumFileSize value="20MB" />
<datePattern value="yyyy-MM-dd'.log'" />
<staticLogFileName value="false" />
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" />
</layout>
</appender>
</log4net>
-->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory name="NHibernate.Test">
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string_name">DatabaseConnectionString</property>
<property name="show_sql">true</property>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
-->
<property name="hbm2ddl.auto">update</property>
<property name="current_session_context_class">web</property>
<mapping assembly="WebAppBasicNHibernate" />
</session-factory>
</hibernate-configuration>
<system.web>
<customErrors mode="Off"/>
<compilation debug="true" targetFramework="4.0" />
</system.web>
</configuration>
Within the configSections
tags, one can see the declaration of NHibernate and Log4net sections. Also, observe that within the connectionStrings
tags there is the Connection String called DatabaseConnectionString
which is used for NHibernate database configuration.
If one takes a look at the log4net
tags, one will see the Log4net configuration settings. The logger named WebAppBasicNHibernateLogger
is used to log the application's events and the NHibernate.SQL
to log the NHibernate`s ones. Observe that the NHibernate.SQL
has its debug level set as DEBUG, which enables all queries that are run to logged. Finally, for that matter, note that both WebAppBasicNHibernateLogger
and NHibernate.SQL use as their logging mechanism the LogFileAppender
, which basically logs data in a disk file, which the setup is in its configuration.
The NHibernate settings can be found between the hibernate-configuration
tags. See that the DatabaseConnectionString
is used, the Microsoft SQL Server is the selected dialect, the Assembly
name matches the application's, the database will be updated automatically (because of the tag hbm2ddl.auto
set to update) and finally the SQL queries are configured to be displayed (because of the tag show_sql
set to true
). This is the core of the NHibernate configuration. Here one can change the Connection String being used, the SQL language, the Assembly of data models, how the database is updated, and so on. So, pay special attention to these settings and learn how to change them correctly.
Finally, it is important to emphasize that in order to run the downloadable application, a database with the settings declared in this Connection String DatabaseConnectionString
must be created. Note that you can change these settings as desired. Also, the downloadable application was tested using SQL Server 2008 Express Edition.
Setting Up NHibernate Session in the Global.asax
Here, may be the must crucial part of the NHibernate setup. If one chooses to use lazy loading for the Entities, the Session
opening and closing have to be used accordingly here.
In order to allow lazy loading, NHibernate has to be open its Session
in the beginning of the request, and closed in the end of it. Therefore, the Global.asax is the right place to do so, because it catches all events when the request is open and when it is closed. Below is shown the code which does what was explained:
protected SessionHelper _sessionHelper = null;
..............
protected void Application_BeginRequest(object sender, EventArgs e)
{
_sessionHelper = new SessionHelper();
_sessionHelper.OpenSession();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
_sessionHelper = new SessionHelper();
_sessionHelper.CloseSession();
}
Note how it is straightforward to setup the Session
of NHibernate in the Global.asax file, for lazy loading. Simply capture the request start event, open the session, capture the request end event and finally close the Session
. Observe that a new object is created every time the Session
is opened or closed, however this does not matter for performance matters because the ISessionFactory
itself is treated as a Singleton in the NHibernateHelper
class.
One last observation is about the Log4net, which is initialized in the event where the application is started, as shown below:
protected void Application_Start(object sender, EventArgs e)
{
XmlConfigurator.Configure();
....
}
One could finish reading this article here, because everything needed to setup NHibernate was explained. Next will be shown how to create NHibernate Entities, Data Access Objects (DAOs), and a few more interesting topics to be discussed.
Create Entities Mapped from the Database
The first thing to do in order to start using NHibernate is to create Entities which will map database tables into Value Objects. To do so, one needs to create a Class representing the Value Object and its mapping into an XML file, usually named as: FILE_NAME.hbm.xml. Here will be repeated something which were already stated in this article: the hbm.xml files must be configured as Embedded Resources, for the application to work as described throughout this article.
The downloadable application comes with a small mechanism to reuse code when creating Entities and Data Access Objects (DAOs). This mechanism, as will be demonstrated, is quite simple, yet will save up a lot of coding. Below is its class diagram:
Basically, two classes are used for the mechanism: the BaseVo
and BaseDao
. All Entities are to inherit from BaseVo
and all Data Access Objects (DAOs) are to inherit from BaseDao
. BaseVo
holds the Id
property, which is used as an Identifier for all entities. Its type is a Generic one, thus it is defined when the BaseVo
is subclassed. Having this Id
in a base class saves the need for every Entity to define its Identifier. Moreover, the BaseVo
and its Identifier are used in the BaseDao
, which implements methods that are useful for every Data Access Object of NHibernate. Having all DAOs inheriting from the base class, make all these methods reusable.
Since this article only intends to depict basic NHibernate setup, here will be created only one Entity with its mapping, providing only a few examples of database interaction: the Person
and PersonDao
, as one can see in the class diagram.
Now, have a look at the BaseVo
class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebAppBasicNHibernate.Vo
{
public class BaseVo<TIdentifier>
where TIdentifier : new()
{
public virtual TIdentifier Id { get; set; }
}
}
Note how straightforward the class is. Also observe the use of Generics for the Identifier. Next, have a look at the BaseDao
class:
using System.Collections.Generic;
using System.Linq;
using System.Transactions;
using NHibernate;
using NHibernate.Linq;
using WebAppBasicNHibernate.NHibernate;
using WebAppBasicNHibernate.Vo;
namespace WebAppBasicNHibernate.Dao
{
public class BaseDao<TEntity, TIdentifier>
where TIdentifier : new()
where TEntity : BaseVo<TIdentifier>
{
protected ISession CurrentSession { get; set; }
public BaseDao()
{
SessionHelper sessionHelper = new SessionHelper();
CurrentSession = sessionHelper.Current;
}
public TEntity LoadById(TIdentifier id)
{
TEntity entity = CurrentSession.Get<TEntity>(id);
return entity;
}
public TIdentifier Create(TEntity entity)
{
TIdentifier identifier = new TIdentifier();
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required))
{
identifier = (TIdentifier)CurrentSession.Save(entity);
transaction.Complete();
}
return identifier;
}
public void SaveOrUpdate(TEntity entity)
{
TIdentifier identifier = new TIdentifier();
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required))
{
CurrentSession.SaveOrUpdate(entity);
transaction.Complete();
}
}
public void Update(TEntity entity)
{
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required))
{
CurrentSession.Update(entity);
CurrentSession.Flush();
transaction.Complete();
}
}
public void Delete(TEntity entity)
{
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required))
{
CurrentSession.Delete(entity);
transaction.Complete();
}
}
public void DeleteById(TIdentifier entityIdentifier)
{
using (TransactionScope transaction = new TransactionScope(TransactionScopeOption.Required))
{
TEntity entity = LoadById(entityIdentifier);
CurrentSession.Delete(entity);
transaction.Complete();
}
}
public IList<TEntity> LoadAll()
{
return CurrentSession.Query<TEntity>().ToList();
}
}
}
Note how this class uses Generics for gathering the Entity Identifier and the Entity itself. See as well how the Entity Generic must inherit from BaseVo
, making the mechanism more robust. Moreover, many useful methods are implementing using the NHibernate API, along with the Generics Entity and Identifier. Whenever a class inherits from the BaseDao
, automatically these Generics values will be filled in and the methods will be useful for this very class.
Finally, look below the Person
and PersonDao
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace WebAppBasicNHibernate.Vo
{
public class Person : BaseVo<Int64>
{
public virtual String Name { get; set; }
public virtual Int32 Age { get; set; }
}
}
using System;
using WebAppBasicNHibernate.Vo;
using System.Collections.Generic;
using NHibernate.Linq;
namespace WebAppBasicNHibernate.Dao
{
public class PersonDao : BaseDao<Person, Int64>
{
public void DeleteByName(String name)
{
var queryResult = CurrentSession.QueryOver<Person>()
.Where(p => p.Name == name);
if (queryResult != null && queryResult.RowCount() > 0)
{
IList<Person> peopleToBeDeleted = queryResult.List();
peopleToBeDeleted.ForEach(personToBeDeleted => CurrentSession.Delete(personToBeDeleted));
CurrentSession.Flush();
}
}
}
}
Note how much code is saved, since PersonDao
has only one implemented method but have several automatically inherited.
The Person.hbm.xml also is important here, and it is shown below:
="1.0" ="utf-8"
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="WebAppBasicNHibernate.Vo" assembly="WebAppBasicNHibernate">
<class name="Person" table="PERSON" >
-->
<id name="Id" column="ID">
<generator class="identity" />
</id>
<property name="Name" column="NAME" not-null="true" />
<property name="Age" column="AGE" not-null="true" />
</class>
</hibernate-mapping>
Note the mapping of table columns into object properties. Also observe the table definition. One important detail to be seen is the usage of the application's namespace.
Having the Entity and its Data Access Object setup, one is ready to do some database operations. This is discussed next.
Examples of Database Interactions
The downloadable application has a few examples of the NHibernate usage. This session will demonstrate them. Below is shown the full code of Global.asax. Note that in the Application_Start
event, a series of NHibernate database operations are performed. Three entities are created and one is deleted. Moreover, observe the use of the Default Microsoft .NET transaction. This is allowed and there is no difference between using this transaction and the one provided by NHibernate. To be fair, NHibernate transaction has one huge disadvantage: it cannot be nested within another NHibernate transaction. This can complicate things because one has to be careful whenever a method is calling another, to see if they all make the use of only one NHibernate transaction.
Another important statement: the event Application_Start
is executed only when the application is started. For it to be executed again, the Internet Information Services (IIS) has to be restarted.
using System;
using System.Collections.Generic;
using System.Transactions;
using log4net;
using log4net.Config;
using WebAppBasicNHibernate.Dao;
using WebAppBasicNHibernate.NHibernate;
using WebAppBasicNHibernate.Vo;
namespace WebAppBasicNHibernate
{
public class Global : System.Web.HttpApplication
{
protected SessionHelper _sessionHelper = null;
private static ILog _log =
LogManager.GetLogger(Log4NetConstants.WEB_APP_BASIC_NHIBERNATE_LOGGER);
protected void Application_Start(object sender, EventArgs e)
{
XmlConfigurator.Configure();
try
{
_sessionHelper = new SessionHelper();
_sessionHelper.OpenSession();
using (TransactionScope transactionScope = new TransactionScope())
{
PersonDao personDao = new PersonDao();
IList<Person> people = personDao.LoadAll();
if (people != null && people.Count > 0)
{
foreach (Person person in people)
{
personDao.Delete(person);
}
}
Person jose = new Person();
jose.Name = "Jose";
jose.Age = 28;
personDao.SaveOrUpdate(jose);
Person maria = new Person();
maria.Name = "Maria";
maria.Age = 29;
personDao.SaveOrUpdate(maria);
Person mario = new Person();
mario.Name = "Mario";
mario.Age = 27;
personDao.SaveOrUpdate(mario);
personDao.Delete(mario);
transactionScope.Complete();
}
_sessionHelper.CloseSession();
}
catch (Exception ex)
{
_log.Error("An error has occurred while initializing the application.", ex);
}
}
protected void Session_Start(object sender, EventArgs e)
{
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
_sessionHelper = new SessionHelper();
_sessionHelper.OpenSession();
}
protected void Application_EndRequest(object sender, EventArgs e)
{
_sessionHelper = new SessionHelper();
_sessionHelper.CloseSession();
}
protected void Application_AuthenticateRequest(object sender, EventArgs e)
{
}
protected void Application_Error(object sender, EventArgs e)
{
}
protected void Session_End(object sender, EventArgs e)
{
}
protected void Application_End(object sender, EventArgs e)
{
}
}
}
Another example can be observed in the Default.aspx.cs. Here, more NHibernate database operations are performed. Note that the NHibernate transaction is used. Remember, there cannot be nested exceptions for this sort of transaction.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using WebAppBasicNHibernate.Vo;
using WebAppBasicNHibernate.Dao;
using log4net;
using NHibernate;
using WebAppBasicNHibernate.NHibernate;
namespace WebAppBasicNHibernate
{
public partial class Default : System.Web.UI.Page
{
private static ILog _log =
LogManager.GetLogger(Log4NetConstants.WEB_APP_BASIC_NHIBERNATE_LOGGER);
protected void Page_Load(object sender, EventArgs e)
{
}
private void ManipulatePerson()
{
try
{
SessionHelper sessionHelper = new SessionHelper();
using (ITransaction transaction = sessionHelper.Current.BeginTransaction())
{
PersonDao personDao = new PersonDao();
personDao.DeleteByName("John");
personDao.DeleteByName("Mary");
_log.Info("Removed John and Mary, in case they do exist");
lblOperations.Text = "Removed John and Mary, in case they do exist";
Person john = new Person();
john.Name = "John";
john.Age = 32;
personDao.SaveOrUpdate(john);
_log.Info("Created John.");
lblOperations.Text += "<br>Created John.";
Person mary = new Person();
mary.Name = "Mary";
mary.Age = 33;
personDao.SaveOrUpdate(mary);
_log.Info("Created Mary.");
lblOperations.Text += "<br>Created Mary.";
transaction.Commit();
}
}
catch (Exception ex)
{
_log.Error("Error while manipulating entities using NHibernate.", ex);
}
}
protected void btnExecuteNHibernateOperations_Click(object sender, EventArgs e)
{
ManipulatePerson();
}
}
}
This finalizes the code explanation. Please, do not forget to create the database in order to run this application. The configurations are set in the Web.Config file, as explained earlier.
Running the Code
The Project compilation and execution is straightforward:
- Open the Project using Microsoft Visual Web Developer 2010 or later. Of course, one can use Visual Studio.
- Compile the project. Note that for the first, all necessary libraries will be automatically downloaded via NuGet.
- Run the Project.
When the project is run, the database tables will be created and the operations in the Global.asax will be executed only when the software is started. They will be executed again only if one restarts Internet Information Services (IIS), as mentioned before. If the application is successfully executed, a screen like the one displayed below will appear:
If you press ªExecute NHibernate Operationsª a few NHibernate database operations will be executed. They were already discussed in the previous session. One can see the results persisted in the database.
Discussion
Although the article is extensive, note that setting up NHibernate is not complicated. Basically, one needs to import the required libraries, setup basic Session
classes, configure the database and start implementing the Value Object and Data Access Object classes.
NHibernate is a powerful framework, it is widely used among Microsoft .NET developers and even more utilized among Java users, of course making use of the Hibernate framework.
NHibernate is so famous because it saves up a lot of time in coding. Basically because it makes the conversion from database data to software object automatically, and vice-versa. It also allows developers to build complex queries in code, which eases immensely the software development and maintenance.
As a negative point, NHibernate has quite a steep learning curve, making its learning something not so trivial. However, since developers love a good challenge, this framework is worth mastering.
Microsoft released the Entity Framework which with all due respect to Microsoft, is a copy of NHibernate with visual modeling wizards. Even so, the first versions of Entity Framework left much room for improvement, which proves how hard it is to build such a framework, and how mature NHibernate is.
With the QueryOver API, NHibernate is still easier to use than Entity Framework and even to date, it is more reliable to use with other databases than Microsoft SQL Server.
To summarize, NHibernate is a great framework and hopefully will be around for many years.