Skip to content

6. Introducción a ORM NHibernate

Este capítulo es una breve introducción a NHibernate, el equivalente en .NET del framework Java Hibernate. Para una introducción completa, puede consultarse:


Título: NHibernate in Action, Autor: Pierre-Henri Kuaté, Editorial: Manning, ISBN: 978-1932394924


Un ORM (mapeador objeto-relacional) es un conjunto de bibliotecas que permite a un programa que utiliza una base de datos explotarla sin emitir órdenes SQL explícitas y sin conocer las particularidades del SGBD utilizado.


Requisitos previos


En una escala [débutant-intermédiaire-avancé], este documento se encuentra en la parte [intermédiaire]. Para comprenderlo se requieren varios requisitos previos que se pueden encontrar en algunos de los documentos que he escrito:

  1. Lenguaje C# 2008: [Apprentissage du langage C# Version 3.0 avec le Framework .NET 3.5 ]
  2. [Spring IoC], disponible en URL [Spring IoC pour .NET ]. Presenta los fundamentos de la inversión de control (Inversion of Control) o la inyección de dependencias (Dependency Injection) del marco Spring.Net [Spring.NET | Homepage ].

En ocasiones, al principio de los párrafos de este documento se incluyen recomendaciones de lectura. Estas remiten a documentos anteriores.


Herramientas


Las herramientas utilizadas en este caso práctico están disponibles gratuitamente en la web. Son las siguientes (diciembre de 2011):

  • Nhibernate 3.2 disponible en Url [http://nhforge.org/Default.aspx]
  • Spring.net 1.3.2 disponible en Url [http://www.springframework.net]. El framework Spring.net es muy completo. Aquí solo utilizaremos la biblioteca que incluye para facilitar el uso del framework Nhibernate.
  • Log4net 1.2.10 disponible en Url [http://logging.apache.org/log4net]. Este marco de trabajo de registros es utilizado por Nhibernate.
  • Nunit 2.5 disponible en Url y [http://www.nunit.org/]. Este marco de pruebas unitarias es el equivalente para .NET del marco JUnit para la plataforma Java.
  • El controlador ADO.NET 6.4.4 de SGBD MySQL 5 disponible en Url [http://dev.mysql.com/downloads/connector/net]

Todos los archivos DLL necesarios para los proyectos de Visual Studio 2010 se han reunido en una carpeta [libnet4]:

 

6.1. El lugar de NHIBERNATE en una arquitectura .NET en capas

Una aplicación .NET que utiliza una base de datos puede estructurarse en capas de la siguiente manera:

La capa [dao] se comunica con SGBD a través de API ADO.NET (véase el apartado 3.3). En la arquitectura anterior, el conector [ADO.NET] está vinculado al SGBD. Por lo tanto, la clase que implementa la interfaz [IDbConnection] es:

  • la clase [MySQLConnection] para SGBD MySQL
  • la clase [SQLConnection] para SGBD y SQLServer

La capa [dao] depende, por tanto, del SGBD utilizado. Algunos frameworks (Linq, Ibatis.net, NHibernate) eliminan esta restricción añadiendo una capa adicional entre la capa [dao] y el conector [ADO.NET] del SGBD utilizado. Aquí utilizaremos el marco [NHibernate].

En el ejemplo anterior, la capa [dao] ya no se dirige al conector [ADO.NET], sino al marco NHibernate, que le presentará una interfaz independiente del conector [ADO.NET] utilizado. Esta arquitectura permite cambiar el SGBD sin cambiar la capa [dao]. Solo hay que cambiar entonces el conector [ADO.NET].

6.2. La base de datos de ejemplo

Para mostrar cómo trabajar con NHibernate, utilizaremos la siguiente base de datos MySQL [dbpam_nhibernate] descrita en el apartado 3.1. La exportación de la estructura de la base de datos a un archivo SQL da el siguiente resultado:

#
# Estructura de la tabla `cotisations`: 
#

CREATE TABLE `cotisations` (
  `ID` bigint(20) NOT NULL auto_increment,
  `SECU` double NOT NULL,
  `RETRAITE` double NOT NULL,
  `CSGD` double NOT NULL,
  `CSGRDS` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=latin1;

#
# Estructura de la tabla `indemnites`: 
#

CREATE TABLE `indemnites` (
  `ID` bigint(20) NOT NULL auto_increment,
  `ENTRETIEN_JOUR` double NOT NULL,
  `REPAS_JOUR` double NOT NULL,
  `INDICE` int(11) NOT NULL,
  `INDEMNITES_CP` double NOT NULL,
  `BASE_HEURE` double NOT NULL,
  `VERSION` int(11) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `INDICE` (`INDICE`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=latin1;

#
# Estructura de la tabla `employes`: 
#

CREATE TABLE `employes` (
  `ID` bigint(20) NOT NULL auto_increment,
  `PRENOM` varchar(20) NOT NULL,
  `SS` varchar(15) NOT NULL,
  `ADRESSE` varchar(50) NOT NULL,
  `CP` varchar(5) NOT NULL,
  `VILLE` varchar(30) NOT NULL,
  `NOM` varchar(30) NOT NULL,
  `VERSION` int(11) NOT NULL,
  `INDEMNITE_ID` bigint(20) NOT NULL,
  PRIMARY KEY  (`ID`),
  UNIQUE KEY `SS` (`SS`),
  KEY `FK_EMPLOYES_INDEMNITE_ID` (`INDEMNITE_ID`),
  CONSTRAINT `FK_EMPLOYES_INDEMNITE_ID` FOREIGN KEY (`INDEMNITE_ID`) REFERENCES `indemnites` (`ID`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

Cabe señalar, en las líneas 6, 20 y 36, que las claves primarias ID tienen el atributo autoincrement. Esto significa que MySQL generará automáticamente los valores de las claves primarias cada vez que se añada un registro. El desarrollador no tiene que preocuparse por ello.

6.3. El proyecto de demostración en C#

Para presentar la configuración y el uso de NHibernate, utilizaremos la siguiente arquitectura:

Un programa de consola [1] manipulará los datos de la base de datos anterior [2] a través del marco [NHibernate] [3]. Esto nos llevará a presentar:

  • los archivos de configuración de NHibernate
  • el API de NHibernate

El proyecto C# será el siguiente:

Los elementos necesarios para el proyecto son los siguientes:

  • en [1], los DLL que necesita el proyecto:
    • [NHibernate]: el DLL del marco NHibernate
    • [MySql.Data]: el DLL del conector ADO.NET del SGBD MySQL
    • [log4net]: el DLL del marco Log4net que permite generar registros
  • en [2], las clases de imágenes de las tablas de la base de datos
  • en [3], el archivo [App.config] que configura toda la aplicación, incluido el marco [NHibernate]
  • en [4], y las aplicaciones de consola de prueba

6.3.1. Configuración de la conexión a la base de datos

Volvamos a la arquitectura de prueba:

En el ejemplo anterior, [NHibernate] debe poder acceder a la base de datos. Para ello, necesita cierta información:

  • el SGBD que gestiona la base de datos (MySQL, SQLServer, Postgres, Oracle, ...). La mayoría de los SGBD han añadido al lenguaje SQL extensiones propias. Al conocer el SGBD, el NHibernate puede adaptar las órdenes SQL que emite a este SGBD. NHibernate utiliza el concepto de dialecto SQL.
  • los parámetros de conexión a la base de datos (nombre de la base, nombre del usuario propietario de la conexión, su contraseña)

Esta información se puede colocar en el archivo de configuración [App.config]. Este es el que se utilizará con una base MySQL 5:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <!-- Secciones de configuración -->
    <configSections>
        <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
        <section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
    </configSections>


    <!-- Configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
    </hibernate-configuration>

    <!-- Esta sección contiene los ajustes de configuración de log4net -->
    <!-- NOTE IMPORTANTE: los registros no están activos por defecto. Hay que activarlos mediante el programa con la instrucción log4net.Config.XmlConfigurator.Configure();
    ! -->
    <log4net>
        <!-- Defina un appender de salida (dónde se enviarán los registros) -->
        <appender name="LogFileAppender" type="log4net.Appender.FileAppender, log4net">
            <param name="File" value="log.txt" />
            <param name="AppendToFile" value="false" />
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n" />
            </layout>
        </appender>
        <appender name="LogDebugAppender" type="log4net.Appender.DebugAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>
        <appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender, log4net">
            <layout type="log4net.Layout.PatternLayout, log4net">
                <param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n"/>
            </layout>
        </appender>

        <!-- Configurar la categoría raíz, establecer el nivel de prioridad predeterminado y añadir los apéndices (dónde se enviarán los registros) -->
        <root>
            <priority value="INFO" />
            <!--
            <appender-ref ref="LogFileAppender" />
            <appender-ref ref="LogDebugAppender"/>
            -->
            <appender-ref ref="ConsoleAppender"/>
        </root>

        <!-- Especificar el nivel para algunos espacios de nombres específicos -->
        <!-- El nivel puede ser: ALL, DEBUG, INFO, WARN, ERROR, FATAL, OFF -->
        <logger name="NHibernate">
            <level value="INFO" />
        </logger>
    </log4net>
</configuration>
  • Líneas 4-7: definen secciones de configuración en el archivo [App.config]. Veamos la línea 6:

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />

Esta línea define la sección de configuración de NHibernate en el archivo [App.config]. Tiene dos atributos: name y «type».

  • El atributo [name] nombra la sección de configuración. Esta sección debe estar delimitada aquí por las etiquetas <name>...</name>, aquí <hibernate-configuration>...</hibernate-configuration> de las líneas 11-24.
  • El atributo [type=classe,DLL] indica el nombre de la clase encargada de procesar la sección definida por el atributo [name], así como el DLL que contiene dicha clase. En este caso, la clase se llama [NHibernate.Cfg.ConfigurationSectionHandler] y se encuentra en DLL [NHibernate.dll]. Recordemos que este DLL forma parte de las referencias del proyecto estudiado.

Consideremos ahora la sección de configuración de NHibernate:


    <!-- configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • línea 2: la configuración de NHibernate se encuentra dentro de una etiqueta <hibernate-configuration>. El atributo xmlns (Xml NameSpace) establece el version utilizado para configurar NHibernate. De hecho, con el tiempo, la forma de configurar NHibernate ha evolucionado. Aquí se utiliza la versión 2.2 de version.
  • línea 3: la configuración de NHibernate se encuentra aquí íntegramente dentro de la etiqueta <session-factory> (líneas 3 y 14). Una sesión NHibernate es la herramienta utilizada para trabajar con una base de datos según el esquema:
    • inicio de sesión
    • trabajo con la base de datos mediante los métodos de API NHibernate
    • cierre de sesión

La sesión es creada por una factory, un término genérico que designa una clase capaz de crear objetos. Las líneas 3-14 configuran esta factory.

  • líneas 4, 6, 8, 9: configuran la conexión a la base de datos de destino. La información principal es el nombre del SGBD utilizado, el nombre de la base de datos, la identidad del usuario y su contraseña.
  • línea 4: define el proveedor de la conexión, aquel al que se solicita una conexión con la base de datos. El valor de la propiedad [connection.provider] es el nombre de una clase NHibernate. Esta propiedad no depende del SGBD utilizado.
  • línea 6: el controlador ADO.NET que se va a utilizar. Es el nombre de una clase NHibernate especializada para un SGBD determinado, en este caso MySQL. La línea 6 se ha comentado, ya que no es imprescindible.
  • Línea 8: la propiedad [dialect] establece el dialecto SQL que se debe utilizar con el SGBD. Aquí es el dialecto del SGBD MySQL.

Si cambiamos a SGBD, ¿cómo encontramos el dialecto NHibernate de este? Volvamos al proyecto C# anterior y hagamos doble clic en el DLL [NHibernate] en la pestaña [References]:

  • en [1], la pestaña [Explorateur d'objets] muestra una serie de DLL, entre los que se encuentran los referenciados por el proyecto.
  • en [2], el DLL [NHibernate]
  • en [3], la DLL [NHibernate] desarrollada. En ella se encuentran los diferentes espacios de nombres (namespace) que se definen en ella.
  • en [4], el espacio de nombres [NHibernate.Dialect] donde se encuentran las clases que definen los diferentes dialectos SQL utilizables.
  • en [5], la clase del dialecto de SGBD MySQL 5.
  • en [6], el espacio de nombres de la clase [MySqlDataDriver] utilizada en la línea 6 a continuación:

    <!-- configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQLDialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>
  • líneas 9-11: la cadena de conexión a la base de datos. Esta cadena tiene el formato «param1=val1;param2=val2; ...». El conjunto de parámetros así definidos permite al controlador de SGBD establecer una conexión. El formato de esta cadena de conexión depende del controlador SGBD utilizado. Las cadenas de conexión para los principales controladores SGBD se encuentran en el sitio web [http://www.connectionstrings.com/]. En este caso, la cadena «Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;» es una cadena de conexión para el SGBD MySQL. Indica que:
    • Server=localhost; : el servidor SGBD se encuentra en la misma máquina que el cliente que intenta establecer la conexión
    • Database=dbpam_nhibernate; : la base de datos MySQL de destino
    • Uid=root; : el usuario que abre la conexión es el usuario root
    • Pwd=; : este usuario no tiene contraseña (caso particular de este ejemplo)
  • línea 12: la propiedad [show_sql] indica si NHibernate debe mostrar en sus registros las órdenes SQL que envía a la base de datos. En la fase de desarrollo, resulta útil establecer esta propiedad en [true] para saber exactamente qué hace NHibernate.
  • Línea 13: para comprender la etiqueta <mapping>, volvamos a la arquitectura de la aplicación:

Si el programa de consola fuera un cliente directo del conector ADO.NET y quisiera la lista de empleados, haría que el conector ejecutara una orden SQL Select, y recibiría a cambio un objeto de tipo IDataReader que tendría que procesar para obtener la lista de empleados deseada inicialmente.

En el ejemplo anterior, el programa de consola es el cliente de NHibernate y NHibernate es el cliente del conector ADO.NET. Veremos más adelante que el API de NHibernate permitirá al programa de consola solicitar la lista de empleados. NHibernate traducirá esta solicitud en una orden SQL Select que ejecutará en el conector ADO.NET. Este le devolverá un objeto de tipo IDataReader. A partir de este objeto, Nhibernate debe ser capaz de construir la lista de empleados que se le ha solicitado. Esto es posible gracias a la configuración. A cada tabla de la base de datos se le asocia una clase C#. Así, a partir de las filas de la tabla [employes] devueltas por IDataReader, NHibernate podrá construir una lista de objetos que representan a los empleados y devolverla al programa de consola. Estas relaciones entre tablas y clases se crean en archivos de configuración. NHibernate utiliza el término «mapping» para definir estas relaciones.

Volvamos a la línea 13 que aparece a continuación:


    <!-- configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

La línea 13 indica que los archivos de configuración de tablas <--> clases se encontrarán en el ensamblado [pam-nhibernate-demos]. Un ensamblado es el ejecutable o el archivo DLL generado al compilar un proyecto. En este caso, los archivos de mapeo se colocarán en el ensamblado del proyecto de ejemplo. Para conocer el nombre de este ensamblado, hay que consultar las propiedades del proyecto:

  • en [1], las propiedades del proyecto
  • en la pestaña [Application] [2], el nombre del ensamblado [3] que se va a generar.
  • Dado que el tipo de salida es [Application console] [4], el archivo generado al compilar el proyecto se llamará [pam-nhibernate-demos.exe]. Si el tipo de salida fuera [Bibliothèque de classes] [5], el archivo generado al compilar el proyecto se llamaría [pam-nhibernate-demos.dll]
  • El ensamblado se genera en la carpeta [bin/Release] del proyecto [6].

De la explicación anterior se desprende que los archivos de las tablas de mapeo <--> clases deberán estar en el archivo [pam-nhibernate-demos.exe] [6].

6.3.2. Configuración del e e de mapeo de tablas <--> clases

Volvamos a la arquitectura del proyecto estudiado:

  • en [1], el programa de consola utiliza los métodos de API del marco NHibernate. Estos dos bloques intercambian objetos.
  • En [2], NHibernate utiliza el API de un conector .NET. Envía órdenes SQL al SGBD de destino.

El programa de consola manipulará objetos que reflejan las tablas de la base de datos. En este proyecto, estos objetos y los enlaces que los unen a las tablas de la base de datos se han colocado en la carpeta [Entites] que se muestra a continuación:

 
  • cada tabla de la base de datos es objeto de una clase y de un archivo de mapeo entre ambas
Table
Classe
Mapping
cotisations
Cotisations.cs
Cotisations.hbm.xml
employes
Employe.cs
Employe.hbm.xml
indemnites
Indemnites.cs
Indemnites.hbm.xml

6.3.2.1. Asignación de la tabla [cotisations]

Consideremos la tabla [cotisations]:

ID
clave primaria de tipo autoincrement
VERSION
N.º de registro version
SECU
tasa (porcentaje) de cotización a la seguridad social
RETRAITE
tasa de cotización para la jubilación
CSGD
tasa de cotización para la contribución social generalizada deducible
CSGRDS
tipo de cotización para la contribución social generalizada y la contribución al reembolso de la deuda social

Una línea de esta tabla puede encapsularse en un objeto de tipo [Cotisations.cs] de la siguiente manera:


namespace PamNHibernateDemos {
    public class Cotisations {
        // propiedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual double CsgRds { get; set; }
        public virtual double Csgd { get; set; }
        public virtual double Secu { get; set; }
        public virtual double Retraite { get; set; }

        // constructores
        public Cotisations() {
        }
        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}]", CsgRds, Csgd, Secu, Retraite);
        }
    }

}

Se ha creado una propiedad automática para cada una de las columnas de la tabla [cotisations]. Cada una de estas propiedades debe declararse virtual, ya que NHibernate va a derivar la clase y redefinir (override) sus propiedades. Por lo tanto, estas deben ser virtuales.

Cabe señalar, en la línea 1, que la clase pertenece al espacio de nombres [PamNHibernateDemos].

El archivo de mapeo [Cotisations.hbm.xml] entre la tabla [cotisations] y la clase [Cotisations] es el siguiente:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Cotisations" table="COTISATIONS">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="CsgRds" column="CSGRDS"/>
        <property name="Csgd" column="CSGD"/>
        <property name="Retraite" column="RETRAITE"/>
        <property name="Secu" column="SECU"/>
    </class>
</hibernate-mapping>
  • el archivo de mapeo es un archivo Xml definido dentro de la etiqueta <hibernate-mapping> (líneas 2 y 14)
  • Línea 4: la etiqueta <class> establece el vínculo entre una tabla de la base de datos y una clase. En este caso, la tabla [COTISATIONS] (atributo table) y la clase [Cotisations] (atributo name). En .NET, una clase debe definirse mediante su nombre completo (espacio de nombres incluido) y mediante el ensamblado que la contiene. Esta información se proporciona en la línea 3. La primera (espacio de nombres) se puede encontrar en la definición de la clase. La segunda (ensamblado) es el nombre del ensamblado del proyecto. Ya hemos indicado cómo encontrar este nombre.
  • Líneas 5-7: la etiqueta <id> sirve para definir la asignación de la clave primaria de la tabla [cotisations].
    • línea 5: el atributo name designa el campo de la clase [Cotisations] que va a recibir la clave primaria de la tabla [cotisations]. El atributo column designa la columna de la tabla [cotisations] que sirve como clave primaria. El atributo unsaved-value sirve para definir una clave primaria aún no generada. Este valor permite a NHibernate saber cómo guardar un objeto [Cotisations] en la tabla [cotisations]. Si este objeto tiene un campo Id=0, realizará una operación SQL INSERT; de lo contrario, realizará una operación SQL UPDATE. El valor de unsaved-value depende del tipo del campo Id de la clase [Cotisations]. En este caso, es de tipo int y el valor por defecto de un tipo int es 0. Un objeto [Cotisations] aún no guardado (por lo tanto, sin clave primaria) tendrá, por tanto, su campo Id=0. Si el campo Id hubiera sido de tipo Object o derivado, se habría escrito unsaved-value=null.
    • línea 6: cuando NHibernate debe guardar un objeto [Cotisations] con un campo Id=0, debe realizar en la base de datos una operación INSERT durante la cual debe obtener un valor para la clave primaria del registro. La mayoría de los SGBD tienen un método propio para generar automáticamente este valor. La etiqueta <generator> sirve para definir el mecanismo que se utilizará para la generación de la clave primaria. La etiqueta <generator class="native"> indica que se debe utilizar el mecanismo predeterminado del SGBD utilizado. En el apartado 6.2 vimos que las claves primarias de nuestras tres tablas MySQL tenían el atributo autoincrement. Durante sus operaciones, INSERT y NHibernate no proporcionarán ningún valor a la columna ID del registro añadido, dejando que MySQL genere dicho valor.
  • línea 8: la etiqueta <version> sirve para definir la columna de la tabla (así como el campo de la clase correspondiente) que permite «versionar» los registros. Inicialmente, version tiene el valor 1. Se incrementa con cada operación UPDATE. Por otra parte, cualquier operación UPDATE o DELETE se realiza con un filtro WHERE ID= id AND VERSION=v1. Por lo tanto, un usuario solo puede modificar o eliminar un objeto si dispone de la version correcta del mismo. Si no es así, NHibernate genera una excepción.
  • línea 9: la etiqueta <property> sirve para definir una asignación de columna normal (ni clave primaria, ni columna de version). Así, la línea 9 indica que la columna CSGRDS de la tabla [COTISATIONS] está asociada a la propiedad CsgRds de la clase [Cotisations].

6.3.2.2. Asignación de la tabla [indemnites]

Consideremos la tabla [indemnites]:

ID
clave primaria de tipo autoincrement
VERSION
N.º de registro version
BASE_HEURE
coste en euros de una hora de guardia
ENTRETIEN_JOUR
indemnización en euros por día de guardia
REPAS_JOUR
Indemnización por comida en euros por día de guardia
INDEMNITES_CP
Indemnizaciones por vacaciones pagadas. Se trata de un porcentaje que se aplica al salario base.

Una línea de esta tabla puede encapsularse en un objeto de tipo [Indemnites] de la siguiente manera:


namespace PamNHibernateDemos {
    public class Indemnites {

        // propiedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual int Indice { get; set; }
        public virtual double BaseHeure { get; set; }
        public virtual double EntretienJour { get; set; }
        public virtual double RepasJour { get; set; }
        public virtual double IndemnitesCp { get; set; }

        // fabricantes
        public Indemnites() {
        }

        // identidad
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}]", Indice, BaseHeure, EntretienJour, RepasJour, IndemnitesCp);
        }

    }
}

El archivo de mapeo de la tabla [indemnites] <--> clase [Indemnites] podría ser el siguiente (Indemnites.hbm.xml):


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Indemnites" table="INDEMNITES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="Indice" column="INDICE" unique="true"/>
        <property name="BaseHeure" column="BASE_HEURE" />
        <property name="EntretienJour" column="ENTRETIEN_JOUR" />
        <property name="RepasJour" column="REPAS_JOUR" />
        <property name="IndemnitesCp" column="INDEMNITES_CP" />
    </class>
</hibernate-mapping>

No hay nada nuevo aquí con respecto al archivo de mapeo explicado anteriormente. La única diferencia se encuentra en la línea 9. El atributo unique="true" indica que en la tabla [indemnites] hay una restricción de unicidad en la columna [INDICE]: no puede haber dos filas con el mismo valor en la columna [INDICE].

6.3.2.3. Mapeo de la tabla [employes]

Consideremos la tabla [employes]:

ID
clave primaria de tipo autoincrement
VERSION
N.º de registro version
PRENOM
nombre del empleado
NOM
su apellido
ADRESSE
su dirección
CP
su código postal
VILLE
su ciudad
INDEMNITE_ID
clave externa en INDEMNITES(ID)

La novedad con respecto a las tablas anteriores es la presencia de una clave externa: la columna [INDEMNITE_ID] es una clave externa sobre la columna [ID] de la tabla [INDEMNITES]. Este campo hace referencia a la fila de la tabla [INDEMNITES] que se debe utilizar para calcular los indemnites del empleado.

La clase [Employe] a de la tabla [employes] podría ser la siguiente:


namespace PamNHibernateDemos {
    public class Employe {
        // propiedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // constructores
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

El archivo de mapeo [Employe.hbm.xml] podría ser el siguiente:


<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

La novedad se encuentra en la línea 15, con la aparición de una nueva etiqueta: <many-to-one>. Esta etiqueta sirve para mapear una columna de clave externa [INDEMNITE_ID] de la tabla [EMPLOYES] a la propiedad [Indemnites] de la clase [Employe]:


namespace PamNHibernateDemos {
    public class Employe {
        // propiedades automáticas
..
        public virtual Indemnites Indemnites { get; set; }

...
    }
}

La tabla [EMPLOYES] tiene una clave externa [INDEMNITE_ID] que hace referencia a la columna [ID] de la tabla [INDEMNITES]. Varias (many) filas de la tabla [EMPLOYES] pueden hacer referencia a una misma fila (one) de la tabla [INDEMNITES]. De ahí el nombre de la etiqueta <many-to-one>. Esta etiqueta tiene aquí los siguientes atributos:

  • column: indica el nombre de la columna de la tabla [EMPLOYES] que es clave externa en la tabla [INDEMNITES]
  • name: indica la propiedad de la clase [Employe] asociada a esta columna. El tipo de esta propiedad es necesariamente la clase asociada a la tabla de destino de la clave externa, en este caso la tabla [INDEMNITES]. Sabemos que esta clase es la clase [Indemnites] ya descrita. Esto es lo que refleja la línea 5 anterior. Esto significa que cuando NHibernate recupere de la base de datos un objeto [Employe], también recuperará el objeto [Indemnites] que le corresponde.
  • cascade: este atributo puede tener varios valores:
    • save-update: una operación de inserción (save) o actualización (update) sobre el objeto [Employe] debe propagarse al objeto [Indemnites] que contiene.
    • delete: la eliminación de un objeto [Employe] debe propagarse al objeto [Indemnites] que contiene.
    • all: propaga las operaciones de inserción (save), actualización (update) y eliminación (delete).
    • none: no propaga nada

Para terminar, recordemos la configuración de NHibernate en el archivo [App.config]:


    <!-- configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

La línea 13 indica que los archivos de mapeo *.hbm.xml se encontrarán en el ensamblado [pam-nhibernate-demos]. Esto no se hace de forma predeterminada. Hay que configurarlo en el proyecto C#:

  • en [1], se seleccionan las propiedades de un archivo de mapeo
  • en [2], la acción de generación debe ser [Ressource incorporée] [3]. Esto significa que, al generar el proyecto, el archivo de mapeo debe incorporarse al ensamblado generado.

6.4. API de NHibernate

Volvamos a la arquitectura de nuestro proyecto de ejemplo:

En los párrafos anteriores, hemos configurado NHibernate de dos maneras:

  • en [App.config], hemos configurado la conexión a la base de datos
  • escribimos, para cada tabla de la base, la clase de imagen de dicha tabla y el archivo de mapeo que permite pasar de la clase a la tabla y viceversa.

Nos queda por descubrir los métodos que ofrece NHibernate para manipular los datos de la base: inserción, actualización, eliminación, lista.

6.4.1. El objeto SessionFactory

Toda operación NHibernate se realiza dentro de una sesión. Una secuencia típica de operaciones NHibernate es la siguiente:

  • abrir una sesión NHibernate
  • iniciar una transacción en la sesión
  • realizar operaciones de persistencia con la sesión (Load, Get, Find, CreateQuery, Save, SaveOrUpdate, Delete)
  • validar (commit) o anular (rollback) la transacción
  • cerrar la sesión NHibernate

Se obtiene una sesión de una fábrica de tipo [SessionFactory]. Esta fábrica es la configurada por la etiqueta <session-factory> en el archivo de configuración [App.config]:


    <!-- configuración NHibernate -->
    <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <session-factory>
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
            <!--
            <property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
            -->
            <property name="dialect">NHibernate.Dialect.MySQL5Dialect</property>
            <property name="connection.connection_string">
                Server=localhost;Database=dbpam_nhibernate;Uid=root;Pwd=;
            </property>
            <property name="show_sql">false</property>
            <mapping assembly="pam-nhibernate-demos"/>
        </session-factory>
</hibernate-configuration>

En código C#, la SessionFactory se puede obtener de la siguiente manera:


ISessionFactory sessionFactory = new Configuration().Configure().BuildSessionFactory();

La clase Configuration es una clase del marco NHibernate. La instrucción anterior utiliza la sección de configuración de NHibernate en [App.config]. El objeto [ISessionFactory] obtenido contiene entonces la:

  • información para crear una conexión con la base de datos de destino
  • archivos de mapeo entre las tablas de la base de datos y las clases persistentes gestionadas por NHibernate.

6.4.2. La sesión NHibernate

Una vez creado SessionFactory (esto se hace una sola vez), se pueden obtener las sesiones que permiten realizar operaciones de persistencia NHibernate. Un código habitual es el siguiente:


try{
      // inicio de sesión 
      using (ISession session = sessionFactory.OpenSession())
      {
        // inicio de transacción
        using (ITransaction transaction = session.BeginTransaction())
        {
........................ opérations de persistance
          // validación de la transacción
          transaction.Commit();
        }
      }
}catch (Exception ex){
....
}
  • línea 3: se crea una sesión a partir de SessionFactory dentro de una cláusula using. Al salir de la cláusula using, la sesión se cerrará automáticamente. Sin la cláusula using, habría que cerrar la sesión explícitamente (session.Close()).
  • Línea 6: las operaciones de persistencia se realizarán dentro de una transacción. O bien se completan todas con éxito, o bien ninguna se completa con éxito. Dentro de la cláusula «using», la transacción se confirma mediante un «Commit» (línea 10). Si, dentro de la transacción, una operación de persistencia lanza una excepción, la transacción se anulará automáticamente mediante un «Rollback» al salir de la cláusula «using».
  • El try/catch de las líneas 1 y 13 permite interceptar una posible excepción lanzada por el código dentro del try (sesión, transacción, persistencia).

6.4.3. La interfaz ISession

A continuación, presentamos algunos de los métodos de la interfaz ISession implementada por una sesión NHibernate:

ITransaction BeginTransaction()
inicia una transacción en la sesión
ITransaction tx=session.BeginTransaction();
void Clear()
vacía la sesión. Los objetos que contenía quedan desasociados.
session.Clear();
void Close()
cierra la sesión. Los objetos que contenía se sincronizan con la base de datos. Esta operación de sincronización también se realiza al final de una transacción. Este último caso es el más habitual.
session.Close();
IQuery CreateQuery(string queryString)
crea una consulta HQL (Hibernate Query Language) para su ejecución posterior.
IQuery query=session.createQuery("select e from Empleado e);
void Delete(object obj)
elimina un objeto. Este puede pertenecer a la sesión (adjunto) o no (separado). Al sincronizar la sesión con la base de datos, se realizará una operación SQL DELETE sobre este objeto.
// se carga un empleado de la BD
Empleado e = session.Get<Empleado>(143);
// se elimina
session.Delete(e);
void Flush()
fuerza la sincronización de la sesión con la base de datos. El contenido de la sesión no cambia.
session.Flush();
T Get<T>(object id)
busca en la base de datos el objeto T con la clave primaria id. Si este objeto no existe, establece el puntero en nulo.
// se carga un empleado de la BD
Empleado e = session.Get<Empleado>(143);
object Save(object obj)
coloca el objeto obj en la sesión. Este objeto no tiene clave primaria antes de guardar. Después de guardar, tiene una. Durante la sincronización de la sesión, se realizará una operación SQL INSERT en la base de datos.
// creamos un empleado
Empleado e = new Empleado(){...};
// lo guardamos
e = session.Save(e);
SaveOrUpdate(object obj)
Realiza una operación Save si obj no tiene clave primaria o una operación Update si ya tiene una.
void Update(object obj)
actualiza el objeto obj en la base de datos. A continuación, se realiza una operación SQL UPDATE en la base.
// se carga un empleado de la BD
Empleado e = session.Get<Empleado>(143);
// se cambia su nombre
e.Nom=...;
// se actualiza en la base
session.Update(e);

6.4.4. La interfaz IQuery

La interfaz IQuery permite consultar la base de datos para extraer datos. Hemos visto cómo crear una instancia:

IQuery query=session.createQuery("select e from Employe e);

El parámetro del método createQuery es una consulta HQL (Hibernate Query Language), un lenguaje similar al SQL, pero que consulta clases en lugar de tablas. La consulta anterior solicita la lista de todos los empleados. A continuación se muestran algunos ejemplos de consultas HQL:

select e from Employe e where e.Nom like 'A%'
select e from Employe order by e.Nom asc
select e from Employe e where e.Indemnites.Indice=2

A continuación, presentamos algunos de los métodos de la interfaz IQuery:

IList<T> List<T>()
devuelve el resultado de la consulta en forma de una lista de objetos T
IList<Empleado> empleados=session.createQuery("select e from Empleado e order by e.Nom asc").List<Empleado>();
IList List()
devuelve el resultado de la consulta en forma de lista, donde cada elemento de la lista representa una fila del resultado de la consulta Select en forma de matriz de objetos.
IList líneas=session.createQuery("select e.Nom, e.Prenom, e.SS from Empleado e").List();
líneas[i][j] representa la columna j de la línea i en un tipo objeto. Así, líneas[10][1] es un tipo objeto que representa el nombre de una persona. Por lo general, se necesitan conversiones de tipo para recuperar los datos en su tipo exacto.
T UniqueResult<T>()
devuelve el primer objeto del resultado de la consulta
Empleado e=session.createQuery("select e from Empleado e where e.Nom='MARTIN'").UniqueResult<Empleado>();

Se puede configurar una consulta HQL:

1
2
3
string numSecu;
...
Employe e=session.createQuery("select e from Employe e where e.SS=:num").SetString("num",numSecu).UniqueResult<Employe>();

En la consulta HQL de la línea 3, :num es un parámetro que debe recibir un valor antes de que se ejecute la consulta. En el ejemplo anterior, se utiliza el método SetString para ello. La interfaz IQuery dispone de varios métodos Set para asignar un valor a un parámetro:

  • - SetBoolean(string name, bool value)
  • - SetSingle(string name, single value)
  • - SetDouble(string name, valor doble)
  • - SetInt32(string name, valor int32)
  • ..

6.5. Algunos ejemplos de código

Los siguientes ejemplos se basan en la arquitectura estudiada anteriormente y que se recuerda a continuación. La base de datos es la base de datos MySQL [dbpam_nhibernate], también presentada. Los ejemplos son programas de consola [1] que utilizan el marco NHibernate [3] para manipular la base de datos [2].

El proyecto C# en el que se insertan los ejemplos que siguen es el ya presentado:

  • en [1], los DLL que necesita el proyecto:
    • [NHibernate]: el DLL del marco NHibernate
    • [MySql.Data]: el DLL del conector ADO.NET del SGBD MySQL 5
    • [log4net]: el DLL de una herramienta que permite generar registros
  • en [2], las clases de imágenes de las tablas de la base de datos
  • en [3], el archivo [App.config] que configura toda la aplicación, incluido el marco [NHibernate]
  • en [4], y aplicaciones de consola de prueba. Son estas últimas las que vamos a presentar parcialmente.

6.5.1. Obtener el contenido de la base de datos

El programa [ShowDataBase.cs] permite visualizar el contenido de la base de datos:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class ShowDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // programa principal
    static void Main(string[] args)
    {
      // inicialización de la fábrica NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // visualización del contenido de la base
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase1();
      }
      catch (Exception ex)
      {
        // se muestra la excepción 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // espera de teclado
      Console.ReadLine();
    }

    // prueba1
    static void ShowDataBase1()
    {
      // inicio de sesión 
      using (ISession session = sessionFactory.OpenSession())
      {
        // inicio de transacción
        using (ITransaction transaction = session.BeginTransaction())
        {
          // se recupera la lista de empleados
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e order by e.Nom asc").List<Employe>();
          // se muestra
          Console.WriteLine("--------------- liste des employés");
          foreach (Employe e in employes)
          {
            Console.WriteLine(e);
          }
          // se recupera la lista de indemnites
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i order by i.Indice asc").List<Indemnites>();
          // se muestra
          Console.WriteLine("--------------- liste des indemnités");
          foreach (Indemnites i in indemnites)
          {
            Console.WriteLine(i);
          }
          // se recupera la lista de cotisations
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- tableau des taux de cotisations");
          Console.WriteLine(cotisations);
          // confirmar transacción
          transaction.Commit();
        }
      }
    }
  }
}

Explicaciones:

  • línea 19: se crea el objeto SessionFactory. Este es el que nos permitirá obtener objetos Session.
  • línea 24: se muestra el contenido de la base
  • líneas 31-37: se cierra SessionFactory en la cláusula finally del try.
  • línea 43: el método que muestra el contenido de la base
  • línea 46: se obtiene una sesión de SessionFactory.
  • línea 49: se inicia una transacción
  • línea 52: consulta HQL para recuperar la lista de empleados. Debido a la clave externa que vincula la entidad Empleado con la entidad Indemnización, con cada empleado se obtendrá su indemnización.
  • línea 60: consulta HQL para obtener la lista de indemnizaciones.
  • línea 68: consulta HQL para obtener la única línea de la tabla cotisations.
  • línea 72: fin de la transacción
  • línea 73: fin del using Itransaction de la línea 49 – la transacción se cierra automáticamente
  • línea 74: fin del using Isession de la línea 46 – la sesión se cierra automáticamente.

Pantalla obtenida:

Affichage base -------------------------------------
--------------- lista de empleados
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[1|1,93|2|3|12]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[2|2,1|2,1|3,1|15]]

--------------- lista de indemnizaciones
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tabla de tarifas de cotisations
[3,49|6,15|9,39|7,88]

Cabe destacar, en las líneas 3 y 4, que al solicitar los datos de un empleado, también se ha obtenido su indemnización.

6.5.2. Insertar datos en la base de datos

El programa [FillDataBase.cs] permite insertar datos en la base:


using System;
using System.Collections;
using System.Collections.Generic;
using NHibernate;
using NHibernate.Cfg;


namespace PamNHibernateDemos
{
  public class FillDataBase
  {

    private static ISessionFactory sessionFactory = null;

    // programa principal
    static void Main(string[] args)
    {
      // inicialización de fábrica NHibernate
      sessionFactory = new Configuration().Configure().BuildSessionFactory();
      try
      {
        // eliminación del contenido de la base
        Console.WriteLine("Effacement base -------------------------------------");
        ClearDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
        Console.WriteLine("Remplissage base -------------------------------------");
        FillDataBase1();
        Console.WriteLine("Affichage base -------------------------------------");
        ShowDataBase();
      }
      catch (Exception ex)
      {
        // se muestra la excepción 
        Console.WriteLine(string.Format("L'erreur suivante s'est produite : [{0}]", ex.ToString()));
      }
      finally
      {
        if (sessionFactory != null)
        {
          sessionFactory.Close();
        }
      }
      // espera de teclado
      Console.ReadLine();
    }

    // prueba1
    static void ShowDataBase()
    {
             // ver ejemplo anterior
    }

    // ClearDataBase1
    static void ClearDataBase1()
    {
      // inicio de sesión 
      using (ISession session = sessionFactory.OpenSession())
      {
        // inicio de transacción
        using (ITransaction transaction = session.BeginTransaction())
        {
          // se recupera la lista de empleados
          IList<Employe> employes = session.CreateQuery(@"select e from Employe e").List<Employe>();
          // se eliminan todos los empleados
          Console.WriteLine("--------------- suppression des employés associés");
          foreach (Employe e in employes)
          {
            session.Delete(e);
          }
          // se recupera la lista de indemnizaciones
          IList<Indemnites> indemnites = session.CreateQuery(@"select i from Indemnites i").List<Indemnites>();
          // se eliminan las indemnizaciones
          Console.WriteLine("--------------- suppression des indemnités");
          foreach (Indemnites i in indemnites)
          {
            session.Delete(i);
          }
          // se recupera la lista de cotisations
          Cotisations cotisations = session.CreateQuery(@"select c from Cotisations c").UniqueResult<Cotisations>();
          Console.WriteLine("--------------- suppression des taux de cotisations");
          if (cotisations != null)
          {
            session.Delete(cotisations);
          }
          // confirmar transacción
          transaction.Commit();
        }
      }
    }

    // FillDataBase
    static void FillDataBase1()
    {
      // inicio de sesión 
      using (ISession session = sessionFactory.OpenSession())
      {
        // inicio de transacción
        using (ITransaction transaction = session.BeginTransaction())
        {
          // se crean dos indemnizaciones
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 2, BaseHeure = 2.1, EntretienJour = 2.1, RepasJour = 3.1, IndemnitesCp = 15 };
          // se crean dos empleados
          Employe e1 = new Employe() { Id = 0, SS = "254104940426058", Nom = "Jouveinal", Prenom = "Marie", Adresse = "5 rue des oiseaux", Ville = "St Corentin", CodePostal = "49203", Indemnites = i1 };
          Employe e2 = new Employe() { Id = 0, SS = "260124402111742", Nom = "Laverti", Prenom = "Justine", Adresse = "La Brûlerie", Ville = "St Marcel", CodePostal = "49014", Indemnites = i2 };
          // se crean las tarifas de cotisations
          Cotisations cotisations = new Cotisations() { Id = 0, CsgRds = 3.49, Csgd = 6.15, Secu = 9.39, Retraite = 7.88 };
          // se guarda todo
          session.Save(e1);
          session.Save(e2);
          session.Save(cotisations);
          // se confirma la transacción
          transaction.Commit();
        }
      }
    }

  }
}

Explicaciones

  • línea 19: se crea SessionFactory
  • líneas 37-43: se cierra en la cláusula finally del try
  • línea 55: el método ClearDataBase1 que vacía la base de datos. El principio es el siguiente:
    • se recuperan todos los empleados (línea 64) en una lista
    • se eliminan uno a uno (líneas 67-70)
  • línea 93: el método FillDataBase1 inserta algunos datos en la base de datos
  • se crean dos entidades Indemnites (líneas 102, 103)
  • se crean dos empleados con estas indemnizaciones (líneas 105, 106)
  • se crea un objeto Cotisations en la línea 108.
  • líneas 110, 111: las dos entidades Empleado se guardan en la base de datos
  • línea 112: la entidad Cotisations se guarda a su vez
  • puede sorprender que las entidades Indemnités de las líneas 102 y 103 no se hayan guardado. De hecho, se guardaron al mismo tiempo que las entidades Employe. Para entenderlo, hay que volver al mapeo de la entidad Employe:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="PamNHibernateDemos" assembly="pam-nhibernate-demos">
    <class name="Employe" table="EMPLOYES">
        <id name="Id" column="ID" unsaved-value="0">
            <generator class="native" />
        </id>
        <version name="Version" column="VERSION"/>
        <property name="SS" column="SS"/>
        <property name="Nom" column="NOM"/>
        <property name="Prenom" column="PRENOM"/>
        <property name="Adresse" column="ADRESSE"/>
        <property name="Ville" column="VILLE"/>
        <property name="CodePostal" column="CP"/>
        <many-to-one name="Indemnites" column="INDEMNITE_ID" cascade="save-update" lazy="false"/>
    </class>
</hibernate-mapping>

La línea 15, que mapea la relación de clave externa entre la entidad Empleado y la entidad Indemnites, tiene el atributo cascade= "save-update", lo que implica que las operaciones «save» y «update» de la entidad Empleado se propagan a la entidad interna Indemnites.

Pantalla obtenida:

Effacement base -------------------------------------
--------------- eliminación de los empleados y de las indemnizaciones asociadas
--------------- eliminación de las indemnizaciones restantes
--------------- eliminación de las tasas de cotisations
Affichage base -------------------------------------
--------------- lista de empleados
--------------- lista de indemnizaciones
--------------- tabla de tarifas de cotisations

Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- lista de empleados
[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
[260124402111742|Laverti|Justine|La Brûlerie|St Marcel|49014|[1|1,93|2|3|12]]
--------------- lista de indemnizaciones
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tabla de tarifas de cotisations
[3,49|6,15|9,39|7,88]

6.5.3. Búsqueda de un empleado

El programa [Program.cs] cuenta con diversos métodos que ilustran el acceso y la manipulación de los datos de la base. A continuación, presentamos algunos de ellos.

El método [FindEmployee] permite buscar a un empleado por su número de la Seguridad Social:


// FindEmployee
    static void FindEmployee() {
      try {
        // inicio de sesión 
        using (ISession session = sessionFactory.OpenSession()) {
          // inicio de transacción
          using (ITransaction transaction = session.BeginTransaction()) {
            // búsqueda de un empleado por su número SS
            String numSecu = "254104940426058";
            IQuery query = session.CreateQuery(@"select e from Employe e where e.SS=:numSecu");
            Employe employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            numSecu = "xx";
            employe = query.SetString("numSecu", numSecu).UniqueResult<Employe>();
            if (employe != null) {
              Console.WriteLine("Employe[" + numSecu + "]=" + employe);
            } else {
              Console.WriteLine("Employe[" + numSecu + "] non trouvé...");
            }

            // confirmar transacción
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicaciones

  • línea 10: la consulta Select configurada por numSecu que se va a ejecutar
  • línea 11: la asignación de un valor al parámetro numSecu y la ejecución del método UniqueResult para obtener un único resultado.

Pantalla obtenida:

Recherche d'un employé -------------------------------------
Employe[254104940426058]=[254104940426058|Jouveinal|Marie|5 rue des oiseaux|St Corentin|49203|[2|2,1|2,1|3,1|15]]
Employe[xx] non trouvé...

6.5.4. Inserción de entidades no válidas

El siguiente método intenta guardar una entidad [Employe] no inicializada.


// SaveEmptyEmployee
    static void SaveEmptyEmployee() {
      try {
        // inicio de sesión 
        using (ISession session = sessionFactory.OpenSession()) {
          // inicio de transacción
          using (ITransaction transaction = session.BeginTransaction()) {
            // se crea un empleado vacío
            Employe e = new Employe();
            // se crea una indemnización inexistente
            Indemnites i = new Indemnites() { Id = 0, Indice = 3, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // que se asocia al empleado
            e.Indemnites = i;
            // se guarda el empleado dejando vacíos los demás campos
            session.Save(e);
            // se confirma la transacción
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicaciones

Recordemos el código de la clase [Employe]:


namespace PamNHibernateDemos {
    public class Employe {
        // propiedades automáticas
        public virtual int Id { get; set; }
        public virtual int Version { get; set; }
        public virtual string SS { get; set; }
        public virtual string Nom { get; set; }
        public virtual string Prenom { get; set; }
        public virtual string Adresse { get; set; }
        public virtual string Ville { get; set; }
        public virtual string CodePostal { get; set; }
        public virtual Indemnites Indemnites { get; set; }

        // constructores
        public Employe() {
        }

        // ToString
        public override string ToString() {
            return string.Format("[{0}|{1}|{2}|{3}|{4}|{5}|{6}]", SS, Nom, Prenom, Adresse, Ville, CodePostal, Indemnites);
        }
    }
}

Un objeto [Employe] no inicializado tendrá el valor nulo en todos sus campos de tipo string. Al insertar el registro en la tabla [employes], NHibernate dejará vacías las columnas correspondientes a estos campos. Sin embargo, en la tabla [employes], todas las columnas tienen el atributo not nulo, lo que impide que haya columnas sin valor. El controlador ADO.NET lanzará entonces una excepción:

sauvegarde d'un employé vide -------------------------------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Employe][SQL: INSERT INTO EMPLOYES (VERSION, SS, NOM, PRENOM, ADRESSE, VILLE, CP, INDEMNITE_ID) VALUES (?, ?, ?, ?, ?, ?, ?, ?)]

6.5.5. Creación de dos indemnizaciones con el mismo índice dentro de una transacción

En la tabla [indemnites], la columna [indice] se ha declarado con el atributo único, lo que impide tener dos filas con el mismo índice. El siguiente método crea dos indemnizaciones con el mismo índice dentro de una transacción:


// CreateIndemnites1
    static void CreateIndemnites1() {
      try {
        // inicio de sesión 
        using (ISession session = sessionFactory.OpenSession()) {
          // inicio de transacción
          using (ITransaction transaction = session.BeginTransaction()) {
            // se crean dos indemnizaciones con el mismo índice
            Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
            // se guardan
            session.Save(i1);
            session.Save(i2);
            // confirmar transacción
            transaction.Commit();
          }
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicaciones

  • En las líneas 9 y 10, se crean dos entidades Indemnites con el mismo índice. Sin embargo, en la base de datos, la columna INDICE tiene el atributo UNIQUE.
  • Las líneas 12 y 13 colocan las dos entidades Indemnites en el contexto de persistencia. Este se sincroniza con la base de datos al validar la transacción de la línea 15. Esta sincronización provocará dos INSERT. El segundo provocará una excepción debido a la unicidad de la columna INDICE. Como nos encontramos dentro de una transacción, el primer INSERT se revertirá.

El resultado obtenido es el siguiente:

Effacement base -------------------------------------
--------------- eliminación de empleados
--------------- eliminación de indemnizaciones
--------------- eliminación de las tasas de cotisations
Création de deux indemnités de même indice dans une transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- lista de empleados
--------------- lista de indemnizaciones
--------------- tabla de tarifas de cotisations

En la línea 9, se puede ver que la tabla [indemnites] está vacía. No se ha producido ninguna inserción.

6.5.6. Creación de dos indemnizaciones del mismo índice fuera de la transacción

El siguiente método crea dos indemnizaciones con el mismo índice sin utilizar ninguna transacción:


// CreateIndemnites2
    static void CreateIndemnites2() {
      try {
        // inicio de sesión 
        using (ISession session = sessionFactory.OpenSession()) {

          // se crean dos indemnizaciones con el mismo índice
          Indemnites i1 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.93, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          Indemnites i2 = new Indemnites() { Id = 0, Indice = 1, BaseHeure = 1.94, EntretienJour = 2, RepasJour = 3, IndemnitesCp = 12 };
          // se guardan
          session.Save(i1);
          session.Save(i2);
        }
      } catch (Exception e) {
        Console.WriteLine("L'exception suivante s'est produite : " + e.Message);
      }
    }

Explicaciones

  • Tenemos el mismo código que antes, pero sin transacción.
  • La sincronización del contexto de persistencia con la base de datos se realizará al cerrar este contexto, línea 13 (cierre de la sesión). La sincronización provocará dos INSERT. El segundo fallará debido a la unicidad de la columna INDICE. Pero como no estamos en una transacción, el primer INSERT no se deshará.

El resultado obtenido es el siguiente:

1
2
3
4
5
6
7
Création de deux indemnités de même indice sans transaction --------------
L'exception suivante s'est produite : could not insert: [PamNHibernateDemos.Indemnites][SQL: INSERT INTO INDEMNITES (VERSION, INDICE, BASE_HEURE, ENTRETIEN_JOUR, REPAS_JOUR, INDEMNITES_CP) VALUES (?, ?, ?, ?, ?, ?)]
Affichage base -------------------------------------
--------------- lista de empleados
--------------- lista de indemnizaciones
[1|1,93|2|3|12]
--------------- tabla de tarifas de cotisations

La base estaba vacía antes de la ejecución del método. En la línea 6, se puede ver que la tabla [indemnites] tiene una fila.