NHibernate - Getting up and running

Introduction to NHibernate

Index

Introduction to NHibernate
Simple Queries on a single table with NHibernate
Configuring Child Collections with NHibernate
Overriding Equals, GetHashCode and Database Design with NHibernate
Querying Collections Efficiently with NHibernate
Persisting objects back to the database with NHibernate

Introduction

NHibernate is a powerful ORM with a complex API. Many consider it best of breed, yet if used incorrectly can cause headaches. Its not a tool that you should attempt to "learn as you go" with. This series will get you up and running as quickly as possible, while guiding you to avoid the common pitfalls. For clarity, I will avoid using any solution (e.g. MVC) and demostrate with unit tests only. For the same reason, I will also avoid 3rd party tools which add a layer of abstraction. While these tools are handy, its better to learn the underlying API first.

Installing the binaries

The easiest way to instal NHibernate is via NuGet, searching for NHibernate Castle which installs NHibernate with Lazy Loading enabled (a lot more on lazy loading later).

Installing NHibernate.Castle with NuGet

Creating your first class and mapping

Using the Employee table of the Northwind database as a reference, create an Employee class. The important thing to note is that all the properties are virtual, this allows NHibernate to override these properties with proxy objects, which are required for lazy loading.

    public class Employee {
        public virtual int EmployeeId {get; set; }
        public virtual string LastName {get; set;}
        public virtual string FirstName {get; set;} 
        public virtual string Title {get; set;} 
        public virtual string TitleOfCourtesy {get; set;} 
        public virtual DateTime BirthDate {get; set;} 
        public virtual DateTime HireDate {get; set;} 
        public virtual string Address {get; set;} 
        public virtual string City {get; set;} 
        public virtual string Region {get; set;} 
        public virtual string PostalCode {get; set;} 
        public virtual string Country {get; set;} 
        public virtual string HomePhone {get; set;} 
        public virtual string Extension {get; set;} 
        public virtual string Photo {get; set;} 
        public virtual string Notes {get; set;} 
        public virtual string ReportsTo {get; set;}
        public virtual string PhotoPath { get; set; }
    }

The way NHibernate wires your classes/objects to the database is via Mapping files, these are XML files which provide table, field and datatype mappings. For the Employee class, our mapping file looks like

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping 
  xmlns="urn:nhibernate-mapping-2.2"
  assembly="ORM.Nhibernate"
  namespace="ORM.Nhibernate.Model">

  <class name="Employee" table="Employees">
    <id name="EmployeeId">
      <generator class="identity" />
    </id>
    
    <property name="LastName" />
    <property name="FirstName" />
    <property name="Title" />
    <property name="TitleOfCourtesy" />
    <property name="BirthDate" />
    <property name="HireDate" />
    <property name="Address" />
    <property name="City" />
    <property name="Region" />
    <property name="PostalCode" />
    <property name="Country" />
    <property name="HomePhone" />
    <property name="Extension" />
    <property name="Photo" />
    <property name="Notes" />
    <property name="ReportsTo" />
    <property name="PhotoPath" />
  </class>

</hibernate-mapping>

As we a using an existing database and not generating the schema (using the SchemaExport tool), this is all the information we require to get up and running. Most of the mapping file above is self explanatory, the key points are

The Mapping Namespace

This should be set to the namespace of your object model

The Id element and the Generator class

The Id element states which field is the primary key and in required for persisting objects. The Generator class within the Id element tells NHibernate how the primary key is generated. There are many options available, the more common ones are

Type Description
assigned lets the application to assign an identifier to the object before Save() is called
identity supports identity columns in DB2, MySQL, MS SQL Server and Sybase
hilo uses a hi/lo algorithm to efficiently generate identifiers of any integral type
native picks identity, sequence or hilo depending upon the capabilities of the underlying database

If you are working with an existing schema, you will probably end up going with identity or assigned. If you are creating a schema from scratch, the recommended generator is hilo which enables the Unit of Work pattern.

Table and Object names

Table and field names do not have to match. It's preferable to have the same name propagate up for clarity, but in the cases where this is not possible the table and column attributes can be used to make the association. In the above mapping, you can see the Employee class is mapped to the Employees table.

Embedding the mapping file

The final (and frequently forgotten step) is to embed the mapping file into the build. Select the properties of the mapping file in Visual Studio (F4) and selecting embedded content for the build action type

Setting Build Action to Embedded Resource

We now have an object wired, ready to go...

Configuring the connection

The configuration can be set up in config or code, it rarely changes, so I like to set up in code. Below is the minimum required to configure and build a Session Factory targeting Sql Server, which I have added to a test base class. Sessions Factories are expensive to create, they should be set up once only.

namespace ORM.Nhibernate {
    public abstract class BaseTests {
        public abstract string ConnectionString { get; }
        protected ISession Session;

        public BaseTests() {
            var cfg = new Configuration();
            cfg.SetProperty(NHibernate.Cfg.Environment.Dialect, 
                typeof(NHibernate.Dialect.SQLiteDialect).AssemblyQualifiedName);

            cfg.SetProperty(NHibernate.Cfg.Environment.ConnectionDriver, 
                typeof(NHibernate.Driver.SqlClientDriver).AssemblyQualifiedName);

            cfg.SetProperty(NHibernate.Cfg.Environment.ProxyFactoryFactoryClass, 
                typeof(NHibernate.ByteCode.Castle.ProxyFactoryFactory).AssemblyQualifiedName);

            cfg.SetProperty(NHibernate.Cfg.Environment.ConnectionString, ConnectionString);

            cfg.AddAssembly("ORM.Nhibernate");

            Session = cfg
                .BuildSessionFactory()
                .OpenSession();
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~BaseTests() {
            Dispose(false);
        }

        protected virtual void Dispose(bool disposing) {
            if (disposing) {

                if (Session != null) {
                    if (Session.IsOpen)
                        Session.Close();

                    Session.Dispose();
                    Session = null;
                }
            }
        }
    }
}

This derived test classes can then execute tests against the open session.

Running your first test

The code below returns a single Employee object populated from the Northwind database. The key points with this code are

1) Always wrap your queries within a transaction (even selects)
2) When retrieving by primary key, use the optimised Session.Get method

using ORM.Nhibernate.Model;
using Xunit;

namespace ORM.Nhibernate.Tests {
    public class EmployeeTests: BaseTests  {

        public override string ConnectionString {
            get { return "Data Source=.;Initial Catalog=Northwind;Integrated Security=SSPI"; }
        }

        [Fact]
        public void CanGetEmployeeById() {
            Employee e = null;

            using (var tx = Session.BeginTransaction()) {
                e = Session.Get(4);
                tx.Commit();
            }

            Assert.NotNull(e);
            Assert.Equal("Peacock", e.LastName);
            Assert.Equal("Margaret", e.FirstName);
        }
    }
}
CanGetEmployeeById Unit Test Result

Summary

The key points to take away from the above are

  • Always make your object properties virtual
  • Remember to correctly namespace and embed your mapping files
  • Avoid using identity key generators
  • Session Factories are expensive, build them once
  • Wrap ALL queries in a transaction
  • When querying by primary key, use the optimized Session.Get method

Source Code

git clone https://github.com/mindfulsoftware/ORM.Nhibernate.git
(version: 4787306bc5e15659a0d0bde6e13d9ce82ec96207)