Tech Day #1 – NHibernate
In my previous post “My Tech ToDo – I can haz skillz?” I mentioned that I had reduced my hours at work, giving me 2 days a month to do whatever the hell I want. I have decided to devote these days to a given tech or subject to sit down and really have a chance to get acquainted.
Now, I know one day is most likely not enough to completely grok a tech or subject, hell some of them can take a lifetime to master – but it is a heck of a lot more than I have been able to do previously.
Anyway, the first of my Tech Days was dedicated to NHibernate.
What is NHibernate?
Briefly for anyone unsure:
- NHibernate is an Object Relational Mapping (ORM) tool.
- These are designed to map the complex data/models created by Object-Orientated Programming to the more simplistic scalar types used by databases.
- This problem is known as the “Object-Relational Impedance Mismatch”, which is normally overcome by creating a data access layer (DAL) which will take the complex objects, transform them for use against the actual data store (such as a database, file etc).
Good ORM’s will take all that away from you, they will take the objects you create, a map of what those objects contain and how you would like them stored and then provide an interface to perform all the database operations for you.
Enter NHibernate.
Getting Started
I decided to opt for a real quick-and-dirty app to just get playing with it to see how it worked. All I really knew about NHibernate before getting started was:
- It’s free and open source (available at Sourceforge).
- NHibernate uses an XML file to map the POCO’s (objects) to the data store – but other options are available (such as Castle Active Record).
- NHibernate’s own configuration is driven by an XML file also.
- NHibernate has quite a startup performance cost, but should be little performance loss over a regular DAL (make sure you know what a singleton is to overcome this!).
- NHibernate can cope with polymorphism in a number of ways.
- NHibernate has the ability to work with log4net to give lots of logging information.
- NHibernate can generate the database schema for you, based on your classes automatically (w00t!).
So, I had a pretty broad understanding of NHibernate (thanks a lot to @ICooper’s presentation at a local UG meeting!) to begin with, it was really a case of seeing it in action and understanding the setup process..
Setup
I thought it would be good to try and keep the application as simple as possible, so I can focus on getting NHibernate to run and play with it. Therefore the code should be considered as “Lab” code only – not recommended for production at all! I decided on:
- A standard WinForms app as the host application.
- xUnit for testing, but the test code will be mashed up with the host application (this saves on duplicating the setup for NHibernate).
- I will only work with simple CRUD operations on a simple object, leaving polymorphism for later.
- I only wanted to change the NHibernate configuration just enough to get the thing running :)
- I would use a portable MS SQL CE data file as the data store (this has both positive and negatives – local file with no SQL service required, but also needs the drivers/libraries to be imported into the project). If you are running SQL Server (and I expect all dev’s will be – if not, grab SQL Express) then it may be easier to use an MDF DB.
Getting Started
I originally download the reference documentation from the website, but I soon gathered that it may be out of date (followed closely and had problems getting it to run) – so I did another search and came across this article by Gabriel Schenker which is absolutely fantastic. I strongly recommended working from that article. You can see that I did.
Download & Symbols Import
Before doing ANYTHING within the application, I would strongly recommend downloading the latest release from Sourceforge and installing the symbols in the “Required_bins” folder into Visual Studio. To do this, copy the files into the “C:\Program Files\Microsoft Visual Studio 9.0\Xml\Schemas” (adjust path as may be necessary) – this will give you Intellisense support for the configuration files.
SQL Server Compact Edition Required Files
If you are using the SQL Server service rather than the standalone SDF files, then obviously skip this step. If not, then please review the notes in this article under “Test the Setup”. It works fine and I would only be regurgitating content so I thought I would rather save myself some typing and share the love :)
Creating the Configuration for NHibernate
To begin with, I started to enter the configuration into the App.config file as instructed in the reference documentation. I then decided to opt against that (especially since the “.config” files for Web/Windows apps can get pretty beefy as it is) and store it in its own configuration file.
Here is the configuration file “hibernate.cfg.xml” that I used:
1: <?xml version="1.0" encoding="utf-8" ?>
2:
3: <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
4:
5: <session-factory>
6: <!-- Tell NHibernate to use the MS SQL 2000 Dialect -->
7: <property name="dialect">NHibernate.Dialect.MsSqlCeDialect</property>
8:
9: <!-- .. And our connection information (basically used to create a SqlConnection -->
10: <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
11: <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
12: <property name="connection.connection_string">Data Source=Data.sdf</property>
13: <property name="show_sql">true</property>
14: <property name="proxyfactory.factory_class">NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle</property>
15:
16: <!-- Here we tell NHibernate the Assembly that contains the POCO's we want to map -->
17: <mapping assembly="NHibernateTechDay"/>
18:
19: </session-factory>
20:
21: </hibernate-configuration>
Now, everything in this file is important! Most of the configuration elements are reasonably self-explanatory:
- dialect tells NHibernate what version of SQL Server (or whatever DBMS you are using) – this means it can use optimisations that are available.
- connection.provider is used to tell which assembly NHibernate should use for the data connection. In this case, we are using a specialist driver for SQL Server CE so we tell it to use a separate driver.
- connection.driver_class with relation to the above, tells NHibernate what driver to actually use.
- connection.connection_string is the actual connection string to use to to connect to the data (note: if you copy the one auto-created by VS, you will need to change it slightly like the example above).
- show_sql will cause SQL statements that are executed by NHibernate to be output to the “Output” window. Very useful for debugging and just seeing what is going on under the hood.
- proxyfactory.factory_class tells NHibernate which assembly to use to generate the proxy classes that are used in the automatic schema generation. In this case, I was using Castle Active Record.
- mapping tells NHibernate that an assembly contains mapping information to be used by NHibernate (we will create the map in a bit). In this case, this is simply the assembly name of the project.
Once the configuration file is completed (be sure to use the name “hibernate.cfg.xml” – this is what NHibernate will be looking for), check the properties and ensure it’s build action is “Copy if newer” or “Copy always” (I prefer copy always), this will ensure the configuration file is output to the build directory as expected.
That should really do it for the core configuration!
Creating the POCO
The next step is to create the object that we would like to be persisted in the database. Me being a cat lover, I opted for the incredibly complex class definition of:
1: public class Cat
2: {
3: public virtual string ID { get; set; }
4: public virtual string Name { get; set; }
5: public virtual string Type { get; set; }
6: }
Glutton for punishment, right? You are probably wondering:
- Why am I using a string for an ID?
Why not? The reason in this case is mainly because I was working from an example. I will play with different types for identity soon. - Why are the properties all virtual? This is a good question, the reason for this is because the auto-schema generator (in this case Active Record) requires that the properties to be exposed are virtual, so it can override them and do some magic.
So I now have a domain object that I wish to persist, how does the ORM know where to put the data in the database? This is where the “map” comes in..
Creating the Map File
The map file dictates to NHibernate “what goes where” in the database. This is the map file for the “Cat” class created earlier (saved as "Cat.hbm.xml”):
1: <?xml version="1.0" encoding="utf-8" ?>
2:
3: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
4: namespace="NHibernateTechDay.Domain" assembly="NHibernateTechDay">
5:
6: <class name="Cat" table="Cat">
7:
8: <id name="ID">
9: <column name="CatID" not-null="false" />
10: <generator class="uuid.hex" />
11: </id>
12:
13: <property name="Name">
14: <column name="Name" length="16" not-null="true" />
15: </property>
16:
17: <property name="Type" />
18:
19: </class>
20:
21: </hibernate-mapping>
So, in this file we have:
- hibernate-mapping – the “namespace” attribute contains the namespace of the domain object that we are mapping, while the “assembly” attribute contains the name of the assembly in which it resides.
- class dictates the actual name of the class as well as the name of the table the objects will be stored within in the database.
- id elements contain information about how the identity of the record is both stored and generated.
- column the name of the database column the identity field is stored in (also marked as “not null” in this case so it must be set).
- generator is used to specify the generator that is used to create the unique ID. There are a whole bunch of these available.
- property elements then map the properties of the class to their related fields in the database. Constraints are applied as attributes (you can see each cat must have a name which is a maximum of 16 chars long).
And we are done! Save the file and then be sure to set its “Build Action” (in the properties window) to “Embedded Resource”. This means the XML file will actually become a part of the project assembly. When NHibernate fires up and reads it configuration, it will pick up the “mapping” elements “assembly” attribute and then hunt for XML files in the assembly. Also ensure you use the name “ClassName.hbm.xml” – convention over configuration!
We now [theoretically] have a working NHibernate configuration with a POCO that is ready to be persisted. What should we do next? Write a test!
Testing the Configuration
Using your testing framework of choice, simply add the following test code:
1: using Xunit;
2: using NHibernate.Cfg;
3: using NHibernate.Tool.hbm2ddl;
4:
5: namespace NHibernateTechDay.Tests
6: {
7: public class NHibernate_Configuration
8: {
9: [Fact]
10: public void can_generate_schema()
11: {
12: var cfg = new Configuration();
13: cfg.Configure();
14: new SchemaExport(cfg).Execute(false, true, false, false);
15: }
16: }
17: }
All we are doing here is basically newing up the NHibernate configuration (which automatically reads everything it needs) and then asking it to export/generate the database schema. If this fails, there will be something wrong with your configuration somewhere. Review and correct as necessary. I had a few errors due to bad configuration and missing SQL Server CE driver files, but I found the error messages to be very explanatory and quickly had them fixed.
Creating the Repository
DDD advocates, or even just those of us that like to write testable &, maintainable code will be familiar with the Repository Pattern. We will create a repository interface to perform the CRUD operations for our Cat data as well:
1: using System.Collections.Generic;
2:
3: namespace NHibernateTechDay.Domain
4: {
5: public interface ICatRepository
6: {
7: void Add(Cat cat);
8: void Update(Cat cat);
9: void Remove(Cat cat);
10: Cat GetByID(string id);
11: Cat GetByName(string id);
12: ICollection<Cat> GetByType(string type);
13: }
14: }
As you can see, we are not defining rocket science here, just the basic operations that we will require our data store to do. We will now create a concrete implementation of this, using NHibernate’s framework to actually do the heavy-lifting.
Creating a Static “Helper” Class
All the work that NHibernate performs takes place within what is called a Session (not to be confused with ASP.NET Sessions). The Session contains all of the reflected object data that NHibernate uses to generate the SQL statements. When the Session fires up and runs off to scan the assemblies designated in it’s configuration file. While this is useful, it is also VERY expensive, reflection is powerful but it doesn’t come cheap! Therefore, it makes sense to create a helper class to keep a reference to the Session so we only ever have to create it once. What pattern do we need here? Yup, the Singleton. Below is the code I used for the static helper class:
1: using NHibernate;
2: using NHibernate.Cfg;
3: namespace NHibernateTechDay
4: {
5: public sealed class NHibernateHelper
6: {
7: private static readonly ISessionFactory sessionFactory;
8: private static ISession currentSession;
9:
10: static NHibernateHelper()
11: {
12: sessionFactory = new Configuration().Configure().BuildSessionFactory();
13: }
14:
15: public static ISession GetCurrentSession()
16: {
17: if (currentSession == null || !currentSession.IsOpen)
18: currentSession = sessionFactory.OpenSession();
19:
20: return currentSession;
21: }
22:
23: public static void CloseSession()
24: {
25: if (currentSession == null)
26: return;
27:
28: currentSession.Close();
29: }
30:
31: public static void CloseSessionFactory()
32: {
33: if (sessionFactory != null)
34: sessionFactory.Close();
35: }
36: }
37: }
Note the singleton ISessionFactory – this is the object that does the heavy reflection stuff. We then have some static utility methods to get the current session and close it etc.
The Cat Repository
1: using NHibernateTechDay.Domain;
2: using NHibernate;
3: using NHibernate.Criterion;
4:
5: namespace NHibernateTechDay.Repositories
6: {
7: public class CatRepository : ICatRepository
8: {
9: #region ICatRepository Members
10:
11: public void Add(Cat cat)
12: {
13: using (ISession session = NHibernateHelper.GetCurrentSession())
14: {
15: using (ITransaction trans = session.BeginTransaction())
16: {
17: session.Save(cat);
18: trans.Commit();
19: }
20: }
21: }
22:
23: public void Update(Cat cat)
24: {
25: using (ISession session = NHibernateHelper.GetCurrentSession())
26: using (ITransaction trans = session.BeginTransaction())
27: {
28: session.Update(cat);
29: trans.Commit();
30: }
31: }
32:
33: public void Remove(Cat cat)
34: {
35: throw new System.NotImplementedException();
36: }
37:
38: public Cat GetByID(string id)
39: {
40: using (ISession session = NHibernateHelper.GetCurrentSession())
41: using (ITransaction trans = session.BeginTransaction())
42: {
43: return session.Get<Cat>(id);
44: }
45: }
46:
47: public Cat GetByName(string name)
48: {
49: using (ISession session = NHibernateHelper.GetCurrentSession())
50: {
51: Cat cat = session
52: .CreateQuery("from Cat c where c.Name=:name")
53: .SetString("name", name)
54: .UniqueResult<Cat>();
55: return cat;
56: }
57: }
58:
59: public System.Collections.Generic.ICollection<Cat> GetByType(string type)
60: {
61: using (ISession session = NHibernateHelper.GetCurrentSession())
62: {
63: var cats = session
64: .CreateCriteria(typeof (Cat))
65: .Add(Restrictions.Eq("Type", type))
66: .List<Cat>();
67: return cats;
68: }
69: }
70:
71: #endregion
72: }
73: }
Quite a bit of content in there, I again recommend going through this article to see how it is put together (a lot of the code was modelled from there). The repository pattern is much loved by DDD and Testing advocates, makes sense – we don’t want to be talking to our data layer directly.
Tests for the Repository
Rather than type out all of the above code in one hit, I actually tested each part of the CRUD operation(s) one at a time and then implemented the code to pass the test. For completeness, here is the test code used:
1: using NHibernateTechDay.Domain;
2: using NHibernate;
3: using NHibernate.Cfg;
4: using NHibernate.Tool.hbm2ddl;
5: using Xunit;
6: using NHibernateTechDay.Repositories;
7: using System.Collections.Generic;
8:
9: namespace NHibernateTechDay.Tests
10: {
11: public class CatRepository_Tests
12: {
13: private ISessionFactory _sessionFactory;
14: private readonly Configuration _cfg;
15:
16: private readonly Cat[] _cats = new Cat[]
17: {
18: new Cat() { Name="Smithy", Type="Fat"},
19: new Cat() { Name="Floss", Type = "Smelly"},
20: new Cat() { Name="Poppy", Type="Fluffly"},
21: new Cat() { Name="Phoebe's Cat", Type = "Smelly"}
22: };
23:
24: public CatRepository_Tests()
25: {
26: _cfg = new Configuration();
27: _cfg.Configure();
28: _sessionFactory = _cfg.BuildSessionFactory();
29: SetupContext();
30: CreateInitialData();
31: }
32:
33: private void CreateInitialData()
34: {
35: using (ISession session = _sessionFactory.OpenSession())
36: using (ITransaction trans = session.BeginTransaction())
37: {
38: foreach (var cat in _cats)
39: {
40: session.Save(cat);
41: }
42: trans.Commit();
43: }
44: }
45:
46: private void SetupContext()
47: {
48: new SchemaExport(_cfg).Execute(false, true, false, false);
49: }
50:
51: [Fact]
52: public void can_add_new_cat()
53: {
54: var cat = new Cat() {Name = "Smithy", Type = "Fat"};
55: ICatRepository repository = new CatRepository();
56: repository.Add(cat);
57: }
58:
59: [Fact]
60: public void can_add_new_cat_and_query_by_id()
61: {
62: var cat = new Cat() {Name = "Smithy", Type = "Fat"};
63: ICatRepository repository = new CatRepository();
64: repository.Add(cat);
65:
66: // use the session to try and load the product.
67: using (ISession session = NHibernateHelper.GetCurrentSession())
68: {
69: var fromDB = session.Get<Cat>(cat.ID);
70: Assert.NotNull(fromDB);
71: Assert.NotSame(fromDB, cat);
72: Assert.Equal(cat.Name, fromDB.Name);
73: Assert.Equal(cat.Type, fromDB.Type);
74: }
75: }
76:
77: [Fact]
78: public void can_update_existing_cat()
79: {
80: var cat = _cats[0];
81: cat.Name = "Fluffy Kitty";
82: ICatRepository repository = new CatRepository();
83: repository.Update(cat);
84:
85: using (ISession session = NHibernateHelper.GetCurrentSession())
86: {
87: var fromDB = session.Get<Cat>(cat.ID);
88: Assert.Equal(cat.Name, fromDB.Name);
89: Assert.NotSame(cat, fromDB);
90: }
91: }
92:
93: [Fact]
94: public void can_get_cat_by_id()
95: {
96: ICatRepository repository = new CatRepository();
97: var fromDB = repository.GetByID(_cats[0].ID);
98:
99: Assert.NotNull(fromDB);
100: Assert.NotSame(_cats[0], fromDB);
101: Assert.Equal(_cats[0].ID, fromDB.ID);
102: }
103:
104: [Fact]
105: public void can_get_cat_by_name()
106: {
107: ICatRepository repository = new CatRepository();
108: var exp = _cats[0];
109: var fromDB = repository.GetByName(exp.Name);
110:
111: Assert.NotNull(fromDB);
112: Assert.NotSame(exp, fromDB);
113: Assert.Equal(exp.ID, fromDB.ID);
114: }
115:
116: [Fact]
117: public void can_get_cats_by_type()
118: {
119: ICatRepository repository = new CatRepository();
120: var fromDB = repository.GetByType("Smelly");
121:
122: Assert.Equal(2, fromDB.Count);
123: Assert.True(IsInCollection(_cats[1], fromDB));
124: Assert.True(IsInCollection(_cats[3], fromDB));
125: }
126:
127: private bool IsInCollection(Cat cat, ICollection<Cat> fromDB)
128: {
129: foreach (var catinDB in fromDB)
130: {
131: if (cat.ID == catinDB.ID)
132: return true;
133: }
134: return false;
135: }
136: }
137: }
Whoa! Hang on a Minute! Where’s the Difference?
Yup, at this point I was first thinking the same.. Up until now we have done pretty much the same as we would normally do:
- Create some POCO’s which represent our domain model.
- Create an interface for the repository.
- Implement the interface, taking the POCO’s and then creating some DAL code to persist them.
All we have actually done is just waste time messing around with creating XML files, right?
Wrong!
It has not been a waste of time! The benefit here is maintenance. Think about it, if we added one single property to a class, we would then need to go through all the CRUD operations within the DAL (or perhaps stored procedures) and update their parameters. All we need to do with NHibernate is add a single “property” element to the mapping file and we are done. Also remember that the database schema can be generated for you. This makes development much quicker where the domain model can be evolving quickly and you are struggling to keep the database relational model up to date. All this goes away with an ORM.
In Summation
A really useful day. I had a lot of fun having finally got the chance to sit down and get a NHibernate app up and running. Am I now an NHibernate expert? No of course not! But this is not the aim of the “Tech Days”, the point of these days is to actually give me a chance to sit down and give a tech a fair shot at convincing me to invest more time into it.
I found NHibernate to be relatively quick to understand, especially once you have these core concepts down:
- The hibernate.cfg.xml configures NHibernate.
- The ClassName.hbm.xml files map the object data to fields in the database.
- The Session is used to perform a “Unit of Work”.. I started off thinking these are essentially SQL Transactions. While this is not technically correct (you perform Transactions within a Session) – the concept is very similar. A Unit of Work essentially tracks the changes made to objects (without changing the database) – when the Session is flushed, then the database is updated (using Transactions as well). This provides a real safe pattern to work with.
- The difference between how Object models work and Relational Databases is known as the “Object-Relational Impedance Mismatch”. This is just nerd-speak for “they are different”, we have all played with that stupid ball and shape thing right? Same thing, you can’t fit a square in a triangle hole.
- NHibernate has some up-front cost both in terms of development and application performance. However, the ease of maintenance it creates will likely pay you back very quickly. The lack of requirement for a real DAL can mean you focus on writing the code to solve the problem - not the problem of saving the solutions data for the original problem!
Now, I didn’t intend for this post to be any sort of tutorial. More of an overview of what I did on my first tech day, a chance to see some of the code and hopefully explain what I have learned in a way that helps others.
If anyone feels that I have misinterpreted any of the information I came across on my travels, please feel free to comment and put me straight!
Happy NHibernating!
I agree with the fast payback after a day or two of steep learning curve.
ReplyDeleteHave you become an ORM zealot yet?
Refusing to add references to System.Data any longer?
I had a Damascine conversion to NHibernate last summer when I had the pleasure of using it on a great contract (http://ianfnelson.com/blog/orm/). Now I groan when I find myself working on projects that are heavy on stored procedures. Hand-rolling data access layers and CRUD procedures is soo boring!
Another thought - if you haven't seen it already, check out the S#arp Architecture project by Billy McCafferty et al.
ReplyDeletehttp://www.sharparchitecture.net/
It's a open source architectural foundation for building maintainable web apps with ASP.NET MVC, NHibernate, Castle Windsor, and all that good stuff.