Reusable Mappings
Something that slightly bothered me with the XML mappings of NHibernate, is the fact that some things need to be configured over and over again. Behold the following two mapping files:
Catalog:
<class name="Catalog" table="`Catalog`" optimistic-lock="version"> <id name="Id" column="Id" type="Guid"> <generator class="guid.comb" /> </id> <version column="Version" name="Version" /> <property name="Name" length="100" type="String"> <column name="Name" /> </property> ... </class>
CatalogCategory:
<class name="CatalogCategory" table="`CatalogCategory`" optimistic-lock="version"> <id name="Id" column="Id" type="Guid"> <generator class="guid.comb" /> </id> <version column="Version" name="Version" /> <property name="Description" length="100" type="String"> <column name="Description" /> </property> ... </class>
Suppose we have the luxury to choose how we deal with the database in our project. I do realize that’s not always feasible because at some point in our careers, we’ve all seen what database masochism can do. Anyway, if would be making the shots, I would like to use a surrogate key for every table and I would recommend sequential GUIDs (called COMBs) for that.
The mapping of the classes shown above both have the same configuration for the Id and Version properties. These properties typically live in a DomainEntity base class of some sort because we don’t want to repeat that tedious code of putting those in every entity of our domain over and over again. It would be nice if we could somehow do the same for the NHibernate mapping files, which we can’t (or at least, not that I know of).
Using Fluent NHibernate we can create an abstract mapping class for our DomainEntity from which we derive all entity mapping classes. The following code would give us the same result as the XML mapping files shown above:
DomainEntityMapping:
public abstract class DomainEntityMapping<TDomainEntity> : ClassMap<TDomainEntity> where TDomainEntity : DomainEntity { protected DomainEntityMapping() { Id(entity => entity.Id, "Id") .GeneratedBy.GuidComb(); OptimisticLock.Version(); Version(entity => entity.Version) .TheColumnNameIs("Version"); } }
CatalogMapping:
public class CatalogMapping : DomainEntityMapping<Catalog> { public CatalogMapping() { Map(catalog=> catalog.Name, "Name"); } }
CatalogCategoryMapping:
public class CatalogCategoryMapping : DomainEntityMapping<CatalogCategory> { public CatalogCategoryMapping() { Map(category=> category.Description, "Name"); } }
This way, I can specify the mapping configuration for Id and Version in a single place which is really nice.
Mapping Tests
This is probably my most favorite feature of Fluent NHibernate. Given the fluent mapping configuration for the Category class shown earlier, the following test checks whether this mapping is valid or not:
[TestFixture] public class When_verifying_the_class_mapping_of_a_catalog : NHibernateSpecification { [Test] public void Then_a_catalog_object_should_be_persistable() { new PersistenceSpecification<Catalog>(Session) .VerifyTheMappings(); } }
Running this test results in the following SQL statements being executed to an in-memory SQLite database:
NHibernate: INSERT INTO “Catalog” (Version, Name, Id) VALUES (@p0, @p1, @p2); @p0 = ‘1’, @p1 = ”, @p2 = ‘c52126cb-f11e-47e4-a481-9bc600134d39’
NHibernate: SELECT catalog0_.Id as Id1_0_, catalog0_.Version as Version1_0_, catalog0_.Name as Name1_0_ FROM “Catalog” catalog0_ WHERE catalog0_.Id=@p0; @p0 = ‘c52126cb-f11e-47e4-a481-9bc600134d39’
The following code shows the NHibernateSpecification base class for all my database tests. The code in this class deals with setting up the in-memory SQLite database and building the required schema based on the mappings. The Fluent NHibernate framework gives some really nice support for this as well:
[TestFixture] public class NHibernateSpecification : Specification { protected ISession Session { get; private set; } protected override void Establish_context() { var config = new SQLiteConfiguration() .InMemory() .ShowSql() .ToProperties(); var sessionSource = new SessionSource(config, new RetailerPersistenceModel()); Session = sessionSource.CreateSession(); sessionSource.BuildSchema(Session); ProvideInitialData(Session); Session.Flush(); Session.Clear(); } protected override void Dispose_context() { if(null != Session) { Session.Dispose(); Session = null; } } }
Fluent NHibernate really lowers the barrier for configuring NHibernate, which is a really big thing in my book. I must admit that I was a bit sceptical at first, but now I noticed that I’m having a hard time going back to the standard XML mapping configuration of NHibernate itself. As Fluent NHibernate is still in an early development stage, some issues can come up. But my personal experience so far is that the user group is very responsive.
Thanks for this one… it seems that I need to be become fluent in Fluent NH 😉
An out of the box question:
How COMB GUIDs works for you for joins ?
In his (quite dated) article Jimmy Nilsson concentrates on insert operation, whereas what you really need is to check updates / joins too.
I’ve went back to ints as joins on huge tables were significantly (ca. 200%) slower. Please drop a comment…
using System;
using System.Collections.Generic;
using NHibernate.Mapping.Attributes;
namespace FirePaymentLib
{
[Serializable]
[Class(Name = “FirePaymentLib.OrderDetail, FirePaymentLib, Culture=neutral”, Table = “FDP_OrderDetail”, Lazy = false)]
public class OrderDetail
{
[Id(0, Name = “Id”, TypeType = typeof(Int32), UnsavedValue = “0”)]
[Column(1, Name = “Id”, SqlType = “int”, Index = “PK_OrderDetail_Id”, Unique = true, NotNull = true)]
[Generator(3, Class = “identity”)]
public virtual int Id { get; private set; }
[Property(Length = 50)]
public virtual string OrderNumber { get; set; }
[Property]
[Column(1, Name = “AmountTotal”, SqlType = “DECIMAL(19,4)”)]
public virtual decimal AmountTotal { get; set; }
[Property(0, Name = “CreatedOn”)]
[Column(1, Name = “CreatedOn”, NotNull = true)]
public virtual DateTime CreatedOn { get; set; }
}
oops, nuked my comment.. as you can see, using NHibernate.Mapping.Attributes makes it very easy to define the HBM stuff within the C# entity class itself.
Check it out sometime.
@stic: I once had a large table where the performance of GUIDs was not optimal. If I remember correctly, we solved this by providing both an INT and a GUID column with a unique index. But this was a very specific scenario. Don’t ever saw the same problem again with modern databases where GUIDs are first class citizens. The DB we where using at the time was Sybase 11.2 or something.
@mycall: I don’t use NH attributes as they violate the Persistence Ignorance of my domain.
Hi, thanks for the post, gave me some good ideas.
Could you post your DomainEntity base class? I’m struggling with how to initialise the Guid in the default constructor. Do you use just Guid.NewGuid()?