Jul 9, 2010

Mapping inheritance hierarchies with Fluent NHibernate

A few days ago I briefly spiked mapping inheritance hierarchies with Fluent NHibernate. The result was so compelling I just have to post about it.

Based on our model we decided to go with class table inheritance. There is a brief discussion of the three well known mapping strategies in NHibernate in Action. What it boils down to is the differences in the classes. If they mostly differ by behavior class table inheritance is recommended, if the differ mostly by data than go with concrete table inheritance. Most of our differences are in behavior so we chose the former.

I used the classic sports player mode for the spike – an abstract Player class other player subclass, like a Base ballplayer for instance. I omitted model behavior from the spike since I focused on the mapping issue. The model looks like this:

    public abstract class Player
    {
        public virtual int Id { get; set; }
        public virtual string Name { get; set; }
        public virtual int Age { get; set; }
    }

    public class BaseballPlayer : Player
    {
        public virtual double BattingAverage { get; set; }
    }

This can be mapped manually, however is very tedious code that adds little business value to our customer. Writing this code by hand would also require a fairly significant investment in infrastructure. Instead we can map using Fluent NHibernate to it, like this:

    public class PlayerClassMap : ClassMap<Player>
    {
        public PlayerClassMap()
        {
            Id(x => x.Id);
            Map(x => x.Name);
            Map(x => x.Age);
        }
    }
    
    public class BaseballPlayerClassMap : SubclassMap<BaseballPlayer>
    {
        public BaseballPlayerClassMap()
        {
            Map(x => x.BattingAverage);
        }
    }

Believe it or not this all that is required to map the inheritance hierarchy above. Testing the mappings is also trivial using Fluent NHibernate’s PersitenceSpecification<T>. Here’s what the test code looks like:

    [TestFixture]
    public class BaseballPlayerClassMapTest : DatabaseTest
    {
        [Test]
        public void Should_do_something()
        {
            new PersistenceSpecification<BaseballPlayer>(session)
                .CheckProperty(x => x.Name, "Joe")
                .CheckProperty(x => x.Age, 23)
                .CheckProperty(x => x.BattingAverage, 0.99)
                .VerifyTheMappings();
        }
    }

This will create a BaseballPlayer, save it to the database, load from the database and verify all the mappings. If we look at the SQL statements generated we something like this:

INSERT INTO "Player" (Name, Age) VALUES (@p0, @p1); select last_insert_rowid();@p0 = 'Joe', @p1 = 23
INSERT INTO "BaseballPlayer" (BattingAverage, Player_id) VALUES (@p0, @p1);@p0 = 0.99, @p1 = 1
SELECT baseballpl0_.Player_id as Id9_0_, baseballpl0_1_.Name as Name9_0_, baseballpl0_1_.Age as Age9_0_, baseballpl0_.BattingAverage as BattingA2_10_0_ FROM "BaseballPlayer" baseballpl0_ inner join "Player" baseballpl0_1_ on baseballpl0_.Player_id=baseballpl0_1_.Id WHERE baseballpl0_.Player_id=@p0;@p0 = 1

The table structure NHibernate will generate is below.

image

Our test fixtures subclass a DatabaseTest class which constructs the session and generates the database schema from our model. We use SQLite for in-memory testing which makes out integration tests pretty fast. Here’s the code for the base class.

 

    public class DatabaseTest
    {
        private ISessionFactory sessionFactory;
        protected ISession session;
        private readonly FluentConfiguration configuration;
        private readonly Configuration config;

        public DatabaseTest()
        {
            config = new Configuration();
            configuration = Fluently.Configure(config)
                .Database(SQLiteConfiguration.Standard.InMemory().ShowSql())
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<HibernateSessionFactoryBuilder>());
        }

        [SetUp]
        public void Setup()
        {
            sessionFactory = GetSessionfactory();
            session = sessionFactory.OpenSession();
            BuildSchema();
        }

        [TearDown]
        public void TearDown()
        {
            session.Close();
            session.Dispose();
        }

        private void BuildSchema()
        {
            var export = new SchemaExport(config);
            export.Execute(false, true, false, session.Connection, null);
        }  

        private ISessionFactory GetSessionfactory()
        {
            return configuration.BuildSessionFactory();
        }
    }

This one could maybe use some cleanup but that’s all we need to do to map our model to the database. We can now focus on delivering business value and not spend too much time on writing mappers and other infrastructure code.