Sunday, 2 January 2011

Making Fluent NHibernate AutoMapper behave with interfaces

I’ve been an avid user of NHibernate for some time now either using the built in class maps or Castles ActiveRecord. It hasn’t been until recently that I have started using Fluent NHibernate and have been wowed by how easy it is to do the class mappings with the ClassMap base class. This is an improvement over ActiveRecord where domain objects are tightly bound to ActiveRecord because of the decorators but you still have strongly typed mappings which will pick up most issues at compile time whereas the NHibernate class maps will only throw up an issue at runtime. One of the things that I really like about Fluent is how easy it is to use interface references between entities, take the following entity structure for example:

public Interface IFoo { }
 
public Interface IBar { IFoo Foo { get; set; } }
 
public Class Foo : IFoo { }
 
public Class Bar : IBar
{
     public IFoo Foo { get; set; }
}
 
public class BarMapping : ClassMap<Bar> { }

This would result in an “Association references unmapped class: IFoo” exception. This can simply be fixed by adding a reference mapping to the class map:

public class BarMapping : ClassMap<Bar>
{
    public BarMapping()
    {
        References<Foo>( x => x.Foo ).Column( "FooId" ).Cascade.None();
    }
}

This fixes the exception and allows the entity to be correctly mapped to the concrete type and everything was right in the world, or it was until a colleague suggested using AutoMapper to remove the need of have ClassMap implementations. This seemed like it was worth a try but I couldn’t help feeling that I was losing a certain amount of control.

public class FooBarPersistenceModel : AutoPersistenceModel
{
    public FooBarPersistenceModel ()
    {
        AddEntityAssembly(typeof(Foo).Assembly)
            .Where(x => x.Namespace == typeof(Foo).Namespace)
            .BuildMappings();
    }
}

The initial implementation with basic entities worked fine but as soon as we tried anything more complicated such as referencing entity interfaces instead of concrete classes we were back to our old friend “Association references unmapped class”, and if we were having trouble doing this what else was going to be a problem. We were at the point of going back to using the ClassMap when I decided to do a bit more digging and realised how much I misunderstood AutoMapper, it isn’t about mapping entities straight to their related tables but about mapping entities to their related tables via a series of conventions. The reason this was lost on me was because AutoMapper is already wired up with some basic conventions so it works out of the box and with very little documentation or examples it wasn’t easy to see how to do something I had previously done another way.

With this knowledge in hand, some basic examples of AutoMapper conventions and the help of Reflector I was able to work out how to map an entity with references to an interface:

public class FooBarPersistenceModel : AutoPersistenceModel
{
    public PlantAutomationPersistenceModel()
    {
        AddEntityAssembly(typeof(Foo).Assembly)
            .Where(x => x.Namespace == typeof(Foo).Namespace)
            .Conventions.Add(new ReferenceConvention())
            .BuildMappings();
    }
}
 
public class ReferenceConvention : IReferenceConvention
{
    public void Apply(IManyToOneInstance instance)
    {
        Type instanceType = instance.Class.GetUnderlyingSystemType();
        if (instanceType = typeof(IFoo))
        {
            instance.CustomClass<Foo>();
        }
 
        instance.Cascade.All();
    }
}
 
//Or a more generic approach might be:
 
public class ReferenceConvention : IReferenceConvention
{
    public void Apply(IManyToOneInstance instance)
    {
        Type instanceType = instance.Class.GetUnderlyingSystemType();
 
        if (instanceType.IsInterface)
        {
            // Assuming that the type starts with an I get the name of the concrete class
            string className = instanceType.Name.Substring( 1 );
 
            instance.CustomClass(instanceType.Assembly.GetType(
                instanceType.FullName.Replace( instanceType.Name, className )));
        }
 
        instance.Cascade.All();
    }
}
 

These classes use the IReferenceConvention interface and if you navigate the FluentNHibernate.Conventions namespace you will see that there are a plethora of conventions. You also have the choice of using your own implementation or a convention builder, the builder is quite nice but if you want reuse then I would opt for a custom implementation.

Everything worked fine until we had an entity that referenced a collection and then we were back to the “Association references unmapped class” exception again. I was in a bit of a rush and couldn’t get the IReferenceConvention to work nor the IHasManyConvention that I had just found. I was about to give up when I came across the IAutoMappingOverride<TEntity> interface.

public Interface IWibble { }
 
public Interface IWobble
{
    IList<IWibble> Wibbles { get; }
}
 
public class Wibble : IWibble { }
 
public class Wobble : IWobble
{
    public IList<IWibble> Wibbles { get; set; }
}
 
public class WobbleAutoMappingOverride : IAutoMappingOverride<Wobble>
{
    public void Override(AutoMapping<Wobble> mapping)
    {
        mapping.HasMany<Wibble>( x => x.Wibbles );
    }
}

It wasn’t perfect but it was a quick fix until my colleague finally found the solution for getting the IHasManyConvention interface to work:

public class ReferenceConvention : IHasManyConvention
{
    public void Apply(IOneToManyCollectionInstance instance)
    {
        var instanceType = instance.ChildType;
        if (instanceType.IsInterface)
        {
           var className = instanceType.Name.Substring(1);
           instance.Relationship.CustomClass(instanceType.Assembly.GetType(instanceType.FullName.Replace(instanceType.Name, className)));
           instance.Cascade.All();
        }
        instance.Cascade.All();
    }
}

As you will see there are many conventions that can be applied which makes AutoMapper very flexible and a better alternative to using the ClassMap base class.