Click here to Skip to main content
15,860,972 members
Articles / Programming Languages / SQL
Article

NHibernate Made Simple

Rate me:
Please Sign up or sign in to vote.
4.92/5 (207 votes)
31 Oct 2007CPOL44 min read 774.9K   23K   527   118
A simple, straightforward tutorial that will get you up to speed on the fundamentals of NHibernate as quickly as possible
Screenshot - image001.gif

Introduction

This article grew out of my frustration trying to get started with NHibernate. It seemed that all the introductory material I found was either very vague or so detailed that I got overwhelmed before getting to first base. What I was looking for was a simple, straightforward tutorial that would get me up to speed on the fundamentals of NHibernate as quickly as possible. I never found it. Hopefully, this article will serve those needs for other people.

This article is going to be rather lengthy, but I encourage you to work your way through it. NHibernate is a complex piece of software, with a steep learning curve. This article will flatten the curve from a matter of days or weeks to a matter of a few hours.

The Problem

NHibernate addresses the well-known problem that object persistence code is a pain in the neck to develop. Various articles estimate that from one-quarter to one-third of application code in an n-tier application is dedicated to the "persistence tier"—reading business object data from a database and writing it back again. The code is repetitive, time-consuming, and a chore to write.

Various solutions to this problem are available. Code generators can create data access code in seconds. But if the business model changes, the code has to be re-generated. "Object-relational managers" (ORMs) like NHibernate take a different approach. They manage data access transparently, exposing a relatively simple API that can load or save an entire object graph with a line or two of code.

Introducing NHibernate

Hibernate is a persistence engine in the form of a Framework. It loads business objects from a database and saves changes from those objects back to the database. As we mentioned above, it can load or save an entire object graph with just a line or two of code.

NHibernate uses mapping files to guide its translation from the database to business objects and back again. As an alternative, you can use attributes on classes and properties, instead of mapping files. To keep things as simple as possible, we're going to use mapping files in this article, rather than attributes. In addition, mapping files make for a cleaner separation between business logic and persistence code.

So, one need only add a few lines of code to an application and create a simple mapping file for each persistent class, and NHibernate takes care of all database operations. It is amazing how much development time is saved by using NHibernate.

Note that NHibernate is not the only ORM framework in the .NET universe. There are literally dozens of commercial and open source products that provide the same services. NHibernate is among the most popular, probably because of its heritage as a descendant of Hibernate, a popular ORM Framework in the Java universe. In addition, Microsoft has promised an 'Entity Framework' for ADO.NET, to provide ORM services. However, the product has been delayed, and it may not be released for some time.

Installing NHibernate

The first step in using NHibernate is to download NHibernate and Log4Net, an open-source logging application that NHibernate can use to record errors and warnings. NHibernate contains the most recent Log4Net binary, or you can download the entire Log4Net install package. Here are the download locations:

Log4Net is not strictly required to use NHibernate, but its automatic logging can be very useful when debugging.

Getting Started

In this article, I am going to use a very simple demo application (the demo app) that does no real work, other than demonstrating data access with NHibernate. It is a console application, which simplifies things by eliminating UI code. The application creates some business objects, uses NHibernate to persist them, and then reads them back from the database.

You will need to do several things to run the demo app on your machine:

  • Replace references to NHibernate and Log4Net
  • Attach the NhibernateSimpleDemo database
  • Modify the connection string

The demo app contains references to NHibernate and Log4Net. The references should be valid on your PC, so long as NHibernate and Log4Net are installed in their default locations. If the references aren't valid, you will need to replace them with references to NHibernate (NHibernate.dll) and Log4Net (log4net.dll) as they are installed on your PC. The DLLs can be found in the NHibernate installation folder on your development PC.

The demo app is configured to use SQL Server Express 2005. The database files (NhibernateSimpleDemo.mdf and NhibernateSimpleDemo.ldf) are packaged with the demo app. You will need to attach the database to SQL Server on your machine.

Finally, the connection string in the App.config file assumes that you are running a named instance of SQL Server Express 2005, and that the instance is named 'SQLEXPRESS'. If your PC is running a different configuration of SQL Server 2005, you will need to modify the connection string in the App.config file. Note that the database will not work with older versions of SQL Server.

The Business Model

There are two ways to develop an application with NHibernate. The first is a "data-centric" approach, which starts with a data model and creates business objects from the database. The second is an "object-centric" approach, which starts with a business model and creates a database to persist the model. The demo app uses the object-centric approach.

Here is the business model for the demo app:

Image 2

The model represents the skeleton of an order system. The model is not complete—there are just enough classes to demonstrate object persistence with NHibernate. And there is a minimum of detail within each class. And it should be obvious that the design of the model doesn't represent best practice. But it's enough to show how NHibernate works.

This article will use the model to demonstrate several aspects of object persistence with NHibernate:

  • Persisting simple properties;
  • Persisting 'components' (objects with no corresponding database table);
  • Persisting one-to-many associations; and
  • Persisting many-to-one associations; and
  • Persisting many-to-many associations

This article won't deal with more advanced topics, like inheritance. There is a wealth of technical information about NHibernate available on the web. This article is designed simply to get you up and running.

The model is made up of five classes, four of which are persistent. The non-persistent OrderSystem class serves as the root of the object model. We instantiate an OrderSystem object when we initialize the application. Then we load the other objects into the OrderSystem.

The OrderSystem.Customers property holds a seller's Customer list. Customers can be accessed by their CustomerID. Each Customer object holds the ID, name and address of a customer, and a list of orders placed by the customer. The address is encapsulated in a separate Address class.

The Order class contains the order ID of an order, its date, a reference to the customer placing the order, and a collection of the products in the order. The Product class holds only the ID and name of a product—remember, we are only trying to show how NHibernate works. Product objects are created when the application is initialized and loaded into the OrderSystem.Catalog property. When an Order object is created, Product object references are copied from the OrderSystem.Catalog property and added to the Order.OrderItems property.

One of NHibernate's strongest features is that it doesn't require special interfaces on business classes. In fact, business objects are generally not aware of the persistence mechanism used to load and save them. The mapping date that NHibernate uses is contained in separate XML files.

This approach loosens the coupling between business classes and data-access classes, resulting in a more flexible, easier-to-maintain business tier. The only requirement that NHibernate imposes is that collections be typed to interfaces, rather than concrete types. That's a practice that is generally recommended for all OO programming, and it does not bind business classes to NHibernate in any way.

The Database

Here is the database that the demo app uses to persist the model:

Image 3

Note that the database and the object model do not match perfectly. The object model has an Address class that has no corresponding table in the database, and the database has an OrderItems table that has no corresponding class. This mismatch is intentional. One of the aspects of NHibernate that we want to show is that there need not be a one-to-one correspondence between classes and database tables.

Here are the reasons for the mismatch:

  • The Address class does not represent an entity in the business model. Instead, it represents a value held in an entity, in this case, the Customer.Address property. We encapsulated the address in a separate class so that we can demonstrate what NHibernate calls "component mapping".
  • The OrderItems table is a link table in a many-to-many relationship between Orders and Products. As such, it does not represent an entity from the business model.

The Customers table contains a skeleton of the usual customer information, including the customer's address. Best practice would call for the address in a separate table, contrary to what we have done here. We included address information in the Customers table so we could demonstrate how to persist what NHibernate calls 'components'—classes that do not have their own tables. We will discuss components in more detail below.

The Orders table has a bare minimum of information; only the ID (order number), Date, and CustomerID of the customer placing the order. The data relation between Orders and Customers is maintained by a foreign key from the Orders.CustomerID column to the Customers.ID column.

Order items require a many-to-many relationship (each order can contain many items, and each product can appear in many orders), so we use the OrderItems table as an intermediary. It simply links an order number to a product ID.

Again, the database is not intended as a best practice, or even real world, design. It contains just enough information to show how NHibernate works.

Mapping the Business Model

Many introductions to NHibernate start with configuration code, but we are going to start at a different place: mapping classes. Mapping is the heart of what NHibernate does, and it presents the greatest stumbling blocks for beginners. Once we have discussed mapping, we will turn to the code required to configure and use NHibernate.

Mapping simply specifies which tables in the database go with which classes in the business model. Note that we will refer to the table to which a particular class is mapped as the "mapping table" for that class.

We noted above that NHibernate does not require any special interfaces or other code in a class that is to be mapped. It does, however, require that declared as virtual, so that it can create proxies as needed. The NHibernate documentation discusses this requirement. For now, simply note that all properties in all of the business model classes in the demo app are declared as virtual.

Mapping can be done by separate XML files, or by attributes on classes, properties, and member variables. If files are used for mapping they can be incorporated in the project in any of several ways. To keep things simple, we are going to show one way of mapping in this article: We will map to XML files that are compiled as resources of an assembly.

You can map as many classes as you want in a mapping file, but it is conventional to create a separate mapping file for each class. This practice keeps the mapping files short and easy to read.

To begin our examination of mapping, let's take a look at the mapping file Customer.hbm.xml. The hbm.xml extension is the standard extension for NHibernate mapping files. We have placed the files in the Model folder, but we could have placed them anywhere in the project. What's important is that the BuildAction property of the file be set to Embedded Resource. This setting will cause the mapping file to be compiled into the assembly, so that it can't get lost or separated from the application.

The opening tags of any mapping file are standard:

XML
<?xml version="1.0"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="NHibernateSimpleDemo"
assembly="NHibernateSimpleDemo">

The first tag is an XML declaration, and the second tag defines the XML namespace. You can include XSD information here, as well. The second tag also contains attributes that define the namespace and assembly names to be used with mapping references. This keeps us from having to include fully-qualified class names in the mapping tags.

The <class> Tag

The next tag identifies the class we are mapping in this file:

XML
<!-- Mappings for class 'Customer' -->
<class name="Customer" table="Customers" lazy="false">

The <class> tag's attributes specifies the class being mapped, and its mapping table in the database:

  • The name attribute specifies the class being mapped
  • The table attribute specifies the mapping table for that class
  • The lazy attribute tells NHibernate not to use 'lazy loading' for this class

'Lazy loading' tells NHibernate not to load an object from the database until the application needs to access its data. That approach helps reduce the memory footprint of a business model, and it can improve performance. To keep things simple, we aren't going to use lazy loading in this application. However, you should learn its ins and outs as soon as possible after you get up and running with NHibernate.

Note that there are a number of optional attributes for the <class> tag that are documented in the NHibernate help. To keep things simple, we won't go into them here.

The <id> Tag

Once we have identified the class being identified and its mapping table, we need to specify the identity property of the class and its corresponding identity column in the mapping table. Note that when we set up the database, we specified the CustomerID field as the primary key of the database. In the column's IdentitySpecification property, we specified that the column was the identity column, that it should initialize at 1 and increment by the same value:

Image 4

So, what we need to do is this:

  • Specify the identity property in the Customer class;
  • Specify the record identity column in the Customers table; and
  • Tell NHibernate to let SQL Server set the value of the CustomerID column in the Customers table.

Here is how we do it:

XML
<!-- Identity mapping -->
<id name="ID">
<column name=" CustomerID " />
<generator class="native" />
</id> 

The identity specification is set by a combination of attributes and enclosed tags:

  • The <id> tag's name attribute specifies the identity property in the Customer class. In this case, it is the ID property.
  • The <column> tag's name attribute specifies the record identity column in the Customers table. In this case, it's the CustomerID column.
  • The <generator> tag's class attribute specifies that record identity values will be generated natively by SQL Server.

Simple Properties

Once we have mapped the identity property for the class, we can begin mapping other properties. The Customer class has one simple property, Name. We want to map it to the Name column of the Customers table. Since the property and column names are the same, our mapping is very simple:

XML
<!-- Simple mappings -->
<property name="Name" />

Note that we could map the Name property to a column with a different name (for example, CustomerName). In that case, as with the <id> tag, we would simply include an attribute in the <property> tag specifying the name of the target column.

The property tag contains a number of optional attributes that can be used to specify the property type, the property length, whether nulls are allowed, and so on. However, NHibernate can infer this information using .NET reflection, so we have omitted these tags in the demo app.

'Component' Mapping

NHibernate uses the term 'component' to refer to a class that does not have a corresponding database table. It follows a distinction often made between "entity classes" and "value classes".

  • An entity class is a class that represents an entity in a business model. In our model, the entity classes are Customer, Order, and OrderItem. These classes represent business objects.
  • The Address class does not represent a business object—it provides a way to encapsulate a value of the Customer object. In NHibernate's terminology, it is a "component" of the Customer object.

Note that NHibernate's use of 'component' is completely unrelated to the .NET use of the term. A component is simply a class that acts as a value class for an entity class, and which has no table of its own.

A component class (value class) is not mapped in its own file. Instead, it is mapped in the file belonging to its parent class, in this case, the Customer class:

XML
<!-- Component mapping: Address-->
<component name="Address">
<property name="StreetAddress" />
<property name="City" />
<property name="State" />
<property name="Zip" />
</component>

The <component> tag is a compound tag. It begins like a <property> tag, with an attribute that specifies the name of the property being mapped. NHibernate will use .NET reflection to determine the property type (that is, the component class). You can specify the name of the class in an optional 'class' attribute.

The <component> tag is a compound tag—it encloses <property> tags to map each property of the component class. As a result, the Customer.hmb.xml file maps two classes—the Customer entity class, and the Address value class.

Associations Generally

OO design is built upon the notion that classes in a business model are associated with each other in various ways:

  • One-to-one: An object is associated with exactly one other object. For example, a Husband object associated with a single Wife object.
  • One-to-many: This type of association is often referred to as "containment". For example, a Customer object may contain a collection of references to all the Order objects that represent orders placed by the Customer.
  • Many-to-one: Many objects can refer to a single object. For example, many Order objects representing orders placed by a particular Customer can hold references to a single Customer object.
  • Many-to-many: Many objects can refer to many other objects. For example, an Order object may contain a collection of references to Product objects that represent products in an order, and a Product object can be contained in many different Order objects.

Associations can be unidirectional or bidirectional. For the purposes of this article, we will treat bi-directional associations to be simply two unidirectional associations that run in opposite directions. So, we will speak of associations running from an "owning class" (the class that 'owns' the association) to a "target class". Now, let's turn to the associations in the Customer class, and how they are mapped in the Customer.hbm.xml file.

Collection Mapping: One-To-Many

Examine the Customer class, and you will see an Orders property, which contains a list of orders placed by the Customer. One of the first things to notice about the Orders property is that it is not typed to the .NET List<T> collection. Instead, it is typed to the IList<T> interface:

C#
// Property variable
private IList<Order> p_Orders = new List<Order>();
// Orders property
public IList<Order> Orders
{
    get { return p_Orders; }
    set { p_Orders = value; }
}

The property is "declared" to be of type IList<Orders>, and the property variable is "instantiated" as a List<Order>.

That's because NHibernate requires that collections be typed to interfaces, rather than implementations. As we noted above, typing to interfaces, rather than concrete classes, is considered good programming practice, and it doesn't bind the business model in any way to NHibernate. Typing to interfaces gives NHibernate flexibility in loading collections and improves its efficiency.

NHibernate provides several different tags that can be used to map collections. Since this collection is an IList<T>, we will use a <bag> tag to map the association:

XML
<!-- One-to-many mapping: Orders -->
<bag name="Orders" cascade="all-delete-orphan" lazy="false">
<key column="CustomerID" />
<one-to-many class="Order" />
</bag>

The tag contains a name attribute, which specifies the property we are mapping. It also contains a cascade attribute, which specifies the cascade style to be applied to this association. "Cascading" means simply that NHibernate will load, save, and delete all child objects. The all-delete-orphan value indicates that NHibernate should cascade all saves and deletes, and that it should delete any orphans that are left as a result of deletion.

Note that the cascade style must be specified in any association mapping, or NHibernate will not cascade saves and deletes. As an alternative, the <class> tag can specify a default cascade style for the entire class, using a default-cascade attribute. However, this attribute only provides for cascades on saves and updates, but not for deletes. For that reason, it is better to use the "cascade" attribute in each association mapping.

Note also that we have turned off lazy loading for this association. As we noted above, lazy loading is very useful when dealing with large, multi-level collections. To keep things simple, it is turned off in the demo app. But we definitely recommend that you use it in production applications.

The <bag> tag encloses two other tags:

  • The <key> tag's column attribute specifies the column in the mapping table for the target class that is used as the foreign key between the mapping table in this class and the mapping table for the target class.
  • The <one-to-many> tag specifies that there is a one-to-many relationship between the class being mapped and the class named in the class attribute. In this case, that's the Order class—one Customer can contain many Orders. Note that the class attribute is required.

Note that there appears to be something missing: We specified the target class, but not its mapping table! The <key> tag specifies a column, but not a table. So how does NHibernate know which table to use? The answer is that, since we specified the target class, NHibernate can look up its mapping table in the target class mapping file. So we do not need to specify the target class mapping table here.

And with that, we have finished mapping the Customer class. We can close the Customer mapping file and move on to the Order class.

Collection Mapping: Many-To-One

Open the Order.hbm.xml file. By now, the contents should look pretty familiar to you. There are the usual <class> and <property> tags, and a <set> tag for the one-to-many association between an order and its items. But there is another association in the Order class, an association to the Customer class, so that each order knows the Customer that placed the order.

At first glance, this might appear to be a one-to-one association—one order to one Customer. But that wouldn't be correct, because many Order objects can contain a reference to a single Customer object. Even though a single Order object is associated with a single Customer object, at the class level, the association is many-to-one.

The <many-to-one> tag maps this association. It is a simple tag, in that it does not enclose other tags. The reason for its simplicity is that, although the association is many-to-one, the 'many' objects are associated one at a time. It is almost as simple as a <property> mapping.

XML
<!-- Many-to-one mapping: Customer -->
<many-to-one name="Customer"
class="Customer"
column="CustomerID"
cascade="all" />

The attributes themselves are straightforward:

  • The name attribute specifies the name of the property being mapped in the owning class. In this case, it's the Customer property of the Order class.
  • The class attribute specifies the target class. In this case, the target is the Customer class.
  • The column attribute specifies the column in the mapping table for the owning class that is used as the foreign key to the target class. In this case, it's the CustomerID column of the Orders table, since we are mapping the Order class.
  • The cascade attribute specifies the cascading style for this association.

The class attribute is optional; NHibernate can determine the class through .NET reflection. The column attribute can be omitted if the column in the mapping table has the same name as the class property being mapped. However, since that would rarely be the case, the attribute would normally be included.

Collection Mapping: Many-To-Many

The final mapping of interest in the Order.hbm.xml file is the mapping of the OrderItems property. Note that the OrderItems property is of type IList<Product>. The OrderItems property contains a collection of references to Product objects.

Initially, this association looks like a one-to-many association, since one Order is associated with many Products. But that association would imply that each Product can only be associated with a single Order. Obviously, any Product can appear in many Orders, so what we have here is a many-to-many association.

In the database, we use the OrderItems table as a link table between the Orders table and the Products table. The OrderItems table contains the ID of the order involved, and the ID of the product involved. The approach is the conventional means of handling a many-to-many association.

Note that we are working with a unidirectional many-to-many association. It runs from the Order class to the Product class. Bidirectional associations are more complex, and we don't cover them in this article.

So, how do we map a many-to-many association? In much the same way as we map a one-to-many association. We use a <bag> tag, which encloses a <many-to-many> tag:

XML
<!-- Many-to-many mapping: OrderItems -->
<bag name="Orders" table="OrderItems" cascade="none" lazy="false">
<key column ="OrderID" />
<many-to-many class="Product" column="ProductID" />
</bag>

Here is how the <bag> tag specifies the association:

  • The <bag> tag's name attribute specifies the property in the Customer class that is being mapped. In this case, it is the OrderItems property.
  • The <bag> tag's table attribute specifies the link table. In this case, it is the OrderItems table.
  • The <bag> tag's cascade attribute specifies the cascading style to be applied to this association.
  • The <bag> tag's lazy attribute turns off lazy loading for this association.
  • The <key> tag's column attribute specifies the foreign key column in the link table that relates the link table to the mapping table for the current class. In this case, the OrderID column of the OrderItems table links it back to the Orders table.
  • The <many-to-many> tag's class attribute specifies the target class for the many-to-many association. In this case, it is the Product class.
  • The <many-to-many> tag's column attribute specifies the foreign key column in the link table that relates the link table to the mapping table for the target class. In this case, it is the ProductID column in the link table, which relates the link table to the Products table of the database.

As in the case of the one-to-many association, we don't need to specify the mapping table for the target class. Since we have specified the target class, NHibernate can look up its mapping table from the target class mapping file.

Note that the <bag> tag's cascade attribute is set to none. That's because we don't want to delete products from the catalog when we delete an order. The use of cascade attributes in this manner gives us fine-grained control over cascading in our class persistence. By setting the one-to-many relation in the Customer mapping file to all-delete-orphan, we ensure that persistence operations (saves, updates, and deletes) cascade from Customers to their Orders. By setting the cascade attribute in the many-to-many relationship in the Orders mapping file to none, we stop the cascading at that point. That keeps products from being deleted from the catalog when an order is deleted.

The only other mapping in the Order class is a simple property mapping for the Date property. We will not spend time on that here, so you can close the Order mapping file.

We will not examine the Product.hbm.xml mapping file, because we have already covered everything that it contains. A good exercise for the reader is to open it and identity all of the items in the file.

Debugging Mapping Documents

Most debugging of mapping documents is done at run time. When an application configures NHibernate, it attempts to compile the mapping documents that it can find. If NHibernate runs into a problem, it will throw an exception of type NHibernate.MappingException. You can handle these exceptions, or you can let them stop execution. In the latter case, the exception and stack trace can be read from the Log4Net log. The most common exception will look something like this:

Could not compile the mapping document: 
    NHibernateSimpleDemo.Model.Order.hbm.xml ---> 
    NHibernate.PropertyNotFoundException: Could not find a getter for 
    property 'OrderItems' in class 'NHibernateSimpleDemo.Order'

Debugging follows the usual pattern—fix the bug, recompile, and re-execute. If your application is able to complete configuring NHibernate, you will know it had no problems with your mapping documents. We discuss configuration below.

Note that if NHibernate complains that one of your classes is unmapped, even though you have created a mapping file for that class, check the <class> declaration in the mapping file, to make sure you entered the name of the class and its mapping table correctly. If those are correct, verify that you set the file's Build Action property to Embedded Resource.

Integrating NHibernate

There is no single 'right' way to integrate NHibernate into your application. The author's personal preference is to follow general three-tier architecture, and to place NHibernate configuration and processing code in a data tier. The demo app has a Persistence folder, which contains a PersistenceManager class.

The PersistenceManager contains generic methods to persist each of the entities in the business model. While a single class is sufficient for the demo app, it is probably not best practice for a production application. In a real-world app, you may want to split these methods out to several persistence classes.

The PersistenceManager class configures NHibernate and holds a global reference to a SessionFactory object. A SessionFactory creates Session objects. Sessions are the basic NHibernate unit of work. A session represents a conversation between your application and NHibernate.

You can think of them as being one level up in a hierarchy from a transaction. A session generally encompasses one transaction, but it can include several. Basically, you open a NHibernate session, execute one or several transactions, close the session, and dispose it.

Sessions are created by a SessionFactory object. A SessionFactory is resource intensive and has a relatively high initialization cost. Sessions, on the other hand, use limited resources and impose little initialization cost. So, the general approach is to create a global SessionFactory when the application is initialized, and use that SessionFactory to create session objects as needed.

The demo project initializes a PersistenceManager object as part of the application initialization. The PersistenceManager configures a SessionFactory, which becomes a member variable of the PersistenceManager. The application can call PersistenceManager.SessionFactory to create sessions as needed.

Configuring NHibernate – Configuration Data

There are two elements to configuring NHibernate:

  • Configuration data
  • Configuration code

Configuration data can be placed either in a separate configuration file in the application root directory, or in the App.config file for the application. To keep things simple, the demo app places the data in the App.config file. It means one less file to get lost or separated from the rest of the application.

At the beginning of this article, we suggested downloading Log4Net along with NHibernate. Log4Net has its own configuration data, which we will place in the App.config folder as well. The Log4Net configuration data is straightforward, so we will not cover it here. Note that both NHibernate and Log4Net need tags in App.config's <configSections> section.

NHibernate's configuration data is reasonably clear. The data is used by NHibernate to create a SessionFactory. Here are the SessionFactory properties that are set from the data:

  • Connection provider: The IConnectionProvider that NHibernate should use. The demo app uses the default provider.
  • Dialect: Which database dialect to use. The demo app specifies the SQL Server 2005 dialect.
  • Connection driver: Which ADO.NET driver to use. The demo app specifies the SQL Server client driver.
  • Connection string: The connection string to use in connecting with the database. The connection string is a standard ADO.NET connection string; you do not need to modify a valid connection string to use it with NHibernate.

Note that the connection string in the demo app is valid for the author's development environment. You will need to change the connection string to match your database setup.

Configuring NHibernate – Configuring Log4Net

You may recall that we imported a reference to Log4Net into the demo app project when we set it up. The first step to configuring NHibernate is to configure Log4Net. Note that if you use Log4Net (its use is optional), Log4Net must be configured before NHibernate, since NHibernate will expect to see Log4Net when it is initialized.

Log4Net configuration is easy. First, make sure that Log4Net is enabled in the App.config file:

XML
<!-- Note: Logger level can be ALL/DEBUG/INFO/WARN/ERROR/FATAL/OFF -->

<!-- Specify the logging level for NHibernates -->
<logger name="NHibernate">
<level value="DEBUG" />
</logger>

Next, add the following attribute to your code. The demo app adds it above the namespace declaration for the PersistenceManager class:

C#
[assembly: log4net.Config.XmlConfigurator(Watch=true)]
namespace NHibernateSimpleDemo
{
    public class PersistenceManager : IDisposable
    {
        …
    }

The final step to configuring Log4Net is to call its Configure() method. The demo app encapsulates the call in a ConfigureLog4Net() method, which is called from the PersistenceManager constructor:

C#
private void ConfigureLog4Net()
{
    log4net.Config.XmlConfigurator.Configure();
}

Configuring NHibernate – Configuration Code

The demo app configures NHibernate when it initializes the PersistenceManager. The PersistenceManager does the configuration in a private method, which is called from the PersistenceManager constructor. The configuration code is straightforward:

C#
private void ConfigureNHibernate()
{
    // Initialize
    Configuration cfg = new Configuration();
    cfg.Configure();

    // Add class mappings to configuration object
    Assembly thisAssembly = typeof(Customer).Assembly;
    cfg.AddAssembly(thisAssembly);

    // Create session factory from configuration object
    m_SessionFactory = cfg.BuildSessionFactory();
}

First, we create an NHibernate Configuration object. Then we pass it the class mappings from the mapping files. Note that the AddAssembly() method requires that all mapping files be embedded in the project assembly. To do this, set the BuildAction property of each mapping file to Embedded Resource.

Once we have passed mapping files to the Configuration object, we simply need to tell it to create a SessionFactory for us. NHibernate will find and read the configuration data it needs; we do not need to specify whether the data is found in App.config or in a separate XML file, and we do not need to explicitly load the data.

BuildSessionFactory() returns a SessionFactory object, which the demo app passes to the Persistence Manager's SessionFactory member variable. After that, the app can call the SessionFactory whenever it needs a new session by simply calling that property on the global SessionFactory object.

Note that if we were using multiple classes; say, one persistence class for each entity in the business model, we would have to pass a reference to the SessionFactory object to each of these classes, so they could create sessions as they need them. Since all of the demo app's persistence methods are encapsulated in a single class (PersistenceManager), they can simply call the SessionFactory as a member variable.

Using NHibernate to Persist Classes

One of NHibernate's strongest features is its ability to automatically cascade loads, saves, and deletes. For example, when we save an Customer object, NHibernate will automatically save the customer's Order objects that have changed since they were last saved. In other words, when we save an object, we save its entire object graph. That feature dramatically simplifies our persistence code.

In fact, by using .NET 2.0 generics, the demo app is able to dispense with the usual persistence classes all together. It's persistence code is reduced to a few generic methods that we can incorporate directly into the PersistenceManager! Obviously, the demo app is not a complete, real world application, and good design might call for a more fine-grained approach to the persistence tier. But the demo app effectively illustrates how NHibernate radically simplifies persistence code.

The PersistenceManager class contains methods to implement all basic CRUD (create retrieve, update, and delete) operations:

  • Save(): This method saves a new or existing object to the database.
  • RetrieveAll(): This method retrieves all objects of a given type from the database.
  • RetrieveEquals(): This method retrieves all objects of a given type where a property of those objects equals a specified value. The method uses NHibernate's QueryByCriteria feature, which can be implemented in a variety of retrieval methods to retrieve 'like' string, or values within a specified range.
  • Delete(): This method has two overloads. The first deletes a single object passed into it. The second deletes a list of objects passed into it.

Most of the methods follow a common pattern:

  • They wrap a new NHibernate session object in a using statement. The using statement ensures that the session is properly closed and disposed when the method is through with it, even if an exception is thrown. The method RetrieveAll<T>() is an exception, which we discuss below.
  • The Save() and Delete() methods further wrap an NHibernate transaction in a using statement, for the same reason.
  • The method calls a generic method of the NHibernate session object, in order to perform the work that needs to be done.

Note that the PersistenceManager includes a Close() method, and that it implements the IDisposible interface. That means the PersistenceManager must be closed and disposed when the application is finished with it, which the demo app does.

The CRUD methods in the PersistenceManager are not intended to represent a complete implementation of object persistence. The CRUD methods are intended to show the basics of how persistence works in NHibernate. The NHibernate documentation contains complete information about the CRUD methods provided by the session object and how to implement those methods in your application.

The Payoff

At this point, you may be asking yourself, as I did, whether NHibernate has such a complicated setup that it might be just as easy to write CRUD code by hand. I personally found NHibernate's learning curve to be rather steep and slow going.

Well, here is where it all pays off. If you think about what we have done so far, it is really little more than creating some short mapping files, and adding a small amount of code to our application. Once you have learned the system, it is really not too bad. What you get in return is considerable.

Think for a moment how much hand-written code would be required to load the Customers collection, along with each Customer's Order objects, and each Order's Product objects. Here is the code required to do that with NHibernate:

C#
IList<T> itemList = session.CreateCriteria(typeof(T)).List<T>();

That's right—one line of code, to load an entire object graph. And here is the code required to write the same object graph to the database:

C#
foreach (Customer Customer in OrderSystem.Customers)
{
    using (ISession session = m_SessionFactory.OpenSession())
    {
        using (session.BeginTransaction())
        {
            session.SaveOrUpdate(item);
            session.Transaction.Commit();
        }
    }
}

The actual save operation requires two lines of code, assuming you want transactional support! So, once the setup is done, NHibernate makes very short work of CRUD operations. Let's turn now to how the demo app implements these operations.

Running the Demo App

The demo app is a console application, so it really has no user interface. Instead, the Program class takes the role of the UI. The Main() method communicates with a Controller class, which manipulates the model in accordance with the requests passed to the Controller by the Program class. In addition, the Program class subscribes to the OrderSystem.Populate event, which fires whenever the OrderSystem is rebuilt or loaded.

This approach is an implementation of "Model-View-Controller" (MVC) architecture, which you can learn more about in another article. If you are unfamiliar with MVC architecture, you may find it helpful to step through the demo app from the top of the Program.Main() method, to understand the flow of control.

The Program.Main() method simply sends requests to the Controller, and these requests provide a broad overview of the actions carried out by the demo app. Note that the application pauses after each major step, to give you a chance to examine the console before moving on.

The application's first task is to instantiate a Controller, which is the only object with which it communicates directly. Note that when the Controller is initialized, it creates two other objects:

  • An OrderSystem object, which contains the business model for the demo app; and
  • A PersistenceManager, which contains all of the persistence tier logic for the application.

As we noted above, the PersistenceManager is a very lightweight class, since it delegates most of its work to NHibernate.

These objects are visible only to the Controller; the Program class has no knowledge of the PersistenceManager, and very limited knowledge of the OrderSystem. This keeps our UI loosely coupled to the rest of the application. As a result, it should be very easy to re-design the demo app as, say, a Windows Forms application. We would only have to design a GUI that passed the same requests to the Controller we already have, and handle the OrderSystem.Populated event.

Next, the application clears all data from the database. To fully demonstrate the workings of NHibernate, we will start with a clean slate each time we run the application. The PersistenceManager.ClearDatabase() method illustrates an important point--we can mix calls to NHibernate with ADO.NET calls. In this case, we need to make several simple ADO.NET calls to clear the database. So, the ClearDatabase() method borrows NHibernate's connection and creates an ADO.NET command object to do the work.

Saving a Business Model

Once it has cleared the database, the application builds the business model in memory. The OrderSystem.Populate() method does the work, and the OrderSystem fires a Populated event when it is done. This event notifies the rest of the application that the business model has either been rebuilt or loaded from the database. The Program class subscribes to this event and uses it to print a list of customers and orders whenever the business model is reloaded or rebuilt.

Once the model has been built, the application saves it. And it is here that NHibernate really shines. The Save<T>() method in the PersistenceManager shows how simple persistence code can be with NHibernate. We don't even have to think about the database. The Controller simply tells the PersistenceManager to save our objects:

C#
private static void SaveBusinessObjects
    (OrderSystem OrderSystem, PersistenceManager persistenceManager)
{
    // Save Products
    foreach (Product product in OrderSystem.Catalog)
    {
        persistenceManager.Save<Product>(product);
    }

    // Save Customers (also saves Orders)
    foreach (Customer Customer in OrderSystem.Customers)
    {
        persistenceManager.Save<Customer>(Customer);
    }
}

Note that the SaveBusinessObjects() method saves Products and Customers, but not Orders. Since we have turned cascading on for the Customer.Orders property, Customers' orders are saved automatically when Customers are saved. So, there is no need to save the Order objects in the OrderSystem.Orders list.

Note also that NHibernate takes care of creating records for the OrderItems table in the database, even though we don't have any code that instructs it to do so. That is because the Order mapping file specifies the OrderItems table as the link table in the many-to-many association contained in the Order.OrderItems property. And that is another example of how NHibernate simplifies persistence code.

Deleting Business Objects

After saving the business model, the application clears it from RAM. We do this to set up the next demo, but it also illustrates an important point: Deleting a business object from RAM does not remove its data from the database. As we will see below, we have to explicitly instruct NHibernate to remove an object from the database. If we simply delete the business object from RAM, NHibernate can reload the business object from the database at any time. That is the subject of our next demonstration.

Loading Objects with NHibernate

Once the business model has been deleted from RAM, the application reloads it from the database. This step shows how to load persistent objects. The application uses the Controller.LoadBusinessObjects() method to load objects. This method is as simple as the other methods in the Program class. It simply calls the PersistenceManager.RetrieveAll<T>() method, which delegates most of its work to NHibernate.

The RetrieveAll<T>() method in the demo app is actually the second version of the method. The original version of the method was very simple. It created an NHibernate session object, which it wrapped in a using statement. Then, it used NHibernate's "Query By Criteria" feature to fetch all objects of a particular type from the database:

C#
public IList<T> RetrieveAll<T>()
{
    using (ISession session = m_SessionFactory.OpenSession())
    {
        // Retrieve all objects of the type passed in
        ICriteria targetObjects = m_Session.CreateCriteria(typeof(T));
        IList<T> itemList = targetObjects.List<T>();

        // Set return value
        return itemList;
    }
}

The type of the object fetched is specified by the T type parameter. The method created an ICriteria object specifying this type and invoked the List<T>() method of the criteria to return a list of objects meeting the criteria.

This led to a subtle problem that's easy to miss. The demo app loads Order objects twice—once explicitly, and once implicitly:

  • It loads Order objects explicitly when it loads the OrderSystem.Orders list.
  • It loads Order objects implicitly when it loads the OrderSystem.Customers list. The cascade attribute in the <bag> tag for the Orders property, found in the Customer mapping file, causes all of a customer's orders to be loaded automatically when the Custo<code>mer object is loaded.

In other words, cascading applies to loads as well as saves. If we didn't have a separate Orders collection, we could dispense with the explicit load. However, the OrderSystem.Orders collection is handy to have around, since it lets us view orders without having to know the customer that placed them. So, we will keep the explicit load.

But loading Order objects twice leads to a risk that NHibernate creates two different Order objects "that represent the same order", when what we want are two references to the "same object":

Image 5

The issue is generally referred to as "object identity". If we end up with two different objects, then a change to an order in the Customers list would not be reflected in the same order in the Orders list! Obviously, the issue is very important.

Here is the rule: NHibernate will guarantee object identity only if references are loaded during the same session. If you take another look at the old version of the RetrieveAll<T>() method, you will see that it fails that test, since a different session is created for each type loaded. In short, the old version of the RetrieveAll<T>() method produced duplicate Order objects, instead of duplicate references to the same Order object.

Here is how we fixed the method. First, we promoted the session variable to be a member variable, rather than a local variable. That change enables a session to last beyond the loading of a single type. Next, we added a new param to the method, SessionAction. This param specifies what action the method should take with respect to the session member variable:

  • Begin: The method should begin a new session.
  • Continue: The method should continue an existing session.
  • End: The method should continue an existing session, and end it when it is done.
  • BeginAndEnd: The method should begin a new session and end that session when it is done.

The revised method can load as many different types of objects as it needs to in the same session, which guarantees the identity of objects loaded more than once:

C#
public IList<T> RetrieveAll<T>(SessionAction sessionAction)
{
    // Open a new session if specified
    if ((sessionAction == SessionAction.Begin) || (sessionAction ==
    SessionAction.BeginAndEnd))
    {
        m_Session = m_SessionFactory.OpenSession();
    }

    // Retrieve all objects of the type passed in
    ICriteria targetObjects = m_Session.CreateCriteria(typeof(T));
    IList<T> itemList = targetObjects.List<T>();

    // Close the session if specified
    if ((sessionAction == SessionAction.End) || (sessionAction ==
    SessionAction.BeginAndEnd))
    {
        m_Session.Close();
        m_Session.Dispose();
    }

    // Set return value
    return itemList;
}

Here is how the method works:

  • First, a new session is begun if one is needed.
  • Then, the type is loaded as before, using query by criteria.
  • Finally, the new session is closed as needed.

To keep things simple, the code omits the try-catch block that you should use if you aren't wrapping code in a using statement. Instead, if the method is told to end a session, the method simply closes and disposes the session.

As we noted above, the application calls the RetrieveAll<T>() method only on the 'topmost' persistent objects; that is, Products, Customers, and Orders. We do not have to explicitly retrieve OrderItems, and we don't have to load Orders into Customers, or OrderItems into Orders. NHibernate takes care of loading any child objects in an object graph.

Note that NHibernate has several features for querying a database to load business objects:

  • Query by criteria: Query the database by creating a Criteria object and setting its properties.
  • Query by example: Query the database by creating a sample object of the type you want to retrieve and setting its properties to specify selection criteria.
  • Hibernate Query Language: A SQL-like query language.
  • SQL statements: You can submit SQL statements to NHibernate if the other methods of querying a database do not fit your needs.

SQL statements should be used only as a last resort, if no other method of querying the database will work. The NHibernate documentation discusses all of these options in detail.

Verifying Object Identity

Once the demo app has reloaded the business model, it demonstrates that object identity has been preserved. In fact, there are two objects that get loaded twice:

  • As we discussed above, Order objects get loaded explicitly into the OrderSystem.Orders property, and implicitly into the OrderSystem.Customers[i].Orders property.
  • In addition, Cus<code>tomer objects get loaded explicitly into the OrderSystemCustomers property, and implicitly into the OrderSystem.Orders[i].Customer property.

The business model is set up so that the first Customer (Able, Inc.) places the first order. That fact allows us to test that:

  • The first Customer in the Customers list, and the Customer in the first order in the Orders list are the same object.
  • The first order in the Orders list, and the first order for the first Customer in the Customers list, are the same object.

We verify object identity using the object.ReferenceEquals() method, which tests whether two references point to the same object:

C#
// Compare Customer #1 to the Order #1 Customer--should be equal
Customer CustomerA = OrderSystem.Customers[0];
Customer CustomerB = OrderSystem.Orders[0].Customer;
bool sameObject = object.ReferenceEquals(CustomerA, CustomerB);
…

// Compare Order #1 to the Customer #1 order--should be equal
Order orderA = OrderSystem.Orders[0];
Order orderB = OrderSystem.Customers[0].Orders[0];
sameObject = object.ReferenceEquals(CustomerA, CustomerB);

The demo app displays the results of its comparisons, then pauses for the user before moving on.

Removing Objects From the Database

As we saw above, deleting an object from the object model does not remove it from the database. The demo app demonstrated this point by clearing the object model, then reloading it from the database.

To remove an object from the database, we have to explicitly tell NHibernate to do so. The demo app's PersistenceManager contains a Delete<T>() method that performs this task:

C#
public void Delete<T>(IList<T> itemsToDelete)
{
    using (ISession session = m_SessionFactory.OpenSession())
    {
        foreach (T item in itemsToDelete)
        {
            using (session.BeginTransaction())
            {
                session.Delete(item);
                session.Transaction.Commit();
            }
        }
    }
}

The method follows the same general pattern as the Save<T>() method. Two using statements are used to wrap an NHibernate session and a transaction, respectively. The heavy lifting is done by a simple call to the session's Delete() method. A call to session.Delete() will delete an object and (assuming cascading has been set in the mapping file) all of the object's children.

The demo app shows how this is done by removing the first Customer, Able, Inc., from the database. Then it clears the object model, as it did before, and reloads the object model from the database. This time, there are only two Customers, and only two Orders. Not only did NHibernate delete the Able, Inc. Customer record from the database, but it deleted Able, Inc.'s Orders, and the OrderItems in those orders, as well.

Putting The Focus Back Where It Belongs

We have completed our tour of basic CRUD operations with NHibernate. I hope you come away with the following point: NHibernate will dramatically simplify the persistence layer of your application. Once you have created mapping files for your classes, you can nearly forget about persistence. And you don't have to spend days or weeks coding a cumbersome persistence layer to get those benefits.

The persistence tier of the demo app illustrates how lightweight this tier can be when using NHibernate. We have only a few methods, and most of those are generic, reducing the need for overloads or creating a persistence class for each business model class. The methods themselves are simple, and do not require much time to design or code. The amount of work that you have to do to persist your objects is slashed. Not bad for a free piece of software!

But there is another benefit, and it may be the most important. Take a look at the demo project in VS 2005's Solution Explorer, and you will see that most of the application's work is being done in the model. The Controller simply manages the work and delegates tasks to the business model and the persistence manager, which is basically just a thin wrapper for NHibernate. As a result, you are free to devote nearly all your attention to your business model. By relieving you of the chore of writing persistence code, NHibernate enables you to keep your focus on your business model, where it belongs.

Where To Go From Here

We have really just scratched the surface of NHibernate. Even so, you should have enough to get going. Play around with it on a couple of demo projects of your own, to get a feel for how it works. At that point you should be ready to dive into the documentation for the framework.

NHibernate ships with two documentation files. The first is a general explanation of the Framework, and the second is a reference to NHibernate's API. In addition, Manning Publications publishes NHibernate in Action (ISBN: 1-932394-92-3), which provides an extensive and detailed explanation of the Framework. It is available in eBook now and it is scheduled to appear in paperback in December 2007.

As you delve further into NHibernate, be sure to learn about lazy loading and how it works. As we discussed above, lazy loading is essential to the efficient use of NHibernate, and it is well worth the time to learn.

Conclusion

I hope you have enjoyed this introduction to NHibernate, and that it will flatten the learning curve involved in getting up to speed with the Framework. Please post comments and questions on Code Project, and I will answer as many as I can. If you find any errors, please post a comment, so that I can correct it in a revised version of the article.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior) Foresight Systems
United States United States
David Veeneman is a financial planner and software developer. He is the author of "The Fortune in Your Future" (McGraw-Hill 1998). His company, Foresight Systems, develops planning and financial software.

Comments and Discussions

 
GeneralMy vote of 5 Pin
LOL MASTER KING25-Oct-15 23:28
LOL MASTER KING25-Oct-15 23:28 
QuestionNice article Pin
User 41802541-Jul-15 10:57
User 41802541-Jul-15 10:57 
QuestionMy vote 5+ Pin
Bulbul Ahmed (KU-00)14-Jun-15 1:41
Bulbul Ahmed (KU-00)14-Jun-15 1:41 
GeneralMy Vote of 5 Pin
Dishant Verma17-Mar-15 3:54
Dishant Verma17-Mar-15 3:54 
GeneralMy vote of 5 Pin
Mahsa Hassankashi11-Mar-15 12:15
Mahsa Hassankashi11-Mar-15 12:15 
QuestionError inserting multiple children objects Pin
hannes748-Jul-14 22:36
hannes748-Jul-14 22:36 
GeneralMy vote of 5 Pin
max1969196922-Jun-14 16:52
max1969196922-Jun-14 16:52 
QuestionMy Vote of 5 Pin
Andrew J Dixon4-May-14 2:19
professionalAndrew J Dixon4-May-14 2:19 
QuestionGreat article Pin
Leonardo Furino5-Feb-14 0:49
Leonardo Furino5-Feb-14 0:49 
QuestionGreat article! Please explain why you added Customer ONLY in the ConfigureNHibernate method Pin
tinoneticm3-Feb-14 1:30
tinoneticm3-Feb-14 1:30 
GeneralMy vote of 5 Pin
Artur Gasoyan7-Jan-14 7:40
Artur Gasoyan7-Jan-14 7:40 
QuestionMy Vote of 5 Pin
Ramashankar_Tiwari17-Dec-13 0:24
Ramashankar_Tiwari17-Dec-13 0:24 
GeneralMy vote of 5 Pin
Sudarikov Kirill18-Jun-13 3:43
Sudarikov Kirill18-Jun-13 3:43 
GeneralMy vote of 5 Pin
Lupit9-Jan-13 2:28
Lupit9-Jan-13 2:28 
GeneralMy vote of 5 Pin
Simone Losi4-Jan-13 2:54
Simone Losi4-Jan-13 2:54 
GeneralMy vote of 5 Pin
ivanfallout8-Oct-12 4:04
ivanfallout8-Oct-12 4:04 
GeneralMy vote of 1 Pin
ivanfallout8-Oct-12 4:03
ivanfallout8-Oct-12 4:03 
GeneralMy vote of 5 Pin
Krish4.Net16-Jul-12 6:59
Krish4.Net16-Jul-12 6:59 
GeneralThank you! Pin
Vitaly Tomilov26-Jun-12 13:25
Vitaly Tomilov26-Jun-12 13:25 
Your article completely convinced me that nHibernate is an out-of-proportion crap!

And then some from here: http://amplicate.com/hate/nhibernate[^]
Let's agree to disagree!
Boris the animal Just Boris.

GeneralMy vote of 5 Pin
Member 88909761-May-12 9:10
Member 88909761-May-12 9:10 
GeneralGreat article Pin
Boniface Kim27-Apr-12 8:43
Boniface Kim27-Apr-12 8:43 
GeneralMy vote of 5 Pin
Hassan Alsheikh Ali7-Mar-12 21:38
Hassan Alsheikh Ali7-Mar-12 21:38 
GeneralMy vote of 4 Pin
hellono14-Mar-12 16:48
hellono14-Mar-12 16:48 
GeneralMy vote of 5 Pin
paul.campion10-Dec-11 3:14
paul.campion10-Dec-11 3:14 
Generalmy vote of 5 Pin
Uday P.Singh6-Dec-11 19:11
Uday P.Singh6-Dec-11 19:11 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.