Next article in Ayende's NHibernate mapping series is about inheritance mappings. As we know, there are few different approaches to inheritance in database. Let's see how we can map it using NHibernate's 3.2 mapping-by-code.
Let's use the same model as Ayende did:![]()
Single table
And let's begin with single table approach (table per hierarchy):
public class PartyMap : ClassMapping<Party>
{
public PartyMap()
{
Id(x => x.Id, i => i.Generator(Generators.HighLow));
Discriminator(x =>
{
x.Force(true);
x.Formula("arbitrary SQL expression");
x.Insert(true);
x.Length(12);
x.NotNullable(true);
x.Type(NHibernateUtil.String);
x.Column("discriminator");
// or...
x.Column(c =>
{
c.Name("discriminator");
// c.Length(21);
// c.NotNullable(false);
// etc.
});
});
}
public class CompanyMap : SubclassMapping<Company>
{
public CompanyMap()
{
DiscriminatorValue("Company");
Property(x => x.CompanyName);
}
}
public class PersonMap : SubclassMapping<Person>
{
public PersonMap()
{
DiscriminatorValue("Person");
Property(x => x.FirstName);
}
}
}
Single table inheritance mapping consists of two parts - discriminator mapping and subclasses mappings.
Discriminator column is mapped with Discriminator method. Mapping options offered by this method looks a bit messy. Length and NotNullable methods are available both in the Column method options (as in other mappings) and in the discriminator options directly. It looks like they are equivalent, what's called last "wins".
Mapping of subclasses is done through separate mapping classes that inherit from SubclassMapping<T>.
An important thing to remember when mapping inheritance using mapping-by-code is that when registering our mappings we should not look for ClassMapping<T> inheritors only - SubclassMapping<T> (and JoinedSubclassMapping<T> and UnionSubclassMapping<T>, mentioned later) do not inherit from ClassMapping<T>. The solution I'm using is to look for all implementations of IPropertyContainerMapper<T>.
Fluent NHibernate's equivalent for single table
Single table inheritance mapping is almost identical in Fluent NHibernate to mapping-by-code:
public class PartyMap : ClassMap<Party>
{
public PartyMap()
{
Id(x => x.Id).GeneratedBy.HiLo(maxLo: "100");
DiscriminateSubClassesOnColumn("discriminator")
.AlwaysSelectWithValue()
.Formula("arbitrary SQL expression")
.ReadOnly()
.Length(12)
.Not.Nullable()
.CustomType<string>();
}
public class CompanyMap : SubclassMap<Company>
{
public CompanyMap()
{
DiscriminatorValue("Company");
Map(x => x.CompanyName);
}
}
public class PersonMap : SubclassMap<Person>
{
public PersonMap()
{
DiscriminatorValue("Person");
Map(x => x.FirstName);
}
}
}
The way of defining subclasses is identical. The discriminator mapping differs in few names: Force is AlwaysSelectWithValue in FNH, Type is CustomType and Insert(false) can be mapped using ReadOnly. Moreover, the DiscriminateSubClassesOnColumn method, that is an entry point for discriminator mapping, requires specifying discriminator column name explicity - no default provided.
There are some more standard column-related methods available, analogously to mapping-by-code Column method options.
Table per class
The second strategy for mapping inheritance is table per class with joined subclasses. In this option subclasses are stored in separate tables that have foreign key to base class table and are joined with the table for base class, if needed. In this case, in mapping-by-code, we have to map subclasses by inheriting from JoinedSubclassMapping<T>. Here is the example of joined subclass mapping with all available options:
public class CompanyMap : JoinedSubclassMapping<Company>
{
public CompanyMap()
{
Key(k =>
{
k.Column("PartyId");
// or...
k.Column(c =>
{
c.Name("PartyId");
// etc.
});
k.ForeignKey("party_fk");
k.NotNullable(true);
k.OnDelete(OnDeleteAction.Cascade); // or OnDeleteAction.NoAction
k.PropertyRef(x => x.CompanyName);
k.Unique(true);
k.Update(true);
});
Property(x => x.CompanyName);
}
}
Fluent NHibernate's equivalent for table per class
In Fluent, subclasses are mapped by inheriting from the same SubclassMap<T> as in single table strategy. Only the method used differs - instead of DiscriminatorValue, we're using KeyColumn method with column name in the parameter (required, no default value). Looks like there's no way to configure other key column's options here.
public class CompanyMap : SubclassMap<Company>
{
public CompanyMap()
{
KeyColumn("PartyId");
Map(x => x.CompanyName);
}
}
Table per subclass
The third option is to use table per subclass with unioned subclasses. This time there is no separate table for base class, common columns are specified in each table for subclass separately and subclass tables share the same key generator. Mapping union subclasses with mapping-by-code is fairly simple - we just need to map the class by inheriting from UnionSubclassMapping<T> - no additional requirements or options.
public class CompanyMap : UnionSubclassMapping<Company>
{
public CompanyMap()
{
Property(x => x.CompanyName);
}
}
Fluent NHibernate's equivalent for table per subclass
Again, in Fluent, subclass maps inherit from the same SubclassMap<T>. To differentiate this strategy from the single table strategy, we have to include a call to UseUnionSubclassForInheritanceMapping method in the parent class mapping.
public class PartyMap : ClassMap<Party>
{
public PartyMap()
{
Id(x => x.Id).GeneratedBy.HiLo(maxLo: "100");
UseUnionSubclassForInheritanceMapping();
}
public class CompanyMap : SubclassMap<Company>
{
public CompanyMap()
{
Map(x => x.CompanyName);
}
}
public class PersonMap : SubclassMap<Person>
{
public PersonMap()
{
Map(x => x.FirstName);
}
}
}