NHibernate is an Object Relational Mapper (ORM). NHibernate bridges the gap between the object world of C# and the relational world of a database (called the "Object Relational Impedance Mismatch"). NHibernate automatically maps objects to the database so you can concentrate on more important matters. On this page we'll explore using NHibernate for the first time.
Let's build a new NHibernate project from scratch:
- Disclaimer:
In this example, I am using NHibernate version 2.0.1.4000 with SQL Server 2005 in 2008. This will not work with the later 3.0.0 versions, although the concepts are the same, the syntax has changed. Use this page only if you have to work with 2.0, like you're in the government of something. I would recommend http://nhforge.org/wikis/howtonh/your-first-nhibernate-based-application.aspx for doing modern implementations.
- 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 reference to "NHibernate.dll".
In my project it is in C:\workfiles\NHibernatePets\lib\NHibernate\bin\net-2.0\NHibernate.dll. - Add the Code For Program.cs
using System; using System.Collections.Generic; using System.Reflection; using System.Linq; using NHibernate; using NHibernate.Cfg; namespace NHibernatePets { public class Pet { virtual public int id { get; set;} virtual public string PetName { get; set;} virtual public string Species { get; set; } virtual public DateTime Birthday { get; set; } virtual public string Speak() { return "Hi. My name is '" + PetName + "' and I'm a " + Species + " born on " + Birthday + "."; } } public class Program { private static void Main() { Pet rosey = new Pet { PetName = "Rosey", Species = "Cat", Birthday = new DateTime(2009, 1, 1, 10, 5, 15) }; Console.WriteLine(rosey.Speak()); //let's save rosey to the database try { using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { session.Save(rosey); transaction.Commit(); } Console.WriteLine("Saved rosey to the database"); } } catch (Exception e) { Console.WriteLine(e); } //let's read all the pets in the database using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } //let's update our pet in the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosey'"); Pet pet = query.List<Pet>()[0]; pet.PetName = "Rosie"; transaction.Commit(); } } //let's read all the pets in the database (again) using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } //let's delete our pet from the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosie'"); Pet pet = query.List<Pet>()[0]; session.Delete(pet); transaction.Commit(); } } } static ISessionFactory SessionFactory; static ISession OpenSession() { if (SessionFactory == null) //not threadsafe { //SessionFactories are expensive, create only once Configuration configuration = new Configuration(); configuration.AddAssembly(Assembly.GetCallingAssembly()); SessionFactory = configuration.BuildSessionFactory(); } return SessionFactory.OpenSession(); } } }
Note: The members persisted in the database have to be virtual if you use lazy loading, which is usually best way. If you don't mark them as virtual you get something like this:
NHibernate.InvalidProxyTypeException: The following types may not be used as proxies: NHibernatePets.Pet: method get_Birthday should be virtual NHibernatePets.Pet: method get_PetName should be virtual
Disclaimer: This code is shown as an example of NHibernate and is not a paradigm of programming practice.
- Create the database schema
Run this in a query analyzer:
CREATE DATABASE Pets GO USE Pets GO CREATE TABLE Pet ( id int identity primary key, PetName varchar(50), Species varchar(50), Birthday DATETIME ) GO
- Create an XML Mapping File
Right-click on the project, add a new item, an XML file named "Pet.hbm.xml"
Right-click on "Pet.hbm.xml", select "Properties" and change the "Build Action" to "Embedded Resource".
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true"> <class name="NHibernatePets.Pet, NHibernatePets" lazy="true"> <id name="id"><generator class="native" /></id> <property name="PetName" column ="PetName"/> <property name="Species" column="Species"/> <!-- We don't have to specify a column name if its the same as the variable name --> <property name="Birthday"/> </class> </hibernate-mapping>
- Add the Configuration File
NHibernate needs to know where the database lives and what type it is.
Right-click on the project, select "New Item...", select "Application configuration File" and then copy this to the "App.config" file:<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> </configSections> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="connection.provider"> NHibernate.Connection.DriverConnectionProvider </property> <property name="connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name="connection.connection_string"> Server=(local);database=Pets;Integrated Security=SSPI; </property> <property name="dialect"> NHibernate.Dialect.MsSql2005Dialect </property> <property name="show_sql"> false </property> </session-factory> </hibernate-configuration> </configuration>
(This information may also be placed in a "hibernate.cfg.xml" file).
- Running the program (Ctl-F5) should produce this:
Hi. My name is 'Rosey' and I'm a Cat born on 1/1/2009 10:05:15 AM. Saved rosey to the database pets.Count = 1 Hi. My name is 'Rosey' and I'm a Cat born on 1/1/2009 10:05:15 AM. pets.Count = 1 Hi. My name is 'Rosie' and I'm a Cat born on 1/1/2009 10:05:15 AM. Press any key to continue . . .
- Using Attributes Instead of XML Mapping Files
Instead of putting the mapping information in an xml file, you can use attributes to tell NHibernate how to map your data. You need an additional add-on dll, "NHibernate.Mapping.Attributes.dll" from here. Download and copy to your HNibernate library and reference it in your project.
Now you can use attributes to map the data. Note that the configuration object now calls "AddInputStream()" to get the meta information from the Pet object instead of the xml file.
using NHibernate.Mapping.Attributes; namespace NHibernatePets { [Class(Lazy=true)] public class Pet { [Id(Name = "id")] [Generator(Class = "native")] virtual public int id { get; set;} [Property] virtual public string PetName { get; set; } [Property] virtual public string Species { get; set; } [Property] virtual public DateTime Birthday { get; set; } ... static ISession OpenSession() { if (SessionFactory == null) //not threadsafe { //SessionFactories are expensive, create only once Configuration configuration = new Configuration(); configuration.AddInputStream( NHibernate.Mapping.Attributes.HbmSerializer.Default.Serialize( typeof(Pet))); SessionFactory = configuration.BuildSessionFactory(); } return SessionFactory.OpenSession(); }
- Debugging the Sql with log4net
NHibernate works with log4net to log events. Replace your App.config file with the following and it will turn on logging to the console:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <configSections> <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" /> <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" /> </configSections> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="connection.provider"> NHibernate.Connection.DriverConnectionProvider </property> <property name="connection.driver_class"> NHibernate.Driver.SqlClientDriver </property> <property name="connection.connection_string"> Server=(local);database=Pets;Integrated Security=SSPI; </property> <property name="dialect"> NHibernate.Dialect.MsSql2005Dialect </property> <property name="show_sql"> true </property> </session-factory> </hibernate-configuration> <log4net> <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net"> <layout type="log4net.Layout.PatternLayout, log4net"> <param name="ConversionPattern" value="%m" /> </layout> </appender> <root> <priority value="WARN" /> <appender-ref ref="ConsoleAppender" /> </root> </log4net> </configuration>
- The Six Amigos
NHibernate has six basic APIs:
- ISession
These are cheap to create and destroy. ISessions are basically a unit of work.
- ISessionFactory
This is very expensive to create and your application should only create one instance per database accessed.
- IConfiguration
As the astute reader may discern, the IConfiguration interface configures NHibernate and is used before creating the ISessionFactory.
- ITransaction
These are used to control the transactional state of operations. ITransaction is optional.
- IQuery
IQuery is light weight and must be used inside a session that created it.
- ICriteria
Let's you do object-oriented queries with criteria.
- ISession
- Fine-Grained Objects
Usually objects do not map neatly to a single table. Sometimes a row in a table may contain more than one object as in our following example. Each of Rachel's pets has a sleeping place and a 'happy' place where they like to lounge around during the day. We introduce a new object , "Place", which is in the same row as the Pet, but are actually separate objects in our code. NHibernate handles this with the "component".
Our mapping file now has two new "component" elements.
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="NHibernatePets"> <class name="NHibernatePets.Pet" lazy="false"> <id name="id" access="field"><generator class="native" /></id> <property name="PetName" /> <property name="Species" /> <property name="Birthday" /> <component name="SleepingPlace" class="NHibernatePets.Place" > <property name="Room" type="String" column="SleepingRoom" not-null="false"/> <property name="Area" type="String" column="SleepingArea" not-null="false"/> </component> <component name="HappyPlace" class="NHibernatePets.Place" > <property name="Room" type="String" column="HappyRoom" not-null="false"/> <property name="Area" type="String" column="HappyArea" not-null="false"/> </component> </class> </hibernate-mapping>
Our database is updated to put the new components in the same table:
CREATE TABLE Pet ( id int identity primary key, PetName varchar(50), Species varchar(50), Birthday DATETIME, SleepingRoom varchar(50) NULL, SleepingArea varchar(50) NULL, HappyRoom varchar(50) NULL, HappyArea varchar(50) NULL ) GO
You can see the new components in the code:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using NHibernate; using NHibernate.Cfg; namespace NHibernatePets { public class Pet { public int id; public string PetName { get; set; } public string Species { get; set; } public DateTime Birthday { get; set; } public Place SleepingPlace { get; set; } public Place HappyPlace { get; set; } public string Speak() { return string.Format("Hi! My name is '{0}' and I'm a {1} born on {2}.\n" + " I sleep in the {3} and my happy place is in the {4} on the {5}", PetName, Species, Birthday, SleepingPlace.Room, HappyPlace.Room, HappyPlace.Area); } } public class Place { public string Room { get; set; } public string Area { get; set; } } public class Program4 { private static void Main() { Pet rosey = new Pet { PetName = "Rosey", Species = "Cat", Birthday = DateTime.Now.AddYears(-1), SleepingPlace = new Place{Room="Bedroom",Area="Sofa"}, HappyPlace = new Place{Room="Kitchen",Area="Window"} }; Console.WriteLine(rosey.Speak()); //let's save rosey to the database try { using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { session.Save(rosey); transaction.Commit(); } Console.WriteLine("Saved rosey to the database"); } } catch (Exception e) { Console.WriteLine(e); } //Environment.Exit(0); //let's read all the pets in the database using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } //let's update our pet in the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosey'"); Pet pet = query.List<Pet>()[0]; pet.PetName = "Rosie"; transaction.Commit(); } } //let's read all the pets in the database (again) using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } //let's delete our pet from the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosie'"); Pet pet = query.List<Pet>()[0]; session.Delete(pet); transaction.Commit(); } } //Let's get out of here... SessionFactory.Close(); //tidy up //Console.ReadKey(); } static ISessionFactory SessionFactory; static ISession OpenSession() { if (SessionFactory == null) //not threadsafe { //SessionFactories are expensive, create only once Configuration configuration = new Configuration(); configuration.AddAssembly(Assembly.GetCallingAssembly()); SessionFactory = configuration.BuildSessionFactory(); } return SessionFactory.OpenSession(); } } }
Produces:
Hi! My name is 'Rosey' and I'm a Cat born on 7/24/2008 11:29:44 AM. I sleep in the Bedroom and my happy place is in the Kitchen on the Window
- Mapping Collections
Our pet now needs records of their visits to the Veternary. First thing to do is create a reference in your project to "Iesi.Collections" to find ISet. Here's our code:
using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using Iesi.Collections.Generic; using NHibernate; using NHibernate.Cfg; using NHibernate.Tool.hbm2ddl; using NUnit.Framework; namespace NHibernatePets { public class Pet { int id; virtual public string PetName { get; set; } virtual public string Species { get; set; } virtual public DateTime Birthday { get; set; } virtual public ISet<VetVisit> VetVisit {get; set; } public Pet() { VetVisit = new HashedSet<VetVisit>(); } virtual public void AddVetVisit(VetVisit vetVisit) { vetVisit.Pet = this; VetVisit.Add(vetVisit); } virtual public string Speak() { string tmp = string.Format("Hi! My name is '{0}' and I'm a {1} born on {2}.\n", PetName, Species, Birthday); VetVisit.ToList().ForEach(v => tmp += v.ToString()+"\r\n"); return tmp; } } public class VetVisit { private int id; virtual public string VetName { get; set; } virtual public string Reason { get; set; } virtual public DateTime VisitDate { get; set; } virtual public Pet Pet { get; set; } public VetVisit() {} public VetVisit(string vetName, string reason, DateTime visitDate) { VetName = vetName; Reason = reason; VisitDate = visitDate; } public override string ToString() { return " On " + VisitDate + " I went to " + VetName + " for " + Reason; } } public class Program5 { private static void Main() { Pet rosey = new Pet { PetName = "Rosey", Species = "Cat", Birthday = DateTime.Now.AddYears(-1)}; rosey.AddVetVisit(new VetVisit("Dr. Altobelli","Shots",DateTime.Now.AddMonths(-4))); rosey.AddVetVisit(new VetVisit("Dr. Altobelli","removing thorn from paw",DateTime.Now.AddMonths(-2))); Console.WriteLine(rosey.Speak()); //let's save rosey to the database try { using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { session.Save(rosey); transaction.Commit(); } Console.WriteLine("Saved rosey to the database"); } } catch (Exception e) { Console.WriteLine(e); } System.Environment.Exit(0); //let's read all the pets in the database using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } //let's update our pet in the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosey'"); Pet pet = query.List<Pet>()[0]; pet.PetName = "Rosie"; transaction.Commit(); } } //let's read all the pets in the database (again) using (ISession session = OpenSession()) { IQuery query = session.CreateQuery("FROM Pet"); IList<Pet> pets = query.List<Pet>(); Console.Out.WriteLine("pets.Count = " + pets.Count); pets.ToList().ForEach(p => Console.WriteLine(p.Speak())); } System.Environment.Exit(0); //let's delete our pet from the database using (ISession session = OpenSession()) { using (ITransaction transaction = session.BeginTransaction()) { IQuery query = session.CreateQuery("FROM Pet WHERE PetName = 'Rosie'"); Pet pet = query.List<Pet>()[0]; session.Delete(pet); transaction.Commit(); } } //Let's get out of here... SessionFactory.Close(); //tidy up //Console.ReadKey(); } static ISessionFactory SessionFactory; static ISession OpenSession() { if (SessionFactory == null) //not threadsafe { //SessionFactories are expensive, create only once Configuration configuration = new Configuration(); configuration.AddAssembly(Assembly.GetCallingAssembly()); SessionFactory = configuration.BuildSessionFactory(); } return SessionFactory.OpenSession(); } [TestFixture] public class GenerateSchema_Fixture { [Test] public void Can_generate_schema() { var cfg = new Configuration(); cfg.Configure(); cfg.AddAssembly(typeof(NHibernatePets.Pet).Assembly); var output = new System.IO.StringWriter(); new SchemaExport(cfg).Execute(true, false, false, false, null, output); //Console.Out.WriteLine("output = " + output.ToString()); } } } }
Here's our mapping file:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" auto-import="true" assembly="NHibernatePets" namespace="NHibernatePets"> <class name="Pet" lazy="false"> <id name="id" access="field"><generator class="native" /></id> <property name="PetName" /> <property name="Species" /> <property name="Birthday" /> <set name="VetVisit" inverse="true" cascade="all"> <key column="PetId"/> <one-to-many class="VetVisit"/> </set> </class> <class name="VetVisit" lazy="false"> <id name="id" access="field"><generator class="native" /></id> <property name="VetName" /> <property name="Reason" /> <property name="VisitDate" /> <many-to-one name="Pet" column="PetId" not-null="true"/> </class> </hibernate-mapping>
Our sql to create the database:
CREATE DATABASE Pets GO USE Pets GO CREATE TABLE Pet ( id int identity primary key, PetName varchar(50), Species varchar(50), Birthday DATETIME ) GO CREATE TABLE VetVisit( id int identity primary key, PetId int CONSTRAINT FK_Pet FOREIGN KEY (PetId) REFERENCES Pet (id), VetName varchar(50), Reason varchar(250), VisitDate DATETIME ) GO
And finally, our output:
Hi! My name is 'Rosey' and I'm a Cat born on 9/11/2008 11:04:00 AM. On 5/11/2009 11:04:00 AM I went to Dr. Altobelli for Shots On 7/11/2009 11:04:00 AM I went to Dr. Altobelli for removing thorn from paw NHibernate: INSERT INTO Pet (PetName, Species, Birthday) VALUES (@p0, @p1, @p2); select SCOPE_IDENTITY(); @p0 = 'Rosey', @p1 = 'Cat', @p2 = '9/11/2008 11:04:00 AM' NHibernate: INSERT INTO VetVisit (VetName, Reason, VisitDate, PetId) VALUES (@p0 , @p1, @p2, @p3); select SCOPE_IDENTITY(); @p0 = 'Dr. Altobelli', @p1 = 'Shots', @p2 = '5/11/2009 11:04:00 AM', @p3 = '3' NHibernate: INSERT INTO VetVisit (VetName, Reason, VisitDate, PetId) VALUES (@p0 , @p1, @p2, @p3); select SCOPE_IDENTITY(); @p0 = 'Dr. Altobelli', @p1 = 'removin g thorn from paw', @p2 = '7/11/2009 11:04:00 AM', @p3 = '3' Saved rosey to the database Press any key to continue . . .
- Persistence Ignorance
The objects being persisted with NHibernate are Plain Old C# Objects (POCOs) that do not know they are being persisted. This has many advantages. The persistence framework is easily replaced when a better one is discovered. Objects can be used in a test harness. Objects do not have to be inherited from a special class. Objects can be reused in other applications. POCOs encourage better separation of concerns.
- Requirements of Objects for use with NHibernate
- Must have a constructor with no arguments
- Collection-valued properties must be particular interfaces (eg, IList, ISet)
- General Notes
- Unruly SQL Names
If you have column names with spaces, or are using sql keywords as variable names (I admit in a lapse of judgment to have done this), you can tell NHibernate to quote the sql variable names by surrounding them with the scarcely used backtick character ("`").
-
<property name="PetName" column ="`Pet Name`"/>
- Table and Column Name Overriding
By default NHibernate looks for tables with the same name as your classes. You can override this using INamingStrategy, so that all your tables can be prepended with a particular string, or columns have some particular case.
- Namespaces
Instead of repeating namespaces over and over, you can use the "namespace" attribute to tell NHibernate the namespace of unqualified items.
<hibernate-mapping namespace="NHibernate.Pet.Model" assembly="NHibernate.Pet">
- Errors like cannot find C:\workfiles\SwitchBoard\SwitchBoard.Tests\bin\Debug\hibernate.cfg.xml
You may need to right-click on your project and add a build event to copy the "hibernate.cfg.xml" file to where nhibernate wants it.
Top rated books on NHibernate:
- Unruly SQL Names
- Why use NHibernate?
- Bridge the Object Relational Impedance Mismatch
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
Common relational databases don't have any concept of a table inheriting from another table, well apparently PostgreSQL does.
- 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
- Bridge the Object Relational Impedance Mismatch