Skip to main content

Introduction to NHibernate

I held this introduction to NHibernate yesterday and thought it might be nice to share this with you. It is just a basic NHibernate application without any fancy schmancy, to familiarize yourself with the concepts.

Download and install the Northwind database

This example builds upon the Northwind database. Please download and install it from here. Once installed you need to find the database install SQL script that should be located in C:\SQL Server 2000 Sample Databases\instnwnd.sql and run it on a database that is available to you.

database schema

Create a domain model

Next thing you create a new Visual Studio console project/solution. This will make it easy for you to run and debug your mappings later.

visual studio solution

As you can see I've created a namespace for our OR-mappings and the domain model. Please download NHibernate and reference it to your project. I've used version 2.1.2.GA in my solution. Don't forget to include binaries for lazy loading. I'm using Castle in this example.

domain model

We keep the domain simple for this example. Here's what the code looks like for these two domain classes. An important aspect here is to keep the properties virtual.

namespace NHibernateExample.Model
{
    public class Product
    {
        public Product()
        {
        }

    public Product(string name, double price)
    {
        Name = name;
        Price = price;
    }

    public virtual int ID { get; set; }

    public virtual string Name { get; set; }

    public virtual double Price { get; set; }

    public virtual Category Category { get; set; }
}

public class Category
{
    public virtual int ID { get; set; }

    public virtual string Name { get; set; }

    public virtual string Description { get; set; }
}

}

The object relational mapping

You map the database tables to your entity object by writing hbm.xml-mapping files. There are nicer ways to do the mapping through code, but I will go through the most common way of mappings here instead.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateExample" namespace="NHibernateExample.Model">
  <class name="NHibernateExample.Model.Product, NHibernateExample" table="Products">
    <id name="ID" column="ProductID">
      <generator class="identity" />
    </id>

&lt;property name=&quot;Name&quot; column=&quot;ProductName&quot; /&gt;
&lt;property name=&quot;Price&quot; column=&quot;UnitPrice&quot; /&gt;
&lt;many-to-one name=&quot;Category&quot; class=&quot;NHibernateExample.Model.Category, NHibernateExample&quot; column=&quot;CategoryID&quot; /&gt;

</class> </hibernate-mapping>

Notice that the ID is specified to be generated as "identity". This means that ID is an identity column in the table, and that the SQL server will generate the value for us on insert. You can also see how Product is related to Category with a many-to-one relationship. We specify the foreign key column name in the column attribute.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernateExample" namespace="NHibernateExample.Model">
  <class name="NHibernateExample.Model.Category, NHibernateExample" table="Categories">
    <id name="ID" column="CategoryID">
      <generator class="identity" />
    </id>

&lt;property name=&quot;Name&quot; column=&quot;CategoryName&quot; /&gt;
&lt;property name=&quot;Description&quot; /&gt;

</class> </hibernate-mapping>

Description does not need to specify column name, because it is the same as property name. This is one of the defaults of NHibernate. You'll have to mark the hbm.xml-files as Embedded Resources for NHibernate to find them.

embedded resource

NHibernate configuration

The configuration for NHibernate specifies what database provider to use, SQL dialect and connection string to the database. If you want NHibernate to be verbose about it's SQL you can set show_sql to true.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
  <session-factory>
    <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
    <property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
    <property name="connection.driverclass">NHibernate.Driver.SqlClientDriver</property>
    <property name="connection.connectionstring">Data Source=(local);Initial Catalog=Northwind;Integrated Security=SSPI</property>
    <property name='proxyfactory.factoryclass'>NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
    <property name="showsql">true</property>
  </session-factory>
</hibernate-configuration>

This xml should be placed in a hibernate.cfg.xml that should be placed in the root of your project. Make sure to mark it as Copy to Output Directory: Copy Always. This file must be present in the output bin directory, unless you choose to specify the path while building the SessionFactory

copy always

Session Management

If you're writing a web application, you might want to consider using one session for each and every request. You can read more about how to accomplish that here and here. In this simple example we will open one session for every database call. That should not be a problem since ISession is considered to be a lightweight object, in contrary to ISessionFactory that should be built only once per application.

public class SessionManager
{
 private ISessionFactory globalSessionFactory;

private static readonly object PadLock = new object(); private static SessionManager instance;

public static SessionManager Current { get { lock (PadLock) { return instance ?? (instance = new SessionManager()); } } }

public ISession OpenSession() { if (globalSessionFactory == null) { globalSessionFactory = CreateSessionFactory(); }

return globalSessionFactory.OpenSession(); }

private ISessionFactory CreateSessionFactory() { return new Configuration() .Configure() .AddAssembly(typeof(SessionManager).Assembly) .BuildSessionFactory(); }

private SessionManager() { } }

In CreateSessionFactory we do Configure() to load the hibernate.cfg.xml file, and AddAssembly will search for and load our Product.hbm.xml and Category.hbm.xml mapping files.

RepositoryBase pattern

Now when we easily can get an instance of ISession from our SessionManager, we can figure out how to do simple CRUD operations on our entities. For this we create a base class for our ProductRepository and CategoryRepository to derive from later.

public abstract class RepositoryBase<TEntity>
 where TEntity : class
{
 protected SessionManager SessionManager;

public RepositoryBase(SessionManager sessionManager) { SessionManager = sessionManager; }

public TEntity GetById(object id) { using (var session = SessionManager.OpenSession()) { return session.Get<TEntity>(id); } }

public void Insert(TEntity TEntity) { using (var session = SessionManager.OpenSession()) using (var transaction = session.BeginTransaction()) { session.Save(TEntity); transaction.Commit(); } }

public void Update(TEntity TEntity) { using (var session = SessionManager.OpenSession()) using (var transaction = session.BeginTransaction()) { session.Update(TEntity); transaction.Commit(); } }

public void Delete(TEntity TEntity) { using (var session = SessionManager.OpenSession()) using (var transaction = session.BeginTransaction()) { session.Delete(TEntity); transaction.Commit(); } } }

We need transactions for all operations that changes the datasource. That's because we can't know if our Insert operation will have to insert data into more than one table, and if it conflicts somewhere along the way we would like the operation to be atomic, and rollback to the state before we started our insert. Here's how we implement ProductRepository and CategoryRepository now.

public class ProductRepository : RepositoryBase<Product>
{
 public ProductRepository(SessionManager sessionManager)
  : base(sessionManager)
 {
 }

public IEnumerable<Product> GetByCategory(Category category) { using (var session = sessionManager.OpenSession()) { return session.CreateQuery("FROM Product as product WHERE product.Category = :category") .SetEntity("category", category) .List<Product>();

} } }

public class CategoryRepository : RepositoryBase<Category> { public CategoryRepository(SessionManager sessionManager) : base(sessionManager) { } }

I've also extended the ProductRepository with a database call to get all Products within a specfied category. A query that is specific for the Product entity and not part of the base repository.

An example application

Here's an example of how we can use the framework.

public class Program
{
 public static void Main(string[] args)
 {
  var beverages = new CategoryRepository(SessionManager.Current).GetById(1);
  var product = new Product("Juice", 12d) { Category = beverages };

var repository = new ProductRepository(SessionManager.Current);

repository.Insert(product); product.Price = 15d;

repository.Update(product); repository.Delete(product);

foreach (var beverage in repository.GetByCategory(beverages)) { Console.WriteLine("{0}, {1} {2:c}", beverage.ID, beverage.Name, beverage.Price); }

Console.ReadLine(); } }

The whole example can be downloaded from here. (Visual Studio 2008 solution)

comments powered by Disqus