Twitter LinkedIn Github

JetBrains

In the first part of this series, we saw the basics of what Fluent NHibernate is about. In this second part, we're going to look into relationships and some of the mappings that FNH produces for us, as well as overriding certain mappings.

 

Relationships

Let's say we have a new requirement that requires all customers to have multiple documents associated with them.

In terms of a relational database world, if we were to have two tables, one representing customers, and the other documents, the result would be:

 

image

There would be a foreign key in Document pointing to the customer Id of the Customer.

In an OO world, we don't (or shouldn't) care about how information is persisted. In other words, when designing our classes, all we care about is having a list of documents that is related to a customer:

 

   1: public class Document
   2: {
   3:     public virtual int Id { get; private set; }
   4:     public virtual string Code { get; set; }
   5:     public virtual DateTime Date { get; set; }
   6:     public virtual string Author { get; set; }
   7:     public virtual string Contents { get; set; }
   8: 
   9: }
  10: 
  11: public class Customer
  12:    {
  13:        public virtual int Id { get; private set; }
  14:        public virtual string NameFirst { get; set; }
  15:        public virtual string NameLast { get; set; }
  16:        public virtual string Telephone { get; set; }
  17:        public virtual string Email { get; set; }
  18: 
  19:        public virtual ISet<Document> Documents { get; set; }
  20: 
  21:        public Customer()
  22:        {
  23:            Documents = new HashedSet<Document>();
  24:        }
  25:    }

 

Our Customer class contains a collection of Documents (line 19). Why is this an ISet as opposed to an IList or ICollection? A set represents an unordered collection of objects that does not allow duplicates*. Having this class layout, let's put it through Fluent NHibernate to see what it spits out.

 

[* The .NET framework does not come with a type Set. However, all distributions of NHibernate come with the Iesi.Collections assembly, since it's used internally. So you don't have much to worry about.]

 

 

Project Layout

Before continuing, let's take a look at our project layout:

image

We have three projects:

1. Example.Domain which is our application per say. This is where our entities reside.

2. Example.Repository is where our NHibernate repository will eventually be. It is also where our Mapping configuration is located.

3. Example.FluentHelpers is a series of classes that will help with generating the database structure, etc.

The reason that our Mapping file is kept in the repository is two-fold:

1. FluentHelpers can be re-used and is project independent. It knows nothing of specific entities or mappings.

2. Domain should have no reference to the persistence mechanism and as such should not know whether we use NHibernate or any other type of ORM.

Our FluentMapper class is just a wrapper around Fluent Nhibernate and NHibernates tools namespace, with three methods: SaveMappings, CreateDatabase and UpdateDatabase. The SaveMappings exports the mappings Fluent NHibernate creates for us to a specific folder (remember, each class has it's own mapping). The implementation is very straight forward:

 

   1: public void SaveMappings(string path)
   2: {
   3:     _persistenceModel.WriteMappingsTo(path);
   4: }

 

As for our mapping configuration, NHMapping, this is where we'll eventually add specific configurations overriding Fluent NHibernate's default settings. But for now it's just that one line of code. The reason is returns an AutoPersistenceModel is because this is what we pass into our FluentMapper to actually do the work.

 

   1: public static AutoPersistenceModel GetMapping()
   2: {
   3:     return AutoPersistenceModel.MapEntitiesFromAssemblyOf<Customer>();
   4: }

 

Generating Mapping Files

Once we put all this code together, we can see the mapping Fluent NHibernate generates for us. To make things easier, I've included a MSBuild task (FluentTask) that does this, along with creating/recreating databases if required (works great as part of an integration test). Here's a sample MSBuild project file to generate the mappings for us:

 

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
   3:   <UsingTask AssemblyFile="Example.FluentHelpers.dll" TaskName="FluentTask" />
   4:   <Target Name="Default">
   5:     <FluentTask
   6:       PersistenceModel="Example.Repository.dll, Example.Repository.NHMappings, GetMapping"
   7:       ConnectionString="Data Source=.\SQLEXPRESS;Initial Catalog=FluentExample;Integrated Security=SSPI"
   8:       MappingOutputFolder="C:\Temp"/>
   9:   </Target>
  10: </Project>

 

For illustrative purposes (i.e. this is NOT needed if you're using Fluent NHibernate), let's see the generated output for our classes:

Customer.hbm

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Example.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="Example.Domain">
   3:   <class name="Customer" table="`Customer`" xmlns="urn:nhibernate-mapping-2.2">
   4:     <id name="Id" type="Int32" column="Id">
   5:       <generator class="identity" />
   6:     </id>
   7:     <property name="NameFirst" type="String">
   8:       <column name="NameFirst" />
   9:     </property>
  10:     <property name="Telephone" type="String">
  11:       <column name="Telephone" />
  12:     </property>
  13:     <property name="Email" type="String">
  14:       <column name="Email" />
  15:     </property>
  16:     <property name="NameLast" type="String">
  17:       <column name="NameLast" />
  18:     </property>
  19:     <set name="Documents">
  20:       <key column="Customer_id" />
  21:       <one-to-many class="Example.Domain.Document, Example.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
  22:     </set>
  23:   </class>
  24: </hibernate-mapping>

 

Document.hbm

 

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Example.Domain, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" namespace="Example.Domain">
   3:   <class name="Document" table="`Document`" xmlns="urn:nhibernate-mapping-2.2">
   4:     <id name="Id" type="Int32" column="Id">
   5:       <generator class="identity" />
   6:     </id>
   7:     <property name="Author" type="String">
   8:       <column name="Author" />
   9:     </property>
  10:     <property name="Date" type="DateTime">
  11:       <column name="Date" />
  12:     </property>
  13:     <property name="Contents" type="String">
  14:       <column name="Contents" />
  15:     </property>
  16:     <property name="Code" type="String">
  17:       <column name="Code" />
  18:     </property>
  19:   </class>
  20: </hibernate-mapping>

 

The interesting part is line 19 - 22 of the Customer.hbm. What this is saying is that there is a relationship of one to many from Customer to Document, indicating the class corresponding to Document. It also indicates that the foreign key in the Document table is Customer_id. To create the database, we can add the attribute DatabaseOperation="Create" to our MSBuild task and it will create the database for us:

 

image

 

Overriding specific mappings

As you may have noticed, all of the string types are automatically mapped to nvarchar(255). This is what NHibernate does by default if a length attribute isn't specified in the property mapping. Considering our Contents property for Document requires a larger amount of data to be stored, we need to override this. For these cases, Fluent NHibernate allows you to override a specific mapping of an entity by using the ForTypesThatDeriveFrom method:

 

   1: public static AutoPersistenceModel GetMapping()
   2: {
   3:      return AutoPersistenceModel.MapEntitiesFromAssemblyOf<Customer>()
   4:          .ForTypesThatDeriveFrom<Document>
   5:            ( m => m.Map(p => p.Contents).WithLengthOf(3000));
   6: }

 

In this particular case, we're telling Fluent Nhibernate, that when mapping Document (and it's descendants), the property Contents should be mapped with a length of 3000 characters. Any other property not specified will still be mapped using the defaults. Making this change, we'll end up with a database column of type nvarchar(3000), with everything else, exactly the same. 

 

 

Summary

In this second part, we defined a relationship between two entities and Fluent NHibernate figured out the rest for us. As we continue with this series, we'll see that as our entity model becomes more complex there are situations where we have to "help" Fluent NHibernate a little bit with the mappings. 

 

Credits

I'd like to thank James Gregory, not only for the wonderful work he's doing with Fluent NHibernate, but also for reviewing these entries to make sure I haven't said something stupid and give the wrong information to my readers.