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.
Hi! Thanks for your post you saved me :-)
ReplyDeleteFollowing your approach I have implemented alse the HasManyToMany convention, (I'm not sure if it is really necessary however).
Glad to have been of help
DeleteVery good article, congratulations!
ReplyDeleteMaybe you can help me understand something. Your arbodagem worked for me!
Before I was trying to map an interface "IUser" to the concrete class "User" without success! See the reference code: http://chopapp.com/#rqhgewsr
Why yours approach works and mine does not?
Hi Riderman, I am using an IHasManyConvention where as your implementation uses an IUserTypeConvention. I don't know if you can achieve the same thing using the User Type Convention. Maybe you can but I have not tried it.
DeleteHi !
ReplyDeleteJust wanted to say that your post saved my life. Thank you very much! :)