NHibernate 3 (Part 2) Tabla por Jerarquía

Published on Author lopezLeave a comment

Anterior post
Siguiente post

En mi anterior post de esta serie, implementé un mapeo simple: una clase, una tabla.

Ahora, tengo tres clases, usando herencia:

El Id es un System.Guid. Quiero diseñar una colección de items. Cada item puede ser una Nota (Note), con contenido de texto, o una página Web (Page) con una URL asociada. La solución que preparé tiene dos proyectos:

Estoy manteniendo este código en mi AjCodeKatas Google code project, en el directorio trunk/NHibernate/ItemsTablePerHierarchyMapping. El código no incluye las librerías de NHibernate, así que tuve que agregar referencias a la solución. Agregué como referencias al proyecto de consola: NHibernate.dll desde el directorio de NHibernate llamado Required_Bins, y la dll  NHibernate.ByteCode.Castle del directorio de NHibernate llamado Required_For_LazyLoading\Castle.

Si son perezosos como yo, y quieren tener la solución ahora, pueden bajar mi anterior ejemplo que contiene las librerías de NHibernate que necesitamos para el presentet: NHibernate3SimpleMapping.zip. El código de este post está en: NHibernate3ItemsTablePerHierarchy.zip.

El proyecto Domain no tiene referencia a las librerías de NHibernate. TOdo el mapeo está en el proyecto de consola. Los archivos .hbm está ahí agregados como recursos embebidos (no se olviden de agregarlos así en sus proyectos; de la forma que planteo la configuración de NHibernate (ver más abajo) esta librería busca archivos hbm en el assembly que le indiquemos; no es la única forma de indicarle los mapeos: espero en próximos posts explorar otras alternativas).

Tengo 3 clases para mapear. Pero quiero almacenar todos los datos de cualquier item en una sola tabla:

Pueden generar la base de datos vacía ejecutando el comando ExecuteAll.cmd que he dejado en el código de ejemplo.

La tabla Items tiene una columna ItemType, que indica si un item es una Note o una Page. El archivo de mapeo Item.hbm.xml referencia esa columna como un discriminador:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ItemsTablePerHierarchyMapping.Domain"
namespace="ItemsTablePerHierarchyMapping.Domain">
  <class name="Item" table="Items">
    <id name="Id">
      <generator class="guid.comb" />
    </id>
    <discriminator column="ItemType" />
    <property name="Title" not-null="true" />
    <property name="Description" not-null="true" />
  </class>
</hibernate-mapping>

Noten que no tengo esa columna reflejada en una propiedad de las clases: el discriminador lo usa el NHibernate, no lo necesitamos nosotros en nuestro dominio.

Bueno, ahora necesito mapear los objetos Note. ¿Cómo escribir ese mapeo? Vean el Note.hbm.xml:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ItemsTablePerHierarchyMapping.Domain"
namespace="ItemsTablePerHierarchyMapping.Domain">
  <subclass name="Note" extends="Item" discriminator-value="Note">
    <property name="Content" />
  </subclass>
</hibernate-mapping>

El nuevo elemento XML que encontramos es subclass. Tiene un atributo extends que referencia a la clase padre. El atributo discriminator-value tiene el valor a poner en la columna discriminadora cuando el Item es una Note. Si no especificamos el valor de discriminador, el nombre de la clase sería usada como valor asumido, en este caso ItemsTablePerHierarchyMappin.Domain.Note.

El mapeo de Page.hbm.xml es similar:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="ItemsTablePerHierarchyMapping.Domain"
namespace="ItemsTablePerHierarchyMapping.Domain">
  <subclass name="Page" extends="Item" discriminator-value="Page">
    <property name="Url"/>
  </subclass>
</hibernate-mapping>

Podemos ejecutar este código de consola:

ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();
using (ISession session = sessionFactory.OpenSession())
{
    using (ITransaction tx = session.BeginTransaction())
    {
        Page page1 = new Page();
        page1.Title = "Technical Blob";
        page1.Description = "My Personal Technical Blob in English";
        page1.Url = "http://ajlopez.wordpress.com";
        Page page2 = new Page();
        page2.Title = "My Personal Blob";
        page2.Description = "My Personal Blob in Spanish";
        page2.Url = "http://ajlopez.zoomblog.com";
        Note note1 = new Note();
        note1.Title = "To Do";
        note1.Description = "My To Do List";
        note1.Content = "Practice NHibernate";
        session.Save(page1);
        session.Save(page2);
        session.Save(note1);
        IQuery query = session.CreateQuery("from Item");
        foreach (Item item in query.List<Item>())
			System.Console.WriteLine(string.Format("Item {0}", item.Title));
        tx.Commit();
        session.Close();
    }
}
System.Console.ReadKey();

para insertar y listar items.

Esta estrategia de mapeo (el viejo truco de grabar cada subclase en una misma table, usando discriminadores 馃槈 es llamada Table Per Hierarchy (Fowler la menciona como Single Table Inheritance). Acá la traduje como Tabla por Jerarquía.

 

Próximos pasos: implementar otras estrategias de mapeo: una tabla por clase concreta (en este caso, Pages y Notes); una tabla por clase  (sería Items, Pages, Notes).

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Leave a Reply

Your email address will not be published. Required fields are marked *