NHibernate en .NET

NHibernate es un ORM (Object/Relational Mapping) cuyo propósito principal es el mapeo entre entidades de una base de datos y objetos de una aplicación,  permitiéndonos mover información de la base de datos a objetos y viceversa. NHibernate (desde adelante NH) originalmente se desarrollo para Java, y luego surgió una versión que se adapto al framework .NET.

Es importante también destacar que NH es software libre y la URL del proyecto es http://nhforge.org.

En este post vamos a crear una aplicaeción .NET e implementar NH para el acceso a los datos (si trabajáramos con una arquitectura de N-Capas, lo utilizaríamos en la DAO). El primer paso es descargar los binarios desde el sitio del proyecto y descomprimirlos en alguna carpeta de su equipo. Para cuando escribí este post descargue la versión 3.0.0.CR1.

Bien, el siguiente paso será crear una solución en Visual Studio 2010 (o la versión que prefieran) y agregar un proyecto del tipo biblioteca de clases. En el mismo vamos a agregar las siguientes referencias desde la ubicación donde descomprimimos nuestros archivos en el paso uno (más precisamente dentro del directorio Required_Bins):

Referencias a NHibernate

Referencias a NHibernate

Dentro de este proyecto vamos a crear una entidad de negocio que denominaremos Usuario:

public class Usuario
{
    public int Id { get; set; }
    public string Nombre { get; set; }
    public int Edad { get; set; }
    public bool Habilitado { get; set; }
}

Esta clase representa una entidad de nuestra aplicación y dentro del proyecto la crearemos dentro de la carpeta “Entities” para una mejor organización.

Pues bien, por el momento no vimos nada raro. Ahora lo que trataremos de hacer es persistir-recuperar instancias de esta entidad desde una base de datos. Pero antes de continuar vamos a crear en nuestra base de datos la tabla Usuarios con la siguiente estructura:

Tabla Usuarios

Tabla Usuarios

Ahora tenemos que relacionar nuestra entidad Usuario con nuestra tabla Usuarios. Para esto debemos hacer el mapeo correspondiente entre ellas y lo primero que vamos a crear es el archivo XML Usuario.hbm.xml dentro de la carpeta Mappers de nuestro proyecto (a modo informativo: dentro del nombre del archivo XML incluimos “hbm” debido a que por convención NHibernate reconoce a los archivos de mapeo de esa forma).

El archivo XML de mapping es el nucleo de la aplicación, ya que el mismo tiene la correspondencia entre el formato de base de datos y el del objeto.

Antes de comenzar a armar el mapping, vamos a buscar dentro del directorio en el que descargamos las librerías de NH un archivo llamado nhibernate-mapping.xsd el cual vamos a moverlo dentro de una nueva carpeta del proyecto, en nuestro caso la llamaremos ”Recursos”. Este archivo vamos a asociarlo nuestro XML de usuarios recientemente creado: haciendo click derecho sobre el XML, vamos a propiedades y en Esquemas ponemos el path correspondiente al XSD.

Asociando nuestro XML de mapeo al schema de NH.

Asociando nuestro XML de mapeo al schema de NH.

A modo informativo: en Visual Studio, si queremos que un archivo XML se valide contra un schema y además tener disponible la funcionalidad de Intellisense, es necesario asociarlo al XSD correspondiente. Para una correcta definición de schemmas ver el siguiente enlace aquí.

Bien, llego el momento de configurar el mapeo!. Cada archivo de mapping que creemos deberé tener es siguiente nodo padre:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                   assembly="NHibernateLibrary"
                   namespace="NHibernateLibrary.Entities">

</hibernate-mapping>

En este nodo vamos a setear dos propiedades importantes, el assembly de nuestra aplicación y el namespace donde está nuestra entidad creada. Esto nos evita tener que escribir los namespaces completos de nuestras clases en otras partes del XML.

Bien, llego el momento de comenzar a armar el mapping correspondiente:

<?xml version="1.0" encoding="utf-8" ?>
   <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
                      assembly="NHibernateLibrary"
                      namespace="NHibernateLibrary.Entities">

      <class name="Usuario" table="Usuarios">
         <id name="Id"></id>
         <properties name="Nombre"></properties>
         <properties name="Edad"></properties>
         <properties name="Habilitado"></properties>
      </class>

</hibernate-mapping>

Podemos ver que el id lo definimos de una manera diferente al resto de las propiedades, esto nos permite indicarle que dicha propiedad se mapeara con la clave primaria de la tabla. Una de las cosas que debemos tener en cuenta, es que en el caso de que la propiedad se llame igual a la columna de la tabla a la que estamos mapeando, no hace falta indicarlo. En nuestro caso las propiedades se llaman igual, pero no la clase y la tabla, por lo tanto debemos indicar esto asignando el atributo “table”. Lo mismo ocurre con los tipos de datos, por ejemplo veamos la definición de una propiedad que no se corresponde con el nombre de la columna en la tabla:

   <property name="Nombre" column="Name"/>

Un atributo que no hemos marcado en el mapping pero que viene activado por defecto y vale la pena explicarlo es lazy=”true”. Con esto activamos la carga perezosa, ó lazy load, de modo que las colecciones no se cargarán hasta que sean utilizadas. Este funcionamiento se logra mediante un proxy y NH se encarga de manejarlo internamente.

Bien, tenemos el mapeo armado, ahora configuremos la información de nuestra base de datos. Para esto vamos a crear un nuevo archivo XML llamado hibernate.cfg.xml (el cual contendrá la configuración necesaria para conectarnos a la base de datos). El mismo lo ubicaremos dentro del directorio “Config” y antes de continuar seteamos la propiedad “Copiar en el directorio de resultados” con el valor “Copiar siempre”, porque de no realizar este cambio los archivos de mapeo y configuración no serán incluidos dentro del assembly final. Nota: realizar las misma configuración con nuestro archivo de mapeo.

Propiedades XML de configuración

Propiedades XML de configuración

También le asignaremos el siguiente esquema (para esto buscaremos el archivo nhibernate-configuration.xsd dentro de lo descargado al inicio y lo pondremos de la carpeta “Recursos”):

Asigando el schema a nuestro archivo de configuración.

Asigando el schema a nuestro archivo de configuración.

Bien, llego el momento de realizar la configuración:

<?xml version="1.0" encoding="utf-8" ?>
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">  
   <session-factory>    
      <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>    
      <property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>    
      <property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>    
      <property name="connection.connection_string">Server=XXXX;database=NHExample;Integrated Security=SSPI;</property>    
      <property name="show_sql">true</property>  
   </session-factory>
</hibernate-configuration>

Lo primero que debemos hacer es configurar el dialecto o idioma de la DB con la que vamos a trabajar. En nuestro caso vamos hacerlo con una base de datos SQL Server 2008, por lo tanto el dialecto será el correspondiente a dicho motor, lo mismo para el driver. Para ver los distintos tipos de base de datos que soporta NHibernate (los cuales son muchísimos!) podemos darle un vistazo al namesapce NHibernate.Dialect:

Algunos de los motores de base de datos soportados.

Algunos de los motores de base de datos soportados.

Bien llegado a este punto tenemos terminada las configuraciones de mapeo y de base de datos. El próximo paso será crear una clase que instanciada nos permita conectarnos mediante NH a la base de datos y exponernos un objeto del tipo ISession para poder realizar las operaciones CRUD (create, read, update and delete) contra la DB. A dicha clase la llamaremos NHibernateManager:

public class NHibernateManager : IDisposable
{
    private static ISessionFactory _sessionFactory;
    public ISession Session;

    public NHibernateManager()
    {
        CreateSession();
        OpenSession();
    }

    private void CreateSession()
    {
        if (_sessionFactory == null)
        {
            var configuration = new Configuration().Configure("Config/hibernate.cfg.xml");
            configuration.AddAssembly("NHibernateLibrary");
            _sessionFactory = configuration.BuildSessionFactory();
        }

        Session = OpenSession();    
   }

   public static ISession OpenSession()
   {
        return _sessionFactory.OpenSession();
   }

   public static void CloseSession()
   {
        _sessionFactory.Dispose();
   }

   #region Miembros de IDisposable
   public void Dispose()
   {
        CloseSession();
   }
   #endregion
}

Ahora vamos a crear nuestras clases encargadas de las operaciones CRUD de nuestras entidades. Para esto vamos a crear una interfaz donde vamos a escribir las firmas para estas operaciones y luego implementarlas. La interfaz vamos a llamarla IUsuarioRepository:

public interface IUsuarioRepository
{
    void Add(Usuario usuario);
    void Update(Usuario usuario);
    void Remove(Usuario usuario);
    Usuario GetById(int id);
    Usuario GetByNombre(string nombre);
}

Y ahora la implementación de la misma en la clase UsuarioRepository (dentro de cada uno de los métodos podemos observar como realizan los distintos tipos de operaciones por medio de NH):

public class UsuarioRepository : IUsuarioRepository
{
   #region Miembros de IUsuarioRepository
   public void Add(Usuario usuario)    
   {
        using (NHibernateManager manager = new NHibernateManager())
        {
            using (ITransaction transaction = manager.Session.BeginTransaction())
            {
                manager.Session.Save(usuario);
                transaction.Commit();
            }
        }
   }
   public void Update(Usuario usuario)
   {
        using (NHibernateManager manager = new NHibernateManager())
        {
            using (ITransaction transaction = manager.Session.BeginTransaction())
            {
                manager.Session.Update(usuario);
                transaction.Commit();
            }
        }
    }
    public void Remove(Usuario usuario)
    {
        using (NHibernateManager manager = new NHibernateManager())
        {
            using (ITransaction transaction = manager.Session.BeginTransaction())
            {
                manager.Session.Delete(usuario);
                transaction.Commit();
            }
        }
    }
    public Usuario GetById(int id)
    {
        using (NHibernateManager manager = new NHibernateManager())
        {
            return manager.Session.Get<Usuario>(id);
        }
    }
    public Usuario GetByNombre(string nombre)
    {
        using (NHibernateManager manager = new NHibernateManager())
        {
            return manager.Session.CreateCriteria(typeof(Usuario)).Add(Restrictions.Eq("Nombre", nombre)).UniqueResult<Usuario>();
        }
    }
    #endregion
}

Bueno, para validar que todo lo realizado sea correcto vamos a crear los test correspondientes para cada tipo de operación CRUD, en este caso vamos a realizar los test con NUnit:

[TestFixture]
public class NHibernateUsuarioTest
{
    private UsuarioRepository repositorio = null;

    [SetUp]
    public void Setup()
    {
        repositorio = new UsuarioRepository();
    }

    [Test]
    public void DeberíaObtenerUsuarioPorElId()
    {
        int id = 1;
        Assert.AreEqual(id, repositorio.GetById(id).Id);
    }

    [Test]
    public void DeberíaObtenerUsuarioPorElNombre()
    {
        string nombre = "Sebis";
        Assert.AreEqual(nombre, repositorio.GetByNombre(nombre).Nombre);
    }

    [Test]
    public void DeberíaCrearUsuario()
    {
        var nuevoUsuario= new Usuario { Nombre = "Juan", Edad = 28, Habilitado = true };
        repositorio.Add(nuevoUsuario);

        var usuario = repositorio.GetById(nuevoUsuario.Id);

        Assert.IsNotNull(usuario);
        Assert.AreNotSame(nuevoUsuario, usuario);
        Assert.AreEqual(nuevoUsuario.Nombre, usuario.Nombre);
        Assert.AreEqual(nuevoUsuario.Edad, usuario.Edad);
        Assert.AreEqual(nuevoUsuario.Habilitado, usuario.Habilitado);
    }

    [Test]
    public void DeberíaActualizarUsuario()
    {
        var editUsuario = repositorio.GetById(1);
        editUsuario.Nombre = "Sebis";
        editUsuario.Edad = editUsuario.Edad + 1;
        editUsuario.Habilitado = false;

        repositorio.Update(editUsuario);

        var usuario = repositorio.GetById(editUsuario.Id);

        Assert.IsNotNull(usuario);
        Assert.AreNotSame(editUsuario, usuario);
        Assert.AreEqual(editUsuario.Nombre, usuario.Nombre);
        Assert.AreEqual(editUsuario.Edad, usuario.Edad);
        Assert.AreEqual(editUsuario.Habilitado, usuario.Habilitado);
    }

    [Test]
    public void DeberíaEliminarUsuario()
    {
        var nuevoUsuario = new Usuario { Nombre = "Tito", Edad = 30, Habilitado = true };
        repositorio.Add(nuevoUsuario);

        var id = nuevoUsuario.Id;
        repositorio.Remove(nuevoUsuario);
        Usuario usuario = repositorio.GetById(id);

        Assert.IsNull(usuario);
        }

    [TearDown]
    public void TearDown()
    {
        repositorio = null;
    }
}

Corremos los test y validamos que los mismos sean correctos:

Resultados de los test

Resultados de los test

Finalmente realizaremos un ejemplo bien simple sobre una aplicación de consola:

class Program
{
    static void Main(string[] args)
    {
        UsuarioRepository repositorio = new UsuarioRepository();
        var u = repositorio.GetById(1);

        Console.Write(string.Format("Nombre: {0}, Edad: {1}, Habilitado: {2}", u.Nombre, u.Edad, u.Habilitado));
        Console.ReadLine();
    }
}

Cuando ejecutemos veremos en la consola que antes de imprimir el resultado, se muestra la consulta SQL que hace NH para recuperar los datos, esto es debido a que en la propiedad show_sql que seteamos en el XML de configuración la seteamos en true.

Resultado en consola

Resultado en consola

Eso es todo gente, existe muchísima información y cuestiones sobre este tema (por ejemplo cuando nuestras clases tienen otras clases como propiedades, la forma de manejar las sesiones, la forma de armar los test unitarios, etc), la idea fue darles una introducción básica al tema para que desde aquí puedan arrancar.

Espero que le sea de utilidad!

About these ads

20 Responses to NHibernate en .NET

  1. Hola amigo, me parece muy interesante nhibernate y toda su funcionalidad …… he realizado paso a paso este proyecto pero me aparece error cuando intento depurar con una aplicacion de consola ::: “No se puede cargar el archivo o ensamblado ‘NHibernate, Version=3.0.0.4000, Culture=neutral, PublicKeyToken=aa95f207798dfdb4′ ni una de sus dependencias. El sistema no puede encontrar el archivo especificado.”

    Y cuando hago el test con Nunit me aperecen errores de que no encuentra en ensamblado de “NHibernateLibrary” …… men que debo hacer ….. Gracias y saludos

  2. devteam dice:

    Reblogged this on Dev Team.

  3. Bernardo dice:

    Hola, estuve probando tu ejemplo y me aparece un error que dice: “could not execute batch command.[SQL: SQL not available].

    la verdad es que no pude encontrar mi error todavía, te agradecería una mano.

  4. Ariel dice:

    Holas Sebis,
    La Clase “Configuración” ya no funciona en el visual 2010 verdad? Quizá al actualizar el framework modificaron tal funcionalidad. Lo mismo me pasa con Restricción. Agrego las referencias al código donde tengo los errores de compilación:

    Error en “Configuration”: The type or namespace Configuration could not be found
    private void CreateSession()
    {
    if (_sessionFactory == null)
    {
    var configuration = new Configuration().Configure(“Config/hibernate.cfg.xml”);
    configuration.AddAssembly(“NHibernate”);
    _sessionFactory = configuration.BuildSessionFactory();
    }

    Session = OpenSession();
    }

  5. Ariel dice:

    Y a continuación el error con la Clase Restriction:

    Error: The name restricción does not exist in this context

    public Usuario GetByNombre(string nombre)
    {
    using (NHibernateManager manager = new NHibernateManager())
    {
    return manager.Session.CreateCriteria(typeof(Usuario)).Add(Restrictions.Eq(“Nombre”, nombre)).UniqueResult();
    }
    }

    • Sebis dice:

      Ariel, la verdad que no he trabajado con la última versión de NHibernate, pero no creo que esas clases hayan “desaparecido” en la nueva versión (al menos eso dice la documentación de la versión 3.3: http://nhforge.org/doc/nh/en/index.html#quickstart-intro. Probablemente te falte alguna referencia en el proyecto.

      Te recomiendo que instales la librería de NH con Nuget. Podes hacerlo desde el Package Manager Console ingresando el siguiente comando: PM> Install-Package NHibernate.

      En caso de que sigas teniendo el problema, lo revisamos.

      Abrazos!

  6. marbalM dice:

    Hola, podrías subir el código fuente? gracias

  7. Pablo dice:

    Estimado , implemente todo en un mvc2 pero me envia un error de en el archivo manger que no me encuentra el archivo de config .. hibernate.cfg.xml , por que motivo podria ser eso si dentro del proyecto cree una carpeta que dice Config/hibernate.cfg.xml ???

  8. Xavier_Pérez dice:

    Sebis, otra vez aquí dandote guerra. Fíjate que quiero hacer una especie de (SELECT * ) para una tabla… Lo hice de esta manera:

    public IList Display()
    {
    using (Connection manager = new Connection())
    {
    return manager.Session.CreateCriteria(typeof(tb_USUARIOS)).List();
    }
    }

    pero existe otra forma de hacerlo utilizando un poco tu metodología del ejemplo?, me refiero a que el resultado yo lo meti a una Lista, pero no se si sea la manera mas correcta.

Deja un comentario

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

Diego Bersano

Pequeños aportes de C# para seguir aprendiendo

sebys#

Un humilde blog de .NET

Seguir

Recibe cada nueva publicación en tu buzón de correo electrónico.

Únete a otros 283 seguidores