Skip to content

Automapping inheritance

jagregory edited this page Oct 14, 2012 · 2 revisions

There are two main things that you'd want to do with inherited classes, either ignore the base class all together, or map them using an inheritance strategy. I'm going to start with the former, then move on to the latter.

Ignoring base-types

This scenario is where you may have a base class in your domain that you use to simplify your entities, you've moved common properties into it so you don't have to recreate them on every entity; typically this would be the Id and perhaps some audit information. So lets start with a model that has a base class we'd like to ignore.

namespace Entities
{
  public abstract class Entity
  {
    public virtual int Id { get; set; }    
  }

  public class Person : Entity
  {
    public virtual string FirstName { get; set; }
    public virtual string LastName { get; set; }
  }

  public class Animal : Entity
  {
    public virtual string Species { get; set; }
  }
}

Relatively simple model here, we've got an Entity base class that defines the Id, then the Person and Animal entities. We have no desire to have Entity mapped by NHibernate, so we need a way to tell the auto mapper to ignore it.

For those individuals from traditional XML mapping land, this is what we're going to be recreating:

<class name="Person">
  <id name="Id">
    <generator class="identity" />
  </id>
  
  <property name="FirstName" />
  <property name="LastName" />
</class>
    
<class name="Animal">
  <id name="Id">
    <generator class="identity" />
  </id>

  <property name="Species" />
</class>

We'll start with this automapping setup:

AutoMap.AssemblyOf<Entity>(cfg);

If we were to run this now, we wouldn't get the mapping we desire. Fluent NHibernate would see Entity as an actual entity and map it with Animal and Person as subclasses; this is not what we desire, so we need to modify our auto mapping configuration to reflect that.

You can ignore base types by simply excluding them from your ShouldMap(Type) method, that's sometimes the cleanest option; however, if you want to be a bit more explicit you can use the IgnoreBase method.

After AutoMap.AssemblyOf<Entity>() we need to alter the conventions that the auto mapper is using so it can identify our base-class.

AutoMap.AssemblyOf<Entity>(cfg)
  .IgnoreBase<Entity>();

We've added the IgnoreBase<Entity> call which simply instructs the automapper to ignore the Entity class; you can chain this call as many times as needed.

With this change, we now get our desired mapping. Entity is ignored as far is Fluent NHibernate is concerned, and all the properties (Id in our case) are treated as if they were on the specific subclasses.

Base-type as an inheritance strategy

If you want to have your base-class included in NHibernate, then just don't include the IgnoreBase above! Easy.

Configuring the subclassing strategy

Fluent NHibernate defaults to using table-per-subclass strategy for automapping inheritance hierarchies. If this is not what you want, then you can modify your configuration to specify with types use a discriminator (and therefore are table-per-hierarchy).

If you always want table-per-hierarchy subclassing, then you just need to override the IsDiscriminated configuration method to always return true

public override bool IsDiscriminated(Type type)
{
  return true;
}

If you wanted to customise it on a per-class basis, you'd set it up like so:

public override bool IsDiscriminated(Type type)
{
  return type.In(typeof(ClassOne), typeof(ClassTwo));
} 

That will make any subclasses of ClassOne and ClassTwo share their parent table, rather than being in their own tables. All other entities will have their own tables for subclasses.

Subclassing FAQs

Abstract base-classes

You'll notice that our Entity class is abstract. This is good practice, but for the record, it is not mandatory. If you're experiencing problems, it's unlikely to be this.

In case you're wondering, making the class abstract is like saying "I'll never create this directly, instead I will create derived classes such as Customer and Order (which inherit from Entity)."

The default behavior is to consider abstract classes as layer supertypes and effectively unmapped, you may want to change this for specific scenarios. The easiest way to do this is to use IncludeBase<T>, where T is your entity.

AutoMap.AssemblyOf<Entity>(cfg)
  .IncludeBase<AbstractBaseClass>();

This forces the automapper to include that base-type, regardless of it's previous assumptions about it.