Software Twist – Adrian Tosca Blog

Combining Explicit Mappings and Conventions in Fluent NHibernate

Posted in Software Development by Adrian Tosca on 2009, March 28

Fluent NHibernate has got a lot of hype recently and for good reasons: the XML configuration is not the funniest part of using Hibernate. The big advantage of the framework is the ability to build up the NHibernate configuration in a type safe manner. But on top of that, the library has a number of features that greatly simplifies the initial work of configuration.

Automapping

The auto mapper mechanism can automatically map all the entities in the domain based on a set of conventions. 

ISessionFactory sessionFactory =
    Fluently.Configure()
        .Database(MsSqlConfiguration.MsSql2005.ConnectionString(
            cs => cs.Is("connection string"))
        .Mappings(m => m.AutoMappings.Add(
            AutoPersistenceModel.MapEntitiesFromAssemblyOf<Location>()))
        .BuildSessionFactory();

And that’s all. In this example the database is configured with SqlServer 2005 but it can be anything else of course. You get a set of of mappings based on a set of standard conventions. The auto-mapper has the possibility to override the default conventions. Unfortunately it is not always easy to both have a set of conventions and make adjustments where needed. In theory this sounds great but in practice there are always exceptions and before you know it you find yourself with a lot of weird ‘conventions’ that are made only to resolve a particular problem. Overriding the conventions only when needed also sound great but it can become a nightmare to maintain.

In practice a compromise between manual mappings and limited a set of conventions might be a better approach most of the time.

Mappings and conventions

The compromise is to build the normal mappings but only with the non repetitive code that is better to stay in conventions. You could have:

    public class Location {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual Country Country { get; set; }
    }

    public class LocationMapping : ClassMap<Location> {
        public LocationMapping() {
            Id(e => e.Id);
            Map(e => e.Name).Unique();
            References(e => e.Country).Not.LazyLoad();
        }
    }

And then have a number of conventions that are general for all the entities. For example we could have all id’s generated with the GuidComb generator:

    public class PrimaryKeyGeneratorConvention : IIdConvention {
        public bool Accept(IIdentityPart id) {
            return true;
        }
        public void Apply(IIdentityPart id) {
            id.GeneratedBy.GuidComb();
        }
    }

And all columns that are named ‘Name’ could have a certain storage column length:

    public class PropertyNameLengthConvention : IPropertyConvention {
        public bool Accept(IProperty property) {
            return property.Property.Name == "Name";
        }
        public void Apply(IProperty property) {
            property.WithLengthOf(255);
        }
    }

The following code will generate a configuration that reads the mappings and apply the conventions:

ISessionFactory sessionFactory =
    Fluently.Configure()
        .Database(MsSqlConfiguration.MsSql2005.ConnectionString(
            cs => cs.Is("connection string"))
        .Mappings(m => m.FluentMappings.AddFromAssemblyOf<LocationMapping>()
        .ConventionDiscovery.AddFromAssemblyOf<PrimaryKeyGeneratorConvention>()
        .BuildSessionFactory();

I found that is better to avoid combining the convention in the same class even if they refer to the same piece of configuration. For example would be better to just make another convention PrimaryKeyNamingConvention if the primary key should be named ‘Id’.