Using Fluent NHibernate ClassMaps? You're Doing It Wrong.

After some internal conversations at work, I tweeted that using ClassMaps with Fluent NHibernate is the the wrong way to approach the problem. This of course was a snarky in the moment tweet as is the title of this post, but I do actually think that auto mapping is the best usage pattern for Fluent NHibernate. And I wanted to expand on that a bit and share my reasoning.

The primary reason people use Fluent NHibernate is to get away from xml based mapping files that look like this.

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    namespace="QuickStart" assembly="QuickStart">
    <class name="Cat" table="Cat">
        <!-- A 32 hex character is our surrogate key. It's automatically
            generated by NHibernate with the UUID pattern. -->
        <id name="Id">
            <column name="CatId" sql-type="char(32)" not-null="true"/>
            <generator class="uuid.hex" />
        </id>
        <!-- A cat has to have a name, but it shouldn't be too long. -->
        <property name="Name">
            <column name="Name" length="16" not-null="true" />
        </property>
        <property name="Sex" />
        <property name="Weight" />
    </class>
</hibernate-mapping>

All of this xml goo can be replaced with a simple ClassMap that allows you to take advantage of intellisense (which technically you don't lose with hbm files if you have your project setup right). It would look something like this.

public class CatMap : ClassMap<Cat>
{
    public CatMap()
    {
        Id(x => x.Id).GeneratedBy.UuidHex("B");
        Map(x => x.Name)
            .WithLengthOf(16)
            .CanNotBeNull();
        Map(x => x.Sex);
        Map(x => x.Weight);
    }
}

What have we gained by doing this? The ClassMap is just as terse as the xml. We have simply avoided having to soil our fingers with the act of typing xml. The real power with FNH is when we start moving toward convention over configuration. Our mapping then becomes as simple as this.

AutoMap.AssemblyOf<IEntity>();

This will map all of the types located in the assembly containing the type IEntity. But let's say that you want to constrain the mapping to only types in that assembly that implement the interface IEntity or are in a specific namespace. We can specify that by providing an implementation of DefaultAutoMappingConfiguration like this.

public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
    public override bool ShouldMap(Type type)
    {
        return type.Namespace.StartsWith("Core.Model") && 
               type.GetInterfaces().Any(y => y == typeof(IEntity));

    }

}

AutoMap.AssemblyOf<IEntity>(new AutomappingConfiguration());

There are several methods that can be overridden globally via the automapping configuration like this. By default FNH will assume any property with the name Id is the unique identifier for that type. Maybe for some reason you want to be the property named Guid. Simply add this override to your configuration.

public class AutomappingConfiguration : DefaultAutomappingConfiguration
{
    public override bool IsId(Member member)
    {
        return member.Name == "Guid";
    }
}

FNH comes with a set of sensible defaults but they can be easily replaced with conventions that better match your particular database schema. In a recent project that I was on, the DBA used schemas heavily. I decided to partition my domain model using the same schema structure and put my entities in different namespaces. I then added the following convention to my automapping.

public class TableNameConvention : IClassConvention
{
    public void Apply(IClassInstance instance)
    {
        var schema = instance.EntityType.Namespace.Split('.').Last();
        var typeName = instance.EntityType.Name;
        var tableName = "[{0}].t_{1}".FormatWith(schema, typeName);

        instance.Table(tableName);
    }
}
AutoMap.AssemblyOf<IEntity>(new AutomappingConfiguration())
                          .Conventions.AddFromAssemblyOf<AutomappingConfiguration>()

The auto mapper call then just needs know where to pick up the custom conventions. Maybe your database does not completely follow a standard convention. Maybe it has two completely different conventions for table naming. Feel free to add as many implementations of IClassConvention as you need and then specify what they apply to by implementing IClassConventionAcceptance as well like this. Each of the convention interfaces have a corresponding acceptance interface.

public class TableNameConvention : IClassConvention, IClassConventionAcceptance
{
    public void Apply(IClassInstance instance)
    {
        var schema = instance.EntityType.Namespace.Split('.').Last();
        var typeName = instance.EntityType.Name;
        var tableName = "[{0}].t_{1}".FormatWith(schema, typeName);

        instance.Table(tableName);
    }

    public void Accept(IAcceptanceCriteria<IClassInspector> criteria)
    {
        criteria.Expect(x => x.Name.StartsWith("foo"));
    }
}

By far the most common response I got to my twitter sniping was the "My database is special and I have to use class maps to map it because it's so weird" excuse. And I completely sympathize with inheriting a messy database schema. For each of the types that do not map cleanly you can provide an override like this.

public class SecurityOverride : IAutoMappingOverride<Security>
{
    public void Override(AutoMapping<Security> mapping)
    {
        mapping.Table("[PortfolioData].[t_LocalSecurityData]");
        mapping.Id(x => x.ID, "SecurityID");
        mapping.Map(x => x.Name, "SecurityName");
        mapping.Map(x => x.Type, "SecurityType");
    }
}

AutoMap.AssemblyOf<IEntity>(new AutomappingConfiguration())
                          .Conventions.AddFromAssemblyOf<AutomappingConfiguration>()
                          .UseOverridesFromAssemblyOf<AutomappingConfiguration>();

Notice that the auto mapping override looks surprisingly like a class map. In fact anything you can do in a class map you can do with an override. So why not choose today to begin introducing sensible conventions to your data model? Use auto mapping to map new tables and tables that get refactored to the new convention based schema. This puts you in a good place to make improvements on your schema easily instead of simply replacing xml configuration with c# class map configuration.

Follow me on Mastodon!