1. Introducción al ORM NHibernate
El PDF de este documento está disponible |AQUÍ|.
Los ejemplos del documento están disponibles |AQUÍ|.
Este documento es una breve introducción a NHibernate, el equivalente en .NET del marco de trabajo Java Hibernate. Para una introducción completa, puede consultarse:
Título: NHibernate in Action, Autor: Pierre-Henri Kuaté, Editorial: Manning, ISBN-13: 978-1932394924
Un ORM (mapeador objeto-relacional) es un conjunto de bibliotecas que permite a un programa que utiliza una base de datos acceder a ella sin emitir órdenes SQL explícitas y sin conocer las particularidades de la base de datos SGBD utilizada.
Requisitos previos
En una escala [débutant-intermédiaire-avancé], este documento se encuentra en la sección [intermédiaire]. Para comprenderlo se requieren varios requisitos previos que se pueden encontrar en algunos de los documentos que he escrito:
- Lenguaje C# 2008: [Aprendizaje del lenguaje C# versión 3.0 con el Framework .NET 3.5 (2008)]
- [Spring IoC], disponible en la URL [Spring IoC para .NET (2005)]. 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 inicio de los párrafos de este documento se incluyen recomendaciones de lectura. Estas hacen referencia a documentos anteriores.
Herramientas
Las herramientas utilizadas en este caso de estudio están disponibles gratuitamente en la web. Son las siguientes (diciembre de 2011):
- Nhibernate 3.2, disponible en la URL [http://nhforge.org/Default.aspx]
- Spring.net 1.3.2, disponible en la URL [http://www.springframework.net]. El marco Spring.net es muy completo. Aquí solo utilizaremos la biblioteca que incluye para facilitar el uso del marco Nhibernate.
- Log4net 1.2.10 está disponible en la URL [http://logging.apache.org/log4net]. Este marco de trabajo de registro es utilizado por NHibernate.
- NUnit 2.5 está disponible en la URL [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 está disponible en la 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]:
![]() |
1.1. El lugar que ocupa 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 la SGBD a través de la API y la ADO.NET. Recordemos los principales métodos de esta API.
En modo conectado, la aplicación:
- abre una conexión con la fuente de datos
- trabaja con la fuente de datos en modo de lectura/escritura
- cierra la conexión
Hay tres interfaces ADO.NET que se ven principalmente afectadas por estas operaciones:
- IDbConnection, que encapsula las propiedades y los métodos de la conexión.
- IDbCommand, que encapsula las propiedades y métodos del comando SQL ejecutado.
- IDataReader, que encapsula las propiedades y métodos del resultado de una orden SQL Select.
La interfaz IDbConnection
Sirve para administrar la conexión con la base de datos. Entre los métodos M y las propiedades P de esta interfaz se encuentran los siguientes:
Nombre | Tipo | Función |
P | cadena de conexión a la base de datos. Especifica todos los parámetros necesarios para establecer la conexión con una base de datos específica. | |
M | abre la conexión con la base de datos definida por ConnectionString | |
M | cierra la conexión | |
M | inicia una transacción. | |
P | estado de la conexión: ConnectionState.Closed, ConnectionState.Open, ConnectionState.Connecting, ConnectionState.Executing, ConnectionState.Fetching, ConnectionState.Broken |
Si Connection es una clase que implementa la interfaz IDbConnection, la conexión se puede establecer de la siguiente manera:
La interfaz IDbCommand
Sirve para ejecutar una orden SQL o un procedimiento almacenado. Entre los métodos M y las propiedades P de esta interfaz se encuentran los siguientes:
Nombre | Tipo | Función |
P | indica qué se debe ejecutar; toma sus valores de una enumeración: - CommandType.Text: ejecuta la orden SQL definida en la propiedad CommandText. Este es el valor por defecto. - CommandType.StoredProcedure: ejecuta un procedimiento almacenado en la base | |
P | - el texto de la orden SQL que se ejecutará si CommandType = CommandType.Text - el nombre del procedimiento almacenado que se ejecutará si CommandType = CommandType.StoredProcedure | |
P | la conexión IDbConnection que se utilizará para ejecutar la orden SQL | |
P | la transacción IDbTransaction en la que se ejecutará la orden SQL | |
P | la lista de parámetros de una orden SQL configurada. La orden «update articles set precio=precio*1.1 where id=@id» tiene el parámetro @id. | |
M | para ejecutar una orden SQL Select. Se obtiene un objeto IDataReader que representa el resultado de Select. | |
M | para ejecutar una orden SQL: Actualizar, Insertar, Eliminar. Se obtiene el número de filas afectadas por la operación (actualizadas, insertadas, eliminadas). | |
M | para ejecutar una orden SQL. Select solo devuelve un único resultado, como en: select count(*) from artículos. | |
M | para crear los parámetros IDbParameter de una orden SQL configurada. | |
M | permite optimizar la ejecución de una consulta parametrizada cuando se ejecuta varias veces con parámetros diferentes. |
Si Command es una clase que implementa la interfaz IDbCommand, la ejecución de una orden SQL sin transacción tendrá la siguiente forma:
La interfaz IDataReader
Sirve para encapsular los resultados de una orden SQL Select. Un objeto IDataReader representa una tabla con filas y columnas, que se procesan secuencialmente: primero la primera fila, luego la segunda, ... Entre los métodos M y las propiedades P de esta interfaz se encuentran los siguientes:
Nombre | Tipo | Función |
P | el número de columnas de la tabla IDataReader | |
M | GetName(i) devuelve el nombre de la columna n.º i de la tabla IDataReader. | |
P | Item[i] representa la columna n.º i de la fila actual de la tabla IDataReader. | |
M | pasa a la siguiente línea de la tabla IDataReader. Devuelve el valor booleano True si se pudo realizar la lectura; de lo contrario, devuelve False. | |
M | cierra la tabla IDataReader. | |
M | GetBoolean(i): devuelve el valor booleano de la columna n.º i de la fila actual de la tabla IDataReader. Los demás métodos análogos son los siguientes: GetDateTime, GetDecimal, GetDouble, GetFloat, GetInt16, GetInt32, GetInt64, GetString. | |
M | Getvalue(i): devuelve el valor de la columna n.º i de la fila actual de la tabla IDataReader como tipo object. | |
M | IsDBNull(i) devuelve True si la columna n.º i de la fila actual de la tabla IDataReader no tiene valor, lo cual se simboliza con el valor SQL NULL. |
La consulta de un objeto IDataReader suele ser similar a lo siguiente:
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 y MySQL
- la clase [SQLConnection] para SGBD y SQLServer
De este modo, la capa [dao] depende del SGBD utilizado. Algunos marcos de trabajo (Linq, Ibatis.net, NHibernate) eliminan esta restricción al agregar 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 comunica con el conector [ADO.NET], sino con el marco NHibernate, el cual le proporcionará una interfaz independiente del conector [ADO.NET] utilizado. Esta arquitectura permite cambiar el SGBD sin modificar la capa [dao]. En ese caso, solo se debe cambiar el conector [ADO.NET].
1.2. La base de datos de ejemplo
Para mostrar cómo trabajar con NHibernate, utilizaremos la siguiente base de datos MySQL [dbpam_nhibernate]:
![]() |
- En [1], la base de datos cuenta con tres tablas:
- [employes]: una tabla que registra a las empleadas de una guardería
- [cotisations]: una tabla que registra las tasas de cotizaciones sociales
- [indemnites]: una tabla que registra la información necesaria para calcular el salario de las empleadas
Tabla [employes]
![]() |
- en [2], la tabla de empleados, y en [3], el significado de sus campos
El contenido de la tabla podría ser el siguiente:
Tabla [cotisations]
![]() |
- en [4], la tabla de cotizaciones y en [5], el significado de sus campos
El contenido de la tabla podría ser el siguiente:
Tabla [indemnites]
![]() |
- en [6], la tabla de indemnizaciones y en [7], el significado de sus campos
El contenido de la tabla podría ser el siguiente:
La exportación de la estructura de la base de datos a un archivo SQL da el siguiente resultado:
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 agregue un registro. El desarrollador no tiene que preocuparse por ello.
1.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 en C# será el siguiente:
![]() |
Los elementos necesarios para el proyecto son los siguientes:
- en [1], los archivos 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
1.3.1. Configuración de la conexión a la base de datos
Volvamos a la arquitectura de prueba:
![]() |
Como se muestra arriba, [NHibernate] debe poder acceder a la base de datos. Para ello, necesita cierta información:
- el SGBD que administra la base de datos (MySQL, SQLServer, Postgres, Oracle, etc.). La mayoría de los SGBD han agregado al lenguaje SQL sus propias extensiones. Al conocer el SGBD, el NHibernate puede adaptar los comandos SQL que emite a ese SGBD. NHibernate utiliza el concepto de dialecto SQL.
- los parámetros de conexión a la base de datos (nombre de la base, nombre de usuario propietario de la conexión, su contraseña)
Esta información se puede incluir en el archivo de configuración [App.config]. A continuación se muestra el que se utilizará con una base de datos 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 de forma predeterminada. Es necesario activarlos mediante el programa con la instrucción log4net.Config.XmlConfigurator.Configure();
! -->
<log4net>
<!-- Define 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] <%X{auth}> - %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] <%X{auth}> - %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] <%X{auth}> - %m%n"/>
</layout>
</appender>
<!-- Configurar la categoría raíz, establecer el nivel de prioridad predeterminado y agregar 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] identifica la sección de configuración. Esta sección debe estar delimitada por las etiquetas <name>...</name>, en este caso <hibernate-configuration>...</hibernate-configuration>, en las líneas 11 a 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 la DLL que contiene dicha clase. En este caso, la clase se llama [NHibernate.Cfg.ConfigurationSectionHandler] y se encuentra en la clase DLL [NHibernate.dll]. Recordemos que esta clase DLL forma parte de las referencias del proyecto que estamos analizando.
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 la versión utilizada para configurar NHibernate. De hecho, con el paso del tiempo, la forma de configurar NHibernate ha cambiado. Aquí se utiliza la versión 2.2.
- línea 3: la configuración de NHibernate se encuentra aquí en su totalidad 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 se crea mediante un factory, un término genérico que designa una clase capaz de crear objetos. Las líneas 3 a 14 configuran este 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, es decir, aquel al que se le 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 debe utilizar. Es el nombre de una clase NHibernate especializada para un SGBD específico, 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 utilizará con el SGBD. Aquí se trata del dialecto de SGBD MySQL.
Si cambiamos a SGBD, ¿cómo encontramos el dialecto NHibernate correspondiente? Volvamos al proyecto de C# anterior y hagamos doble clic en el DLL [NHibernate] en la pestaña [References]:
![]() |
- en [1], la pestaña [Explorateur d'objets] muestra varios DLL, entre ellos los a los que hace referencia el proyecto.
- En [2], la DLL y la [NHibernate]
- en [3], la DLL y la [NHibernate] desarrolladas. En ellas se encuentran los distintos espacios de nombres (namespace) que están definidos.
- en [4], el espacio de nombres [NHibernate.Dialect], donde se encuentran las clases que definen los distintos dialectos SQL que se pueden utilizar.
- 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 que se utilice. Las cadenas de conexión para los principales controladores SGBD se pueden encontrar 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. Durante la fase de desarrollo, es útil establecer esta propiedad en [true] para saber exactamente qué hace NHibernate.
- línea 13: para entender 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. Más adelante veremos que el API de NHibernate permitirá que el programa de consola solicite la lista de empleados. NHibernate traducirá esta solicitud en una orden SQL Select que hará que el conector ADO.NET la ejecute. Este le devolverá un objeto de tipo IDataReader. A partir de este objeto, NHibernate debe poder construir la lista de empleados que se le solicitó. Esto es posible gracias a la configuración. A cada tabla de la base de datos se le asocia una clase de 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 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 DLL que se genera 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 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].
1.3.2. Configuración del « » de las tablas de mapeo <--> clases
Volvamos a la arquitectura del proyecto analizado:
![]() |
- 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 manejará objetos que reflejan las tablas de la base de datos. En este proyecto, estos objetos y los vínculos que los unen a las tablas de la base de datos se han colocado en la carpeta [Entites] a continuación:
![]() |
- cada tabla de la base de datos cuenta con una clase y un archivo de mapeo entre ambas
Tabla | Clase | Mapeo |
cuotas | Cotisations.cs | Cotisations.hbm.xml |
empleados | Employe.cs | Employe.hbm.xml |
indemnizaciones | Indemnites.cs | Indemnites.hbm.xml |
1.3.2.1. Mapeo de la tabla [cotisations]
Consideremos la tabla [cotisations]:
![]() |
|
Una fila 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; }
// fabricantes
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 (virtual), ya que NHibernate derivará de 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 (incluido el espacio de nombres) 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 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 que aún no se ha generado. 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. Por lo tanto, un objeto [Cotisations] que aún no se haya guardado (y que, por lo tanto, no tenga clave primaria) tendrá su campo Id=0. Si el campo Id hubiera sido de tipo Object o un 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 cuentan con un método propio para generar automáticamente este valor. La etiqueta <generator> sirve para definir el mecanismo que se utilizará para generar la clave primaria. La etiqueta <generator class="native"> indica que se debe utilizar el mecanismo predeterminado del SGBD en uso. En el párrafo 1.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 agregado, 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, la versión es 1. Se incrementa con cada operación UPDATE. Por otro lado, 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 cuenta con la versión correcta del mismo. De no ser 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 versión). Así, la línea 9 indica que la columna CSGRDS de la tabla [COTISATIONS] está asociada a la propiedad CsgRds de la clase [Cotisations].
1.3.2.2. Asignación de la tabla [indemnites]
Consideremos la tabla [indemnites]:
![]() |
|
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; }
// constructores
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>
Aquí no hay nada nuevo 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] existe una restricción de unicidad en la columna [INDICE]: no puede haber dos filas con el mismo valor en la columna [INDICE].
1.3.2.3. Mapeo de la tabla [employes]
Consideremos la tabla [employes]:
![]() |
|
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 utilizará para calcular las prestaciones del empleado.
La clase [Employe] imagen 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 asignar 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. Significa que cuando NHibernate recupere de la base de datos un objeto [Employe], también recuperará el objeto [Indemnites] que le corresponde.
- cascada: este atributo puede tener diversos valores:
- save-update: una operación de inserción (save) o actualización (update) en 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 ocurre de forma predeterminada. Es necesario configurarlo en el proyecto de 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.
1.4. de API a 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 que representa esa tabla y el archivo de mapeo que permite pasar de la clase a la tabla y viceversa.
Ahora nos queda descubrir los métodos que ofrece NHibernate para manipular los datos de la base: inserción, actualización, eliminación y lista.
1.4.1. El objeto SessionFactory
Toda operación de NHibernate se realiza dentro de una sesión. Una secuencia típica de operaciones de 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
Una sesión se obtiene de una fábrica de tipo [SessionFactory]. Esta fábrica es la que se configura mediante 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 cuenta entonces con la:
- información para establecer 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 que maneja NHibernate.
1.4.2. La sesión NHibernate
Una vez creado el objeto 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, sería necesario cerrar la sesión de manera explícita (session.Close()).
- Línea 6: las operaciones de persistencia se realizarán dentro de una transacción. O bien todas se realizan con éxito, o bien ninguna lo hace. Dentro de la cláusula using,, la transacción se valida mediante un Commit (línea 10). Si, dentro de la transacción, una operación de persistencia genera una excepción, la transacción se invalidará automáticamente mediante un Rollback al salir del using.
- El bloque try/catch de las líneas 1 y 13 permite interceptar cualquier excepción generada por el código dentro del bloque try (sesión, transacción, persistencia).
1.4.3. La interfaz ISession
A continuación, presentamos algunos de los métodos de la interfaz ISession implementada por una sesión NHibernate:
inicia una transacción en la sesión ITransaction tx=session.BeginTransaction(); | |
vacía la sesión. Los objetos que contenía quedan desasociados. session.Clear(); | |
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 común. session.Close(); | |
crea una consulta HQL (Hibernate Query Language) para su ejecución posterior. IQuery query=session.createQuery("select e from Empleado e); | |
elimina un objeto. Este puede pertenecer a la sesión (vinculado) o no (desvinculado). 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 tabla BD Empleado e = session.Get<Empleado>(143); // se elimina session.Delete(e); | |
fuerza la sincronización de la sesión con la base de datos. El contenido de la sesión no cambia. session.Flush(); | |
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 tabla BD Empleado e = session.Get<Empleado>(143); | |
coloca el objeto obj en la sesión. Este objeto no tiene clave primaria antes de la operación Save. Después del Save, sí 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); | |
Realiza una operación Save si obj no tiene una clave primaria, o una operación Update si ya tiene una. | |
actualiza el objeto obj en la base de datos. A continuación, se realiza una operación SQL UPDATE en la base de datos. // se carga un empleado de la operación BD Empleado e = session.Get<Empleado>(143); // se cambia su nombre e.Nom = ...; // se actualiza en la base de datos session.Update(e); |
1.4.4. La interfaz IQuery
La interfaz IQuery permite realizar consultas a la base de datos para extraer datos. Ya hemos visto cómo crear una instancia:
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:
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>(); | |
devuelve el resultado de la consulta en forma de una lista donde cada elemento de la lista representa una fila del resultado de la consulta Select en forma de una matriz de objetos. IList líneas = session.createQuery("select e.Nom, e.Prenom, e.SS from Empleado").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. | |
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:
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 cuenta con varios métodos Set para asignar un valor a un parámetro:
- - SetBoolean(string name, bool value)
- - SetSingle(nombre de cadena, valor único)
- - SetDouble(nombre de cadena, valor de tipo double)
- - SetInt32(nombre de cadena, valor int32)
- ..
1.5. Algunos ejemplos de código
Los siguientes ejemplos se basan en la arquitectura analizada anteriormente y que se resume 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 de trabajo NHibernate [3] para manipular la base de datos [2].
![]() |
El proyecto en C# en el que se insertan los ejemplos que siguen es el que ya se presentó:
![]() |
- en [1], los DLL que necesita el proyecto:
- [NHibernate]: el archivo DLL del marco de trabajo NHibernate
- [MySql.Data]: el DLL del conector ADO.NET del SGBD MySQL 5
- [log4net]: la 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 de trabajo [NHibernate]
- en [4], y las aplicaciones de consola de prueba. Son estas últimas las que presentaremos parcialmente.
1.5.1. Obtener el contenido de la base de datos
El programa [ShowDataBase.cs] permite mostrar 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 de datos
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 entrada del 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 indemnizaciones
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 cotizaciones
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 los objetos Session.
- línea 24: se muestra el contenido de la base de datos
- líneas 31-37: se cierra el objeto SessionFactory en la cláusula finally del objeto try.
- línea 43: el método que muestra el contenido de la base
- línea 46: se obtiene una Session a partir de la 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 Employe con la entidad Indemnite, junto 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 fila de la tabla de cotizaciones.
- 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:
Cabe destacar en las líneas 3 y 4 que al consultar a un empleado, también se obtuvo su indemnización.
1.5.2. Insertar datos en la base de datos
El programa [FillDataBase.cs] permite insertar datos en la base de datos:
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 la fábrica NHibernate
sessionFactory = new Configuration().Configure().BuildSessionFactory();
try
{
// eliminación del contenido de la base de datos
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 entrada del 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 cotizaciones
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 la 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 tasas de cotización
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 el 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 por 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: a su vez, se guarda la entidad «Cotizaciones»
- Puede resultar sorprendente 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 a la asignación 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 Employe y la entidad Indemnites, tiene el atributo cascade= "save-update", lo que implica que las operaciones "save » y «update» de la entidad Employe se propaguen a la entidad interna Indemnites.
Pantalla obtenida:
Effacement base -------------------------------------
--------------- suppression des employés et des indemnités associées
--------------- suppression des indemnités restantes
--------------- suppression des taux de cotisations
Affichage base -------------------------------------
--------------- liste des employés
--------------- liste des indemnités
--------------- tableau des taux de cotisations
Remplissage base -------------------------------------
Affichage base -------------------------------------
--------------- liste des employés
[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]]
--------------- liste des indemnités
[1|1,93|2|3|12]
[2|2,1|2,1|3,1|15]
--------------- tableau des taux de cotisations
[3,49|6,15|9,39|7,88]
1.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 seguro 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 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é...
1.5.4. Inserción de entidades no válidas
El siguiente método intenta guardar una entidad [Employe] que no está 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 sin datos
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 cadena. Al insertar el registro en la tabla [employes], NHibernate dejará vacías las columnas correspondientes a esos campos. Sin embargo, en la tabla [employes], todas las columnas tienen el atributo «not null», lo que impide que las columnas queden sin valor. Por lo tanto, el controlador ADO.NET lanzará una excepción:
1.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 «unique», lo que impide que haya 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);
// confirmación de la 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 en la línea 15. Esta sincronización generará 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:
En la línea 9, se puede observar que la tabla [indemnites] está vacía. No se ha realizado ninguna inserción.
1.5.6. Creación de dos indemnizaciones con el mismo índice fuera de una transacción
El siguiente método crea dos indemnizaciones con el mismo índice sin utilizar una transacción:
// CreateIndemnites2
static void CreateIndemnites2() {
try {
// inicio de sesión
using (ISession session = sessionFactory.OpenSession()) {
// se crean dos prestaciones 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, en la línea 13 (cierre de Session). La sincronización generará 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 revertirá.
El resultado obtenido es el siguiente:
La base de datos estaba vacía antes de ejecutar el método. En la línea 6, se puede ver que la tabla [indemnites] tiene una fila.























