NHibernate supports the three basic inheritance mapping strategies:
table per class hierarchy
table per subclass
table per concrete class
In addition, NHibernate supports a fourth, slightly different kind of polymorphism:
implicit polymorphism
It is possible to use different mapping strategies for different branches of the same inheritance hierarchy. You can then make use of implicit polymorphism to achieve polymorphism across the whole hierarchy. However, NHibernate does not support mixing <subclass>, and <joined-subclass> and <union-subclass> mappings under the same root <class> element. It is possible to mix together the table per hierarchy and table per subclass strategies, under the the same <class> element, by combining the <subclass> and <join> elements (see Section 9.1.4, “Mixing table per class hierarchy with table per subclass”).
It is possible to define subclass, union-subclass, and joined-subclass mappings in separate mapping documents directly beneath hibernate-mapping. This allows you to extend a class hierarchy by adding a new mapping file. You must specify an extends attribute in the subclass mapping, naming a previously mapped superclass.
<hibernate-mapping> <subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping>
Suppose we have an interface IPayment, with implementors CreditCardPayment, CashPayment, ChequePayment. The table-per-hierarchy mapping would display in the following way:
<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"> <property name="CreditCardType" column="CCTYPE"/> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
Exactly one table is required. There is a limitation of this mapping strategy: columns declared by the subclasses, such as CCTYPE, cannot 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"/> <property name="CreditCardType" column="CCTYPE"/> ... </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.
NHibernate's implementation of table per subclass does not require a discriminator column. Other object/relational mappers use a different implementation of table per subclass that 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. If you want to use a discriminator column with the table per subclass strategy, you can combine the use of <subclass> and <join>, as follows:
<class name="Payment" 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"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="CreditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class>
The optional fetch="select" declaration tells NHibernate not to fetch the ChequePayment subclass data using an outer join when querying the superclass.
You can even mix the table per hierarchy and table per subclass strategies using the following approach:
<class name="Payment" 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"> <join table="CREDIT_PAYMENT"> <property name="CreditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class>
For any of these mapping strategies, a polymorphic association to IPayment is mapped using <many-to-one>.
<many-to-one name="Payment" column="PAYMENT" class="IPayment"/>
There are two ways we can map the table per concrete class strategy. First, you can use <union-subclass>.
<class name="Payment"> <id name="Id" type="Int64" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="Amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="CreditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class>
Three tables are involved for the subclasses. Each table defines columns for all properties of the class, including inherited properties.
The limitation of this approach is that if a property is mapped on the superclass, the column name must be the same on all subclass tables. The identity generator strategy is not allowed in union subclass inheritance. The primary key seed has to be shared across all unioned subclasses of a hierarchy.
If your superclass is abstract, map it with abstract="true". If it is not abstract, an additional table (it defaults to PAYMENT in the example above), is needed to hold instances of the superclass.
An alternative approach is to make use of implicit polymorphism:
<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"/> <property name="CreditCardType" column="CCTYPE"/> ... </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>
Notice that the IPayment interface is not mentioned explicitly. Also notice that properties of IPayment are mapped in each of the subclasses. If you want to avoid duplication, consider using XML entities (by example [ <!ENTITY allproperties SYSTEM "allproperties.xml"> ] in the DOCTYPE declaration and &allproperties; in the mapping).
The disadvantage of this approach is that NHibernate does not generate SQL UNIONs when performing polymorphic queries.
For this mapping strategy, a polymorphic association to IPayment is usually mapped using <any>.
<any name="Payment" meta-type="string" id-type="Int64"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any>
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 inheritance hierarchy. 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, IPayment is not mentioned 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 (provided it does not implement IPayment).
There are limitations to the "implicit polymorphism" approach to the table per concrete-class mapping strategy. There are somewhat less restrictive limitations to <union-subclass> mappings.
The following table shows the limitations of table per concrete-class mappings, and of implicit polymorphism, in NHibernate.
Table 9.1. Features of inheritance mappings
Inheritance strategy | Polymorphic many-to-one | Polymorphic one-to-one | Polymorphic one-to-many | Polymorphic many-to-many | Polymorphic Load()/Get() | Polymorphic queries | Polymorphic joins | Outer join fetching |
---|---|---|---|---|---|---|---|---|
table per class-hierarchy | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.Get<IPayment>(id) | from IPayment p | from Order o join o.Payment p | supported |
table per subclass | <many-to-one> | <one-to-one> | <one-to-many> | <many-to-many> | s.Get<IPayment>(id) | from IPayment p | from Order o join o.Payment p | supported |
table per concrete-class (union-subclass) | <many-to-one> | <one-to-one> | <one-to-many> (for inverse="true" only) | <many-to-many> | s.Get<IPayment>(id) | from IPayment p | from Order o join o.Payment p | supported |
table per concrete class (implicit polymorphism) | <any> | not supported | not supported | <many-to-any> | use a query | from IPayment p | not supported | not supported |