Chapter 8. Inheritance Mapping

8.1. The Three Strategies

NHibernate supports the three basic inheritance mapping strategies.

  • table per class hierarchy

  • table per subclass

  • table per concrete class (some limitations)

It is even possible to use different mapping strategies for different branches of the same inheritance hierarchy, but the same limitations apply as apply to table-per-concrete class mappings. NHibernate does not support mixing <subclass> mappings and <joined-subclass> mappings inside the same <class> element.

Suppose we have an interface IPayment, with implementors CreditCardPayment, CashPayment, ChequePayment. The table-per-hierarchy mapping would look like:

<class name="IPayment" table="PAYMENT">
    <id name="Id" type="Int64" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="PAYMENT_TYPE" type="String"/>
    <property name="Amount" column="AMOUNT"/>
    ...
    <subclass name="CreditCardPayment" discriminator-value="CREDIT">
        ...
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        ...
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        ...
    </subclass>
</class>

Exactly one table is required. There is one big limitation of this mapping strategy: columns declared by the subclasses may not have NOT NULL constraints.

A table-per-subclass mapping would look like:

<class name="IPayment" table="PAYMENT">
    <id name="Id" type="Int64" column="PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="Amount" column="AMOUNT"/>
    ...
    <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        ...
    </joined-subclass>
</class>

Four tables are required. The three subclass tables have primary key associations to the superclass table (so the relational model is actually a one-to-one association).

Note that NHibernate's implementation of table-per-subclass requires no discriminator column. Other object/relational mappers use a different implementation of table-per-subclass which requires a type discriminator column in the superclass table. The approach taken by NHibernate is much more difficult to implement but arguably more correct from a relational point of view.

For either of these two mapping strategies, a polymorphic association to IPayment is mapped using <many-to-one>.

<many-to-one name="Payment"
    column="PAYMENT"
    class="IPayment"/>

The table-per-concrete-class strategy is very different.

<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="Id" type="Int64" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="Amount" column="CREDIT_AMOUNT"/>
    ...
</class>

<class name="CashPayment" table="CASH_PAYMENT">
    <id name="Id" type="Int64" column="CASH_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="Amount" column="CASH_AMOUNT"/>
    ...
</class>

<class name="ChequePayment" table="CHEQUE_PAYMENT">
    <id name="Id" type="Int64" column="CHEQUE_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <property name="Amount" column="CHEQUE_AMOUNT"/>
    ...
</class>

Three tables were required. Notice that nowhere do we mention the IPayment interface explicitly. Instead, we make use of NHibernate's implicit polymorphism. Also notice that properties of IPayment are mapped in each of the subclasses.

In this case, a polymorphic association to IPayment is mapped using <any>.

<any name="Payment"
        meta-type="class"
        id-type="Int64">
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any>

It would be better if we defined an IUserType as the meta-type, to handle the mapping from type discriminator strings to IPayment subclass.

<any name="payment"
        meta-type="PaymentMetaType"
        id-type="Int64">
    <column name="PAYMENT_TYPE"/> <!-- CREDIT, CASH or CHEQUE -->
    <column name="PAYMENT_ID"/>
</any>

There is one further thing to notice about this mapping. Since the subclasses are each mapped in their own <class> element (and since IPayment is just an interface), each of the subclasses could easily be part of another table-per-class or table-per-subclass inheritance hierarchy! (And you can still use polymorphic queries against the IPayment interface.)

<class name="CreditCardPayment" table="CREDIT_PAYMENT">
    <id name="Id" type="Int64" column="CREDIT_PAYMENT_ID">
        <generator class="native"/>
    </id>
    <discriminator column="CREDIT_CARD" type="String"/>
    <property name="Amount" column="CREDIT_AMOUNT"/>
    ...
    <subclass name="MasterCardPayment" discriminator-value="MDC"/>
    <subclass name="VisaPayment" discriminator-value="VISA"/>
</class>

<class name="NonelectronicTransaction" table="NONELECTRONIC_TXN">
    <id name="Id" type="Int64" column="TXN_ID">
        <generator class="native"/>
    </id>
    ...
    <joined-subclass name="CashPayment" table="CASH_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="Amount" column="CASH_AMOUNT"/>
        ...
    </joined-subclass>
    <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT">
        <key column="PAYMENT_ID"/>
        <property name="Amount" column="CHEQUE_AMOUNT"/>
        ...
    </joined-subclass>
</class>

Once again, we don't mention IPayment explicitly. If we execute a query against the IPayment interface - for example, from IPayment - NHibernate automatically returns instances of CreditCardPayment (and its subclasses, since they also implement IPayment), CashPayment and ChequePayment but not instances of NonelectronicTransaction.

8.2. Limitations

NHibernate assumes that an association maps to exactly one foreign key column. Multiple associations per foreign key are tolerated (you might need to specify inverse="true" or insert="false" update="false"), but there is no way to map any association to multiple foreign keys. This means that:

  • when an association is modified, it is always the same foreign key that is updated

  • when an association is fetched lazily, a single database query is used

  • when an association is fetched eagerly, it may be fetched using a single outer join

In particular, it implies that polymorphic one-to-many associations to classes mapped using the table-per-concrete-class strategy are not supported. (Fetching this association would require multiple queries or multiple joins.)

The following table shows the limitations of table-per-concrete-class mappings, and of implicit polymorphism, in NHibernate.

Table 8.1. Features of inheritance mappings

Inheritance strategyPolymorphic many-to-onePolymorphic one-to-onePolymorphic one-to-manyPolymorphic many-to-manyPolymorphic Load()/Get()Polymorphic queriesPolymorphic joinsOuter join fetching
table-per-class-hierarchy<many-to-one><one-to-one><one-to-many><many-to-many>s.Get(typeof(IPayment), id)from Payment pfrom Order o join o.payment psupported
table-per-subclass<many-to-one><one-to-one><one-to-many><many-to-many>s.Get(typeof(IPayment), id)from IPayment pfrom Order o join o.Payment psupported
table-per-concrete-class (implicit polymorphism)<any>not supportednot supported<many-to-any>use a queryfrom Payment pnot supportednot supported