Creating class hierarchy mappings

It's common to have an inheritance hierarchy of subclasses. In this example, we will show you one method for mapping inheritance with NHibernate, called table-per-class hierarchy.

Getting ready

Complete the previous Mapping a class with XML example.

How to do it…

  1. Create a new class named Book with the following code:
    namespace Eg.Core
    {
      public class Book : Product
      {
    
        public virtual string ISBN { get; set; }
        public virtual string Author { get; set; }
    
      }
    }
  2. Create a new class named Movie with the following code:
    namespace Eg.Core
    {
      public class Movie : Product 
      {
    
        public virtual string Director { get; set; }
    
      }
    }
  3. Change the 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>
  4. Create a new embedded resource named 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>
  5. Create another embedded resource named 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>
  6. Create a new folder named ClassHierarchy in the MappingRecipes project.
  7. Add a new class named Recipe to the folder:
    public class Recipe : BaseMappingRecipe
    {
        protected override void Configure(Configuration cfg)
        {
           cfg.AddAssembly(typeof(Product).Assembly);
         }
           protected override void AddInitialData(
           ISession session)
         {
            session.Save(new Book
         {
            Name = "NHibernate Cookbook", ISBN = "12334"
          });
             session.Save(new Movie
          {
             Name = "Intouchables", 
             Director = "Olivier Nakache"
           });
     }
           public override void RunQueries(ISession session)
    {
           session.CreateQuery("from Product")
          .List<Product>();
    }
    }
  8. Run the MappingRecipes application and start the ClassHierarchy recipe.
  9. Investigate the query log and the created tables.

How it works…

In this example, we've mapped a table-per-class hierarchy, which means that the data for our entire hierarchy is stored in a single table, as shown in the following screenshot:

How it works…

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, it would be Eg.Core.Product, Eg.Core.Book, or Eg.Core.Movie. These defaults can be overridden in the mappings using a discriminator-value attribute on our class and subclass elements.

In our Book.hbm.xml mapping, we have defined Book as a subclass of Product with Author and ISBN properties. In our Movie.hbm.xml mapping, we have 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 would not be able to insert Product or Book instances, because they do not define a Director property. If this is required, use one of the hierarchy mapping strategies that are listed in the next section.

There's more…

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.

Table per class

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 would 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>

Executing our recipe with table per class mapping would create three tables and execute the following queries:

  • Inserting data:
    INSERT INTO Product(Name, Description, UnitPrice, Id) 
    VALUES (@p0, @p1, @p2, @p3);
    INSERT INTO Book (Author, ISBN, Id) 
    VALUES (@p0, @p1, @p2);
    INSERT INTO Product(Name, Description, UnitPrice, Id) 
    VALUES (@p0, @p1, @p2, @p3);
    INSERT INTO Movie(Director, Id) 
    VALUES (@p0, @p1);
    
  • Listing all products:
    SELECT
            product0_.Id AS Id0_,
            product0_.Name AS Name0_,
            product0_.Description AS Descript3_0_,
            product0_.UnitPrice AS UnitPrice0_,
            product0_1_.Author AS Author1_,
            product0_1_.ISBN AS ISBN1_,
            product0_2_.Director AS Director2_,
            CASE 
                WHEN product0_1_.Id IS NOT NULL THEN 1 
                WHEN product0_2_.Id IS NOT NULL THEN 2 
                WHEN product0_.Id IS NOT NULL THEN 0 
            END AS clazz_ 
        FROM
            Product product0_ 
        LEFT OUTER JOIN
            Book product0_1_ 
                ON product0_.Id=product0_1_.Id 
        LEFT OUTER JOIN
            Movie product0_2_ 
                ON product0_.Id=product0_2_.Id
    

Note how the extra tables are joined (LEFT OUTER to be specific) and a CASE clause is used to return the clazz_ value, which indicates the return type to NHibernate.

Table per concrete class

In table-per-concrete-class mappings, each class gets its own table containing columns, for all properties of the class and the base class.

There is no duplication of data. That is, data from a book instance is written only in the Book table, not the Product table. To fetch Product data, NHibernate will use the SQL UNION operator to query all the three tables. Using table-per-concrete-class, our Movie mapping would 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>

Executing our recipe with table per concrete class mapping would result in the following queries:

  • Inserting data:
    INSERT INTO Book (Name, Description, UnitPrice, Author, ISBN, Id) 
    VALUES (@p0, @p1, @p2, @p3, @p4, @p5);
    INSERT INTO Movie (Name, Description, UnitPrice, Director, Id) 
    VALUES (@p0, @p1, @p2, @p3, @p4);
    
  • Listing all products:
    SELECT 
            product0_.Id AS Id0_,
            product0_.Name AS Name0_,
            product0_.Description AS Descript3_0_,
            product0_.UnitPrice AS UnitPrice0_,
            product0_.Author AS Author1_,
            product0_.ISBN AS ISBN1_,
            product0_.Director AS Director2_,
            product0_.clazz_ AS clazz_ 
        FROM
            (SELECT
                [columns snipped…]
                0 AS clazz_ 
            FROM
                Product 
            UNION
            SELECT
                [columns snipped…]
                1 AS clazz_ 
            FROM
                Book 
            UNION
            SELECT
                [columns snipped…]
                2 AS clazz_ 
            FROM
                Movie 
        ) product0_
    

See also

  • Mapping a class with XML
  • Mapping a one-to-many relationship
  • Setting up a base entity class
  • Handling versioning and concurrency
  • Creating mappings fluently
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.91.24