I've been playing around with NHibernate for an afternoon and am very impressed. I started with the Quick Start Guide. NHibernate maps fields in C# objects into a database without the programmer having to directly specify the SQL code to do it. NHibernate also handles caching of objects. This frees the programmer to concentrate on the business logic and not getting objects into and out of a database. Free utilities can convert between c# classes, .hbm.xml, and database schemas.
You can download NHibernate at http://nhibernate.sourceforge.net. For this example, you will also need NUnit, since the NUnit tests do all the work.
- Why use NHibernate?
One reason is that it helps to bridge the "Object Relational impedance mismatch" where objects and entities in a relational database don't really map to each other in a one-to-one fashion.
- Granularity Mismatch
A C# object may closely align to a row in a table, but at times it may only relate to certain columns of a table, the granularity of the object and database are different.
- Inheritance Mismatch
Databases don't have any concept of a table inheriting from another table.
- Identity Mismatch
Database rows often have a primary key to identify the contents of the row, but objects don't have such a thing although they have a unique memory address.
- Association Mismatch
Databases have foreign keys to relate rows together. These can be traversed in either direction - given a primary key you can find all the rows in another table pointing to it, or if you have the foreign key you can navigate to the primary key. With objects, a pointer is strictly unidirectional. You can get from the object to the thing it points to, but not the other way around.
- Transactional Mismatch
Databases are great at making sure a complete series of actions takes place, or all the actions are rolled back. Objects don't have that concept.
- Data Constraints
In objects we can ensure certain constraints are met on member data. For example, the value of private field can be only a prime number. This is difficult to enforce in a database.
- Data Types
Floating point numbers may behave differently with different precision. Strings have no defined length constraints in modern object oriented languages.
- Structural Objects often contain collections, which in turn may have other collections forming a tree structure. For example, a country can have states which has counties which have towns. This can be represented in a database, but its not quite as straight-forward as in objects.
- Granularity Mismatch
- Example C# class
[This example is from a previous version of nhibernate and will not work in the latest version. I will post a new version with the virtual keyword in the near future.]
To show a very easy example, I'll use the following class. Note that all the members going into the database have accessors.
using System; using NUnit.Framework; using NHibernate; using NHibernate.Cfg; using System.Reflection; namespace SDLite { /// <summary> /// Tiny program to demonstrate NHibernate /// </summary> public class Survey { private String name; private String groupName; private int status; private int priority; public Survey(){} public Survey(string name, string groupName, int priority, int status) { this.Name = name; this.GroupName = groupName; this.Priority = priority; this.Status = status; } //NHibernate needs accessors public string Name {get{return name;}set{name=value;}} public string GroupName {get{return groupName;}set{groupName = value;}} public int Status {get{return status;}set{status=value;}} public int Priority {get{return priority;}set{priority=value;}} public override string ToString() { return "Survey\r\n Name:"+Name+"\r\n GroupName: "+GroupName+"\r\n Status: "+Status+"\r\n Priority: "+Priority+"\r\n"; } } ... - Next, we need to create the table in the database:
create database NHibernate use NHibernate go CREATE TABLE Surveys ( Name nvarchar(40) default NULL, GroupName nvarchar(40) default NULL, Status int default 0, Priority int default 10, PRIMARY KEY (Name) ) go
- Mapping file
The first thing needed is a mapping file to tell NHibernate how to map the fields in your object to the table and columns in a database. This needs to be an embedded object if you are using Visual Studio. In the "class" element, the second argument in the "name" attribute is the assembly name, so NHibernate can reflect the objects. Here's our Survey.hbm.xml file:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.0"> <class name="SDLite.Survey, SDLite" table="Surveys"> <id name="Name" column="Name" type="String" length="40"> <generator class="assigned" /> </id> <property name="GroupName" column= "GroupName" type="String" length="40"/> <property name="Status" type="integer" /> <property name="Priority" type="integer"/> </class> </hibernate-mapping>
- Database connection information
In the app.config file we need to tell NHibernate how to connect to the database server.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="nhibernate" type="System.Configuration.NameValueSectionHandler, System, Version=1.0.5000.0,Culture=neutral, PublicKeyToken=b77a5c561934e089" /> </configSections> <nhibernate> <add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" /> <add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" /> <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" /> <add key="hibernate.connection.connection_string" value="Trusted_Connection=yes; Data Source=Marvin; Trusted_Connection=yes; database=NHibernate" /> </nhibernate> </configuration> - Using NHibernate in NUnit tests
This TestFixture is embedded in the .cs file. We can create, edit, and delete Survey objects without ever having to write a line of SQL. Best of all, if the database schema changes, only the .hbm.xml file changes, not the code.
[TestFixture] public class SurveyTest { Configuration cfg; ISessionFactory factory; [TestFixtureSetUp] public void OneTimeSetUp() { Console.Out.WriteLine("running "+MethodBase.GetCurrentMethod()); cfg = new Configuration(); cfg.AddAssembly("SDLite"); factory = cfg.BuildSessionFactory(); } [TestFixtureTearDown] public void OneTimeTearDown() { Console.Out.WriteLine("running "+MethodBase.GetCurrentMethod()); cfg = null; factory = null; } [Test] public void Save() { Console.Out.WriteLine("running "+MethodBase.GetCurrentMethod()); string name = "TestSurvey42"; Survey s = CreateSurvey(name,"TestGroup",10,1); } [Test] public void Retrieve() { Console.Out.WriteLine("running "+MethodBase.GetCurrentMethod()); string name = "TestSurveyRetrieve"; CreateSurvey(name,"TestGroup",10,1); Survey s = GetSurvey(name); Assert.IsNotNull(s); Assert.AreEqual(s.Name,name); Assert.AreEqual(s.GroupName,"TestGroup"); Assert.AreEqual(s.Priority,10); Console.Out.WriteLine(s.ToString()); } [Test] public void Modify() { Console.Out.WriteLine("running "+MethodBase.GetCurrentMethod()); string name = "TestSurveyModify"; CreateSurvey(name,"TestGroup",10,1); Survey survey = GetSurvey(name); survey.Priority = 5; survey.Status = 0; Console.Out.WriteLine(survey.ToString()); ISession session = factory.OpenSession(); //ITransaction transaction = session.BeginTransaction(); session.Update(survey); //transaction.Commit(); session.Flush(); session.Close(); } /// <summary> /// utility method to get a survey /// </summary> /// <param name="name">survey name</param> /// <returns>survey object</returns> public Survey GetSurvey(string name) { ISession session = factory.OpenSession(); session = factory.OpenSession(); Survey s = (Survey)session.Load(typeof(Survey), name); session.Close(); return s; } /// <summary> /// If it already exists, it is deleted. /// Then it is created. /// </summary> /// <param name="name">survey name</param> public Survey CreateSurvey(string name, string groupName, int priority, int status) { DeleteSurvey(name); ISessionFactory factory = cfg.BuildSessionFactory(); ISession session = factory.OpenSession(); ITransaction transaction = session.BeginTransaction(); Survey survey = new Survey(name,groupName,priority,status); //Console.Out.WriteLine(survey.ToString()); session.Save(survey); transaction.Commit(); session.Close(); return survey; } /// <summary> /// deletes the survey if it exists, if it does not exist, we exit quietly /// </summary> /// <param name="name">survey name</param> public void DeleteSurvey(string name) { ISessionFactory factory = cfg.BuildSessionFactory(); ISession session = factory.OpenSession(); Survey survey = (Survey)session.Get(typeof(Survey), name); if(survey != null) { ITransaction transaction = session.BeginTransaction(); session.Delete(survey); transaction.Commit(); session.Close(); } } } - View entire Survey.cs file.
- Debugging Hints
NHibernate is notoriously difficult to debug. Here's a few tips:
- Set the log4net to the INFO level and read the log.
-
- Links
- In Visual Studio create a new console application called "NHibernatePets"
- Download NHibernate
Download from http://nhforge.org. Create a directory in your project called "lib\NHibernate" and copy the NHibernate directories into it. Make a refence to "NHibernate.dll".
In my project it is in C:\workfiles\NHibernatePets\lib\NHibernate\bin\net-2.0\NHibernate.dll. - To Program.cs add the following "using" statements:
-
using System.Reflection; using NHibernate; using NHibernate.Cfg;
- Create the database schema
-
CREATE DATABASE Pets GO USE Pets GO CREATE TABLE Pets ( id int identity primary key, name varchar(50), species varchar(50), birthyear DATETIME, mother int, father int ) GO

