Skinning the Cat with Fluent NHibernate
Introduction
As you may have noticed, I have become interested in the concept of Object Relational Mapping and the NHibernate framework. One of the more painful/tedious aspects of using NHibernate is hand writing the xml mapping files. That is why I got excited when I heard that Jeremy Miller was open sourcing his mapping generation libraries.
The Fluent NHibernate project is an effort to create a set of APIs that generate NHibernate mapping files using a fluent interface. I downloaded the source code from the Google Code Repository and quickly found myself adding fluent methods. I submitted my changes to the project and was accepted as a contributor.
One of the tasks identified by the project owner, James Gregory, was to create a quick start guide that easily fit into the NHibernate Quick Start section 1.3 Mapping the cat. I assigned the task to myself and start hacking some code.
Creating the Mapping Class
First, create a new class project QuickStart.Domain to hold domain model objects that need to be mapped. To this assembly, add the Cat class from the NHibernate quick start.
namespace QuickStart.Domain
{
public class Cat
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual char Sex { get; set; }
public virtual float Weight { get; set; }
}
}
This is what the class looks like after a little ReSharper Code Clean Up loving.
Then add a second class project QuickStart.Domain.Mapping to hold domain model mapping classes using the Fluent NHibernate library. Add references to both the FluentNHibernate.dll and the domain model library.
Create a new class in the mapping library called CatMap. This class will inherit from ClassMap where T is the type you are creating the map for, in this case Cat. Create a constructor for the CatMap class. The constructor is where the mappings will be defined.
Because CatMap is an instance of ClassMap, you can begin using the fluent interface in the constructor right away.
public CatMap()
{
this.TableName = "Cat";
}
The TableName property of the ClassMap object specifies the name of the table in the data store that stores the Cat class. This explicit setting of the table name is unnecessary. If one is not provided the API will assume the table has the same name as the class being mapped. So, for the rest of this example, it will be dropped.
The API offers several fluent methods for defining an identifier though the Id method of ClassMap. The Cat example uses the UUID generator which looks like this:
public CatMap()
{
this.Id(x => x.Id)
.GeneratedBy
.UuidHex("B");
}
An identity column in SQL server would be mapped like this:
public CatMap()
{
Id(x => x.Id);
}
These two examples take advantage of the new C# 3.0 syntax sugar lambda expressions. An explanation of lambda expressions is outside the scope of this article, but tons of information is available on the topic on the web.
The remainder of the CatMap constructor uses the Map method of ClassMap to define the remaining properties of the class.
public CatMap()
{
this.TableName = "Cat";
this.Id(x => x.Id)
.GeneratedBy
.UuidHex("B");
//non-nullable string with a length of 16
this.Map(x => x.Name)
.WithLengthOf(16)
.CanNotBeNull();
//simple properties
this.Map(x => x.Sex);
this.Map(x => x.Weight);
}
Both the Sex and Weight properties of the Cat class are mapped quickly with a single call to them Map method. You do not need to explicitly specify the type of your properties, Fluent NHibernate will infer it based on the type being mapped. The Name property has an additional two fluent calls to limit the length of the property to sixteen characters and to disallow null values.
Usage of this mapping generates the following XML document:
<?xml version="1.0" encoding="utf-8"?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
default-lazy="false"
assembly="QuickStart.Domain" namespace="QuickStart.Domain">
<class name="Cat" table="Cat" xmlns="urn:nhibernate-mapping-2.2">
<id name="Id" column="Id" type="String" unsaved-value="0">
<generator class="uuid.hex">
<param name="format">B</param>
</generator>
</id>
<property name="Weight" column="Weight" type="Single">
<column name="Weight" />
</property>
<property name="Sex" column="Sex" type="Char">
<column name="Sex" />
</property>
<property name="Name" column="Name" length="16" type="String" not-null="true">
<column name="Name" />
</property>
</class>
</hibernate-mapping>
This mapping xml is a bit more verbose than the original example from the NHibernate quick start, but this post is being written using an alpha version of the Fluent NHibernate library. There is a lot of clean up and work left to be done.
Hooking It All Up
So, how exactly did I go from CatMap to generated XML document? There is currently no recommended methodology that I am aware of, but I am happy to share how I accomplished it.
I started by adding an interface to the Mapping library called IMapGenerator that looks like this:
namespace QuickStart.Domain.Mapping
{
public interface IMapGenerator
{
string FileName { get; }
XmlDocument Generate();
}
}
FileName is defined in the fluent interface on the ClassMap class, I know I added it while writing this code . It represents the conventional name for the NHibernate mapping files. For example, We are mapping the class Cat so FirstName would contain the string “Cat.hbm.xml”. Generate on the other hand, will be a wrapper around the ClassMap’s CreateMapping method.
I then added the interface to the CatMap class. The final CatMap looks like this:
namespace QuickStart.Domain.Mapping
{
public class CatMap : ClassMap<Cat>, IMapGenerator
{
public CatMap()
{
Id(x => x.Id)
.GeneratedBy
.UuidHex("B");
Id(x => x.Id);
//non-nullable string with a length of 16
Map(x => x.Name)
.WithLengthOf(16)
.CanNotBeNull();
//simple properties
Map(x => x.Sex);
Map(x => x.Weight);
}
public XmlDocument Generate()
{
return CreateMapping(new MappingVisitor());
}
}
}
I now have a way to identify all my mapping classes using the interface. I wanted to be able to automatically get a list of all the classes that implement the IMapGenerator interface, so created a helper class GeneratorHelper with a single static method GetMapGenerators. The class lives in the Mapping library and looks like this:
namespace QuickStart.Domain.Mapping
{
public class GeneratorHelper
{
private const string GENERATOR_INTERFACE = "IMapGenerator";
public static IList<IMapGenerator> GetMapGenerators()
{
IList<IMapGenerator> generators = new List<IMapGenerator>();
Assembly assembly = Assembly.GetAssembly(typeof(IMapGenerator));
foreach (Type type in assembly.GetTypes())
{
if (null == type.GetInterface(GENERATOR_INTERFACE)) continue;
var instance = Activator.CreateInstance(type) as IMapGenerator;
if (instance != null)
generators.Add(instance);
}
return generators;
}
}
}
This method uses reflection to locate and load the assembly that contains the IMapGenerator interface. It then iterates over the types in the loaded assembly and checks to see if the type implements the IMapGenerator interface. If a match is found, an instance of that class is created and added to the generators list.
Finally, I created a console application, QuickStart.Domain.Mapping.Mapper, and added a reference to my Mapping library. The implementation of my console app is fairly straightforward.
namespace QuickStart.Domain.Mapping.Mapper
{
internal class Program
{
private static void Main(string[] args)
{
IList<IMapGenerator> generators =
GeneratorHelper.GetMapGenerators();
foreach (IMapGenerator generator in generators)
{
XmlDocument classMapXML = generator.Generate();
classMapXML.Save(generator.FileName);
}
}
}
}
The app calls the generator helper class to get a list of IMapGenerator objects which it then iterates over calling each objects Generate method and saves the result using the conventional name for mapping files. It works fairly well and I can add as many mapping classes as I need to the Mapping library and spin out xml any time I need.
Now where Fluent NHibernate becomes really interesting is when you decide to do away with xml mapping files all together. For a great example of this check out Zachariah Young's post, "Does the Fluent NHibernate create static XML mapping files?".