It's common to have an inheritance hierarchy of subclasses. In this example, I will show you one method for mapping inheritance with NHibernate, called table-per-class hierarchy.
Book
with the following code:namespace Eg.Core { public class Book : Product { public virtual string ISBN { get; set; } public virtual string Author { get; set; } } }
Movie
with the following code:namespace Eg.Core { public class Movie : Product { public virtual string Director { get; set; } } }
Product
mapping to match the XML shown in the following code:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core"> <class name="Product"> <id name="Id"> <generator class="guid.comb" /> </id> <discriminator column="ProductType" /> <natural-id mutable="true"> <property name="Name" not-null="true" /> </natural-id> <property name="Description" /> <property name="UnitPrice" not-null="true" /> </class> </hibernate-mapping>
Book.hbm.xml
with the following XML:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core"> <subclass name="Book" extends="Product"> <property name="Author"/> <property name="ISBN"/> </subclass> </hibernate-mapping>
Movie.hbm.xml
with the next XML:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core"> <subclass name="Movie" extends="Product"> <property name="Director" /> </subclass> </hibernate-mapping>
In this example, we've mapped a table-per-class hierarchy, meaning data for our entire hierarchy is stored in a single table, as shown in the next screenshot:
NHibernate uses a discriminator column, ProductType in this case, to distinguish among products, books, and movies. By default, the discriminator contains the class name. In this example, that would be Eg.Core.Product
, Eg.Core.Book
, or Eg.Core.Movie
. These defaults can be overridden in the mappings by using a discriminator-value
attribute on our class and subclass elements.
In our Book.hbm.xml
mapping, we've defined Book
as a subclass of Product
with Author
and ISBN
properties. In our Movie.hbm.xml
mapping, we've defined Movie
as a subclass of Product
with a Director
property.
With table-per-class-hierarchy, we cannot define any of our subclass properties as not-null="true"
, because this would create a not-null
constraint on those fields. For instance, if we set up the Director
property as not null, we wouldn't be able to insert Product
or Book
instances, because they don't define a Director
property. If this is required, use one of the hierarchy mapping strategies listed next.
Java refugees may recognize the extends
attribute, as extends
is the Java keyword used to declare class inheritance. NHibernate first came to life as a port of Java's Hibernate ORM.
Table-per-class hierarchy is the suggested method for mapping class hierarchies, but NHibernate always gives us other options. However, mixing these options within the same class hierarchy is discouraged, and only works in very limited circumstances.
In table-per-class mappings, properties of the base class (Product
) are stored in a shared table, while each subclass gets its own table for the subclass properties.
Table per subclass uses the joined-subclass
element, which requires a key
element to name the primary key column. As the name implies, NHibernate will use a join to query for this data. Also, notice that our Product table doesn't contain a ProductType column. Only table-per-class hierarchy uses discriminators. Using table-per-class, our Movie
mapping will appear as the following code:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core"> <joined-subclass name="Movie" extends="Product"> <key column="Id" /> <property name="Director" /> </joined-subclass> </hibernate-mapping>
In table-per-concrete-class mappings, each class gets its own table containing columns for all properties of the class and the base class, as shown in the next screenshot:
There is no duplication of data. That is, data from a Book
instance is only written to the Book table, not the Product table. To fetch Product
data, NHibernate will use unions to query all three tables. Using table-per-concrete-class, our Movie
mapping will appear as shown in the following code:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="Eg.Core" namespace="Eg.Core"> <union-subclass name="Movie" extends="Product"> <property name="Director" /> </union-subclass> </hibernate-mapping>
3.133.107.25