Monday, November 28, 2011

Automapping with Fluent NHibernate: ShouldMap() implementation

When you use the automapping feature of Fluent NHibernate you will very quickly encounter a class called DefaultAutomappingConfiguration. This is the base class for your own automapping configuration class which you want to implement to control certain aspects of the automapping feature.

During startup of your application you have to provide NHibernate with information about how to map your domain classes to the database schema and back. Fluent NHibernate allows doing this automatically. Well, most of the time. There are cases when you want to be a little more specific with regards to those automatically created mappings.

The code you want to execute during startup looks similar to the following:

private static ISessionFactory CreateSessionFactory() {
  var rawConfig = new Configuration();
  rawConfig.SetNamingStrategy(new PostgresNamingStrategy());
  var sessionFactory = Fluently.Configure(rawConfig)
    .Database(PostgreSQLConfiguration.PostgreSQL82
      .ConnectionString(Common.DatabaseConnectionString))
    .Mappings(m => m.AutoMappings
      .Add(AutoMap
        .AssemblyOf<Customer>(new DbMappingConfiguration())
      .Conventions.Add(ForeignKey.EndsWith("Id"))));
   return sessionFactory.BuildSessionFactory();
}

The interesting piece in this case is line 9 where you provide the assembly that contains all your domain classes that you want to map. In this case you only need to provide one such class and Fluent NHibernate will also try to automatically map all other types that it can find in that assembly. Sometimes this is exactly what you want. In other cases you may not want to map all of the classes. Enter the DefaultMappingConfiguration class.

You can implement you own mapping configuration by deriving from DefaultMappingConfiguration. You can override just the aspects that you want to change, everything else is taken care of by the default implementation.

In this post I want to show one way for controlling which classes are mapped. A simplistic approach would be implementing your custom mapping configuration as follows:

public class DbMappingConfiguration 
    : DefaultAutomappingConfiguration {
  public override bool ShouldMap(Type type) {
    return type.Equals(typeof(Customer))
      || type.Equals(typeof(Order))
      || type.Equals(typeof(Address))
      || type.Equals(typeof(Invoice));
  }
}

This works. However as you can imagine this is not very intuitive and you will have to maintain this code as you modify your domain. Each time you add, remove or rename any of your domain classes you will have to update this method (admittedly the renaming is not a problem if you use a refactoring tool).

So let’s think of a better solution. The one I want to present here is based on a marker attribute. The implementation PersistentDomainClassAttribute is very simple:

[AttributeUsage(AttributeTargets.Class, 
                AllowMultiple = false, 
                Inherited = false)]
public class PersistentDomainClassAttribute 
  : Attribute {
}

The usage is fairly simple, too. Here is an example:

[PersistentDomainClass]
public class Customer {
  public virtual Guid Id { get; private set; }
  public virtual string FirstName { get; set; }
  public virtual string LastName { get; set; }
}

Having this in place on all domain classes the custom mapping configuration can now be simplified as follows using a generic implementation:

public class DbMappingConfiguration 
   : DefaultAutomappingConfiguration {
  public override bool ShouldMap(Type type) {
    var attr = type.GetCustomAttributes(
                  typeof(PersistentDomainClassAttribute),
                  true);
    return attr.Length > 0;
  }
}

As a result you now have a solution that lets you choose which domain classes to make persistent. At the same time you no longer have to maintain the ShouldMap() implementation of your custom mapping configuration either.

1 comments:

Rob Angelier said...

You could also just derive from a base class and check the baseType property inside the ShouldMap method.

Post a Comment

All comments, questions and other feedback is much appreciated. Thank you!