Twitter Flickr Pinterest LinkedIn YouTube Google Maps E-mail RSS
formats

NHibernate, mi primera aplicación ORM, caso práctico.

Un ORM es una técnica-software para convertir datos orientados a objetos a una base de datos relacional, usando un motor de persistencia. Se crea una base de datos orientada a objetos virtual sobre una base de datos relacional.  Por lo tanto su principal ventaja es «olvidarse» de la tediosa labor e crear todas las sentencias SQL para obtener,actualizar, insertar o borrar datos (CRUD como veremos adelante) en la base, así como soporte para la persistencia.

Existen dos alternativas entre otras …

Hibernate es una herramienta ORM (Object-Relational Mapping) ó Mapeo Objeto Relacional para la plataforma JAVA.

NHibernate es la alternativa de software libre disponible para .NET en C#, distribuido bajo los térmimos LGPL. Es una de las primeras aproximaciones para el mundo .NET de proyectos ORM, a la cual se ha unido Microsoft con LinqToSQL y Entity Framework.

Vamos a tratar NHibernate. Al emplearle para el acceso a datos, el desarrollador garantiza que su aplicación es independiente en cuanto al motor de datos empleado en producción.  Soporta los SGBDR más empleados en el mercado como puede ser MYSQL, Postgre, Oracle, MSSQL, etc. Sólo es necesario cambiar una línea en el fichero de configuración para que podamos emplear una base de datos distinta.

NHiberbate facilita el mapeo de atributos entre una base de datos relacional tradicional y el modelo de objetos de una aplicación, mediante archivos declarativos (XML) que permiten establecer estas relaciones. Intenta solucionar el problema de la diferencia entre los 2 modelos usados hoy en día para organizar y manipular datos: El usado en la memoria del ordenador (orientación a objetos) y el usado en los sistemas gestores bases de datos (modelo relacional).

Para lograrlo permite al desarrollador especificar cómo es su modelo de datos, qué relaciones existen y qué forma tienen. Con esta información NHibernate le permite a la aplicación manipular los datos de la base operando sobre objetos, con todas las características de la POO.

Hibernate convertirá los datos entre los tipos utilizados por c# y los definidos por SQL. Hibernate genera las sentencias SQL y libera al desarrollador del manejo manual de los datos que resultan de la ejecución de dichas sentencias, manteniendo la portabilidad entre todas las bases de datos con un ligero incremento en el tiempo de ejecución.

NHibernate está diseñado para ser flexible en cuanto al esquema de tablas utilizado, para poder adaptarse a su uso sobre una base de datos ya existente. También tiene la funcionalidad de crear la base de datos a partir de la información disponible.

Posee también un lenguaje de consulta de datos llamado HQL (Hibernate Query Language), al mismo tiempo que una API para construir las consultas de forma programada (conocida como «criteria«).

Trabajemos un poco con él. La versión que he empleado es la NH3.3.1

Nada de hibernar y ;-), manos a la obra.

nhibernate-logotipo

Aspectos a tratar en el taller, caso práctico…

  • Instalación.
  • Definiremos una clase de negocio sencilla.
  • Crearemos un mapeo para leer y grabar el objeto de negocio.
  • Configuraremos NHhibernate para interacturar con la base de datos local.
  • Escribiremos código CRUD usando el patrón del repositorio.  En computación CRUD es el acrónimo de Crear, Obtener, Actualizar y Borrar (del original en inglés: Create, Read, Update and Delete). Es usado para referirse a las funciones básicas en bases de datos o la capa de persistencia en un software.
  • Realizaremos pruebas unitarias para asegurarnos que el código trabaja correctamente.

Instalación, primer paso.

Como he podido comprobar, NHibernate se puede usar desde Visual Studio, al crear una nueva solución. En esa solución se agrega una biblioteca de clases dándole el nombre de Nhibertante. En forma de dll o componente para ser agregado como referencia. Son un conjunto de dlls. Es necesario relacionar tablas con clases.

Grosso Modo, a través de un fichero .xml, se codifica la configuración de acceso a la base. Se indican las tablas y campos afectados. En el web.config se establecen las bases de datos con las que trabajar.Para manipular datos, se crean sesiones nhsession en las clases.

Nhibernate-esquema

Generar esta estructura es nuestro objetivo.

solutionex

La página oficial es http://nhforge.org/

Lo primero que tenemos que hacer es descomprimir el archivo zip descargado.  El fichero puedes bajártelo desde aquí. Al hacerlo se crea generalmente una carpeta c:\code\sharelibs\Nhibernate, aunque puedes darle otra ruta.

La carpeta Sharelibs es la que necesitaremos para añadir la referencia y dlls, desde Visual Studio. Así de simple. En función de la versión del IDE donde nos encontremos, no obstante funciona perfectamente con VS 2008 y Framework .NET 3.5

Existen alternativas para automatizar la descarga desde Visual Studio, sin necesidad de salir del IDE. Una de ellas es  NuGet. Nuget es una extensión para Visual studio, para agregar third-party y tools al VS.

Antes de comenzar a construir nuestra aplicación y los objetos de negocio, necesitaremos crear un proyecto en blanco. Es necesario abrir VS y crear un proyecto librería de clases o class library.  Es en este punto cuando veremos algo interesante, crear objetos de negocio.

He descubierto varios vídeos en castellano, sobre el trabajo con Visual Studio 2010, por si deseamos aprender este proceso de forma visual.

Parte 1 , Parte 2

Definiremos una clase de negocio sencilla. Los objetos de negocio.

Comencemos definiendo un simple dominio. Por el momento consiste en una entidad llamada Producto. El producto tiene 3 propiedades. Nombre, categoría y SinCatalogar (discontinued).

Es necesario crear una carpeta Domain (Dominio) dentro del proyecto solución en VS.  Dentro de esta carpeta crearemos una clases llamada product.cs (producto). El código es muy sencillo y usa propiedades automáticas (una nueva característica del compilador de C# 3.0)

namespace FirstSolution.Domain
{
    public class Producto
    {
        public string nombre { get; set; }
        public string Categoria { get; set; }
        public bool SinCatalogar { get; set; }
    }
}

Ahora queremos se capaces de almacenar de forma persistente instancias de esta entidad en una base de datos relacional. ¿Es el objetivo no?, para eso usamos NHibernate. Una instancia de una entidad en un dominio de aplicación tiene correspondencia con una fila en una tabla de la base de datos. Esto es un dato importante. Por lo tanto, tendremos que definir un mapeo entre la entidad y la correspondiente tabla en la base de datos. Este mapeo puede ser realizado de varias formas, definiendo un fichero de mapeo o mapping en forma de documento xml o decorando la entidad con atributos. Comencemos con el mapeo del fichero.

Crearemos un mapeo para leer y grabar el objeto de negocio. Definiendo el Mapping o mapeo.

Crearemos otra carpeta llamada Mappings en el proyecto. Crearemos un nuevo documento xml en esta carpeta y lo llamaremos producto.hbm.xml. Como podemos apreciar, «hbm» es parte del nombre del fichero.  Esta nomenclatura o convención es usada por NHibernate para reconocer automáticamente el fichero de tipo mapeo. Es necesario definir este fichero xml como «Recurso embebido».

En el explorador de Windows localiza el nhibenate-mapping.xsd en la carpeta del código fuente de NHibernate y cópiala en tu carpeta Sharedlibs.  Ahora podemos usar este fichero de definición de esquema xml cuando definamos nuestros ficheros de mapeo. Visual Studio te ofrecerá ayuda intelsense y validacion cuando edites el documento xml de mapeo.

Vuelve a VS y añade el esquema a el fichero producto.hbm.xml como muestra la imagen inferior.

Comencemos ahora. Cada fichero de mapeo tiene que definir un nodo raíz o root. Se puede apreciar en <hibernate-mapping>

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

  <!-- more mapping info here -->

</hibernate-mapping>

En un fichero de mapeo cuando se referencia a una clase del dominio de  aplicación, siempre tendrás que proporcinar un FQN o nombre de acceso completo a la clase. Ejemplo: Primerejemplo.Domain.Producto.

Para hacer que el fichero xml sea menos detallado o complejo puedes definir un nombre de ensamblado (en este caso las clases del dominio de aplicación están implementadas y el namespace de las clases del dominio se componen de dos atributos, ensamblado y namespace del nodo raíz. Es similar a usar una instrucción o estamento en C#.

Volviendo a la clase, Ahora primero tendremos que definir una clave primaria para la entidad producto. Técnicamente nosotros podríamos dejar el nombre como clave, pero es aconsejable subrogar la clave. Añadiremos una propiedad a la clase y la llamaremos id. Usaremos Guid como tipo de id, pero también puede definirse como int o long.

Así es como queda la clase (lo dejo a partir de ahora en inglés que es como viene en la ayuda).

using System;

namespace FirstSolution.Domain
{
    public class Product
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public string Category { get; set; }
        public bool Discontinued { get; set; }
    }
}

Podemos apreciar que hemos agregado el Guid id.

Ahora veamos como ha quedado el fichero de mapeo.

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

  <class name="Product">
    <id name="Id">
      <generator class="guid" />
    </id>
    <property name="Name" />
    <property name="Category" />
    <property name="Discontinued" />
  </class>

</hibernate-mapping>

Vemos como se han agregado las propiedades de la clase en forma de atributos xml.

Sigamos…

NHibernate no nos entorpece a intentar definir algunos valores razonables por defecto. Es decir, si no proporcionamos nombres de comumnas se definen automaticamente en base a la propiedad.

Nuestro explorador de soluciones en Visual Studio (VS) deberá ahora tener este aspecto (Domanin.cd contiene el diagrama de clases). Tendremos que haber añadido la carpeta de diseño y creado el diagrama de clases nosotros mismos, aunque esto es solo una buena costumbre y no es necesario para este caso práctico.

Vayamos ahora al siguiente punto a tratar.

Configurar NHibertante.

Ahora tenemos que decirle a HHibernate que producto gestor de datos queremos usar y proporcionar en forma de cadena de conexion (connection string). NHibernate soporta muchos gestores de bases de datos.

Añade un nuevo fichero xml a la solución(proyecto) y llamalo hibernate.cfg.xml. Establece sus propiedades a «Copy to Output» y «Copy always».  Usando SQL Server Compact Edition , es este taller introduciremos la siguiente información dentro del fichero xml.

<?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.MsSqlCeDialect</property>
    <property name="connection.driver_class">NHibernate.Driver.SqlServerCeDriver</property>
    <property name="connection.connection_string">Data Source=FirstSample.sdf</property>

    <property name="show_sql">true</property>
  </session-factory>
</hibernate-configuration>

Con este fichero de configuración de indicamos a NH que queremos usar MS SQL Compact Edition como nuestra base de datos destino. El nombre de la base de datos será FirstSample.sdf. Hemos también definido que queremos ver el SQL generado y enviado a la base por NH (es muy recomendable para propósitos de depuración durante el desarrollo).

Añadimos una base de datos vacia llamada FirstSample.sdf al proyecto. Es necesario seleccionar local database (base de datos local como plantilla o template).

Pulsamos en add o añadir e ignoramos el asistente o wizard para la creación de un Dataset, presionando cancelar.

Testeando la configuración o setup establecido

Es el momento de testear nuestra configuración. Lo primero es verifica que tenemos los siguientes ficheros  en la carpeta Sharedlibs.

Los últimos 8 ficheros pueden encontrarse en el directorio de la carpeta de programa  «Microsoft SQL Server Compact Edition»

Observación: El System.Data.SqlServerCe.dll se encuentra localizado en la subcarpeta Desktop.

El resto de fichero pueden encontrarse en la carpeta NHibernate.

Añade una referencia al ejemplo de tu proyecto. Adicionalmente añade referencias a NHhibernate.dll, nunit.framework y Systm.Data.SqlServerCe.dll (recuerda referenciar los ficheros situados en la carpeta SharedLibs).

Presta atención estableciendo la propiedad «Copia Local» a true para el ensamblado System.Data.SqlServerCE.dll porque por defecto es false. Añade una copia de hibernate.cfg.xml al raíz de este proyecto.

Añade una clase llamada GenerateSchema_Fixture al proyecto. El proyecto debería tener un aspecto como la imagen inferior.

Necesitaremos los 7 ficheros sqce*.dll en el directorio de salida. Podemos hacer esto usando un evento post-build en VS. Introduce el siguiente comando en el apartado Build Events->Post-build comand line: tal y como muestra la imagen inferior.

copy $(ProjectDir)..\..\SharedLibs\sqlce*.dll $(ProjectDir)$(OutDir)

Ahora añade el siguiente código al fichero GenerateSchema_Fixture

using FirstSolution.Domain;
using NHibernate.Cfg;
using NHibernate.Tool.hbm2ddl;
using NUnit.Framework;
namespace FirstSolution.Tests
{
    [TestFixture]
    public class GenerateSchema_Fixture
    {
        [Test]
        public void Can_generate_schema()
        {
            var cfg = new Configuration();
            cfg.Configure();
            cfg.AddAssembly(typeof (Product).Assembly);

            new SchemaExport(cfg).Execute(false, true, false, false);
        }
    }
}

La  primera línea del método test crea una nueva instancia de la clase configuración NHibernate. Esta clase es usada para configurar NHibernate. En la segunda línea le decimos a NH que se configure. NH revisará la información de configuración hasta que nosotros no proporcionemos algún valor en el método test. Además NH buscará un fichero llamado hibernate.cfg.xml en el directorio de salida. Esto es exactamente  lo que queremos definiendo nuestras opciones en este fichero.

En la tercera línea de código le diremosa NH que puede encontrar información de mapeo en el ensamblado, el cual contiene tambien la clase producto.  Al tiempo que sólo encontrará un fichero(product.hbm.xml) como un recurso embebido.

La cuarta línea de código usa la clase SchemaExport de NH para generar automáticamente y de forma mágica el schema en la base de datos por nosotros. SchemaExport creará la tabla de producto en la base y cada vez que la llamamos borrará la tabla, los datos y la recreará.

Nota: Con este método test no queremos averiguar si NH hace el trabajo correctamente(puedes estar seguro que sí) pero si evaluar que hemos establecido nuestro sistema correctamente.. Sin embargo, puedes comprobar la base de datos y ver que la nueva tabla de productos se creó.

Si tenemos Testdriven.net instalado podemos hacer clic con el botón derecho dentro del método test y elegir «Run Test(s)» para ejecutar la prueba.

Si todo es correcto, deberemos ver el siguiente resultado en la ventana de salida.

Si tenemos instalado ReSharper  podemos empezar los test haciendo click en el circulo verde-amarillo en la parte izquierda y elegir run o ejecutar.

El resultado es como el siguiente.

En caso de problemas.

Si las pruebas fallan, comprueba por segunda vez si tienes los siguientes ficheros en el directorio destino.

Nuestras primeras operaciones CRUD

Continuamos con el taller práctico. En la parte superior de esta entrada expongo que es un CRUD.

Ahora obviamente  nuestro sistema esta listo para comenzar. Hemos implementado satisfactoriamente nuestro dominio, definido los ficheros de mapeo y configurado NHibernate. Finalmente hemos usado NH para generar automáticamente el schema de la base de datos desde nuestro dominio.

Un nuevo concepto DDD (Domain Driven Design), o diseño guiado por dominio es un enfoque para el desarrollo de software con necesidades complejas. No es una tecnología ni metodología. Las premisas del diseño guiado por el dominio son las siguientes:

– Poner como principal objetivo el proyecto y la lógica del dominio.
– Basar los diseños complejos en un modelo.
– Iniciar una creativa colaboración entre técnicos y expertos del dominio para interactuar lo más cercano posible a los conceptos del problema.
Es pues un conjunto de practicas y terminologías para tomar decisiones de diseño que enfoquen y aceleren el manejo de dominios complejos en proyectos de software.

Fue acuñado por Eric Evans en su libro Domain-Driven Design.  Menciono el DDD, porque hemos definido un repositorio para todas las operaciones CRUD (crear, leer, actualizar y borrar), intentando satisfacer el espíritu  de DDD. Establecer las premisas futuras aunque la implementación de la aplicación no lo despliegue.

Añadimos una nueva interfaz para a la carpeta del dominio en nuestro proyecto. La llamaremos IProductRepository. Con el siguiente interfaz.

using System;
using System.Collections.Generic;

namespace FirstSolution.Domain
{
    public interface IProductRepository
    {
        void Add(Product product);
        void Update(Product product);
        void Remove(Product product);
        Product GetById(Guid productId);
        Product GetByName(string name);
        ICollection<Product> GetByCategory(string category);
    }
}

Se añade una clase ProductRepository_Fixture para testear el proyecto y se añade el siguiente código.

[TestFixture]
    public class ProductRepository_Fixture
    {
        private ISessionFactory _sessionFactory;
        private Configuration _configuration;

        [TestFixtureSetUp]
        public void TestFixtureSetUp()
        {
            _configuration = new Configuration();
            _configuration.Configure();
            _configuration.AddAssembly(typeof (Product).Assembly);
            _sessionFactory = _configuration.BuildSessionFactory();
        }
    }

En la cuarta línea del método TestFixtureSetUp creamos una sesion. Esto es un proceso costoso que debería ser ejecutado solo una vez. Esta es la razón del por qué se pone dentro de este método el cual es solo ejecutado una sola vez durante el ciclo de test.

Para conservar nuestro métodos de test sin efectos recreamos nuestro schema antes de la ejecución de cada test. Así pues, añadimos el siguiente método

[SetUp]
        public void SetupContext()
        {
            new SchemaExport(_configuration).Execute(false, true, false, false);
        }

Y ahora podemos implementar el método test y añadir un nueva instancia de producto a la base de datos. Comenzar añadiendo una nueva carpeta llamada Repositories a nuestro proyecto. Añadir una clase ProductRepository a esta carpeta. Hacer que ProductRepository herede de la interfaz IProductRepository.

using System;
using System.Collections.Generic;
using FirstSolution.Domain;

namespace FirstSolution.Repositories
{
    public class ProductRepository : IProductRepository
    {
        public void Add(Product product)
        {
            throw new NotImplementedException();
        }

        public void Update(Product product)
        {
            throw new NotImplementedException();
        }

        public void Remove(Product product)
        {
            throw new NotImplementedException();
        }

        public Product GetById(Guid productId)
        {
            throw new NotImplementedException();
        }

        public Product GetByName(string name)
        {
            throw new NotImplementedException();
        }

        public ICollection<Product> GetByCategory(string category)
        {
            throw new NotImplementedException();
        }
    }
}

Manipulando datos.

Ahora volcemos a la clase test  ProductRepository_Fixture e implementamos el primer método test.

 [Test]
        public void Can_add_new_product()
        {
            var product = new Product {Name = "Apple", Category = "Fruits"};
            IProductRepository repository = new ProductRepository();
            repository.Add(product);
        }

La primera ejecución de este método test fallará hasta que no implementemos el método Add en la clase del repositorio. Vamos a realizarlo.Pero tendremos que esperar, hemos definido un pequeña clase de ayuda primero la cual nos proporciona objetos de session a demanda.

using FirstSolution.Domain;
using NHibernate;
using NHibernate.Cfg;

namespace FirstSolution.Repositories
{
    public class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;

        private static ISessionFactory SessionFactory
        {
            get
            {
                if(_sessionFactory == null)
                {
                    var configuration = new Configuration();
                    configuration.Configure();
                    configuration.AddAssembly(typeof(Product).Assembly);
                    _sessionFactory = configuration.BuildSessionFactory();
                }
                return _sessionFactory;
            }
        }

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

Esta clase crea una fábrica de sesiones solo la primera vez que un cliente necesite una sesión  Ahora hemos definido el método añadir en el ProductRepository de la siguiente forma:

 public void Add(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Save(product);
                    transaction.Commit();
                }
        }

La segunda ejecución del método test fallará de nuevo desplegando el siguiente mensaje.

Esto es debido a que NHibernate se encuentra configurado por defecto para usar una carga perezosa (Lazy load) para todas las entidades.  Este es el enfoque recomendado para una máxima flexibilidad.

¿Cómo podemos resolver este aspecto? Es fácil, tenemos que  hacer todas las propiedades y métodos virtuales de los objetos del dominio. De tal forma que la clase producto quedaría:

    public class Product
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }
        public virtual string Category { get; set; }
        public virtual bool Discontinued { get; set; }
    }

Ahora ejecutemos el test de nuevo. Debería ser correcto y mostrar la siguiente salida:

Observa el SQL arrojado por NHibernate.

Ahora pensamos que hemos insertado correctamente el nuevo producto en la base de datos. Vamos a probar si realmente es así.

Vamos a extender nuestro método test.

      [Test]
        public void Can_add_new_product()
        {
            var product = new Product {Name = "Apple", Category = "Fruits"};
            IProductRepository repository = new ProductRepository();
            repository.Add(product);

            // use session to try to load the product
            using(ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                // Test that the product was successfully inserted
                Assert.IsNotNull(fromDb);
                Assert.AreNotSame(product, fromDb);
                Assert.AreEqual(product.Name, fromDb.Name);
                Assert.AreEqual(product.Category, fromDb.Category);
            }
        }

Ejecutemos el test de nuevo, esperemos que tenga éxito …

Ahora nosotros hemos implementado también otros métodos en el repositorio. Para probar esto, preferiría tener un repositorio (que es la tabla de base de datos) que ya contiene algunos productos. Nada más fácil que esto. Sólo tienemos que añadir un CreateInitialData método a la clase de prueba de la siguiente manera:

private readonly Product[] _products = new[]
                 {
                     new Product {Name = "Melon", Category = "Fruits"},
                     new Product {Name = "Pear", Category = "Fruits"},
                     new Product {Name = "Milk", Category = "Beverages"},
                     new Product {Name = "Coca Cola", Category = "Beverages"},
                     new Product {Name = "Pepsi Cola", Category = "Beverages"},
                 };

        private void CreateInitialData()
        {

            using(ISession session = _sessionFactory.OpenSession())
                using(ITransaction transaction = session.BeginTransaction())
                {
                    foreach (var product in _products)
                        session.Save(product);
                    transaction.Commit();
                }
        }

Llamar a este método desde el método SetupContext (despues de crear la llamada al esquema) y listo. Ahora, cada vez que el esquema de base de datos se crea,  la base de datos se rellena con algunos productos.

Vamos a probar el método de actualización del repositorio con el siguiente código:

    [Test]
        public void Can_update_existing_product()
        {
            var product = _products[0];
            product.Name = "Yellow Pear";
            IProductRepository repository = new ProductRepository();
            repository.Update(product);

            // use session to try to load the product
            using (ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                Assert.AreEqual(product.Name, fromDb.Name);
            }
        }

Cuando ejecutamos por primera vez este código, fallará hasta que el método Update no este implementado in el repositorio.

Nota: Este es el comportamiento esperado ya que en TDD o desarrollo guiado en pruebas la primera vez que se ejecuta debe fallar.

De forma análoga al método Add implementamos el método Update del repositorio. La única diferencia es que nosotros llamamos al método de actualización del objeto de session de NHibernate en lugar  del método de guardado.

        public void Update(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            using (ITransaction transaction = session.BeginTransaction())
            {
                session.Update(product);
                transaction.Commit();
            }
        }

Ejecutamos el test de nuevo y vemos que tiene éxito.

 

El método de borrado es directo. Cuando se prueba si el registro ha sido realmente eliminado, nos aseguramos que el valor devuelto de la sesión por el método get es igual a null. Aquí se encuentra el método de test:

 [Test]
        public void Can_remove_existing_product()
        {
            var product = _products[0];
            IProductRepository repository = new ProductRepository();
            repository.Remove(product);

            using (ISession session = _sessionFactory.OpenSession())
            {
                var fromDb = session.Get<Product>(product.Id);
                Assert.IsNull(fromDb);
            }
        }

y esto es la implementación para eliminarlo del repositorio

     public void Remove(Product product)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                using (ITransaction transaction = session.BeginTransaction())
                {
                    session.Delete(product);
                    transaction.Commit();
                }
        }

Consultando la base de datos

Todavía nos queda implementar 3 métodos que consultan a la base  de datos para los objetos. Vamos a empezar con lo más fácil, GetByID, primero escribiremos la prueba.

        [Test]
        public void Can_get_existing_product_by_id()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetById(_products[1].Id);
            Assert.IsNotNull(fromDb);
            Assert.AreNotSame(_products[1], fromDb);
            Assert.AreEqual(_products[1].Name, fromDb.Name);
        }

Y el código para completar el test

       public Product GetById(Guid productId)
        {
            using (ISession session = NHibernateHelper.OpenSession())
                return session.Get<Product>(productId);
        }

Ahora, eso fue fácil. Para los siguientes dos métodos usaremos un nuevo método de el objeto session. Vamos a empezar con el método GetByName. Como de costumbre, escribiremos la primera prueba, TDD

       [Test]
        public void Can_get_existing_product_by_name()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetByName(_products[1].Name);

            Assert.IsNotNull(fromDb);
            Assert.AreNotSame(_products[1], fromDb);
            Assert.AreEqual(_products[1].Id, fromDb.Id);
        }

La implementación del método GetByName se puede hacer mediante el uso de dos enfoques diferentes. El primero es usar HQL (Hibernate Query Language) y el segundo un HCQ (Hibernate Query Criteria). Vamos a empezar con HQL. HQL es un lenguaje orientado a objetos de consulta similar (pero no igual a) SQL.

Para ser añadido: Implementación de GetByName utilizando HQL. Implementar HCQ como se muestraa funciona como se espera y devuelve un producto entidad.

En el ejemplo anterior he introducido una técnica muy utilizada cuando se utiliza NHibernate. Se llama interfaces fluidas o fluent interfaces. Como resultado, el código es menos detallado y más fácil de entender. Se puede ver que una consulta HQL es una cadena que se han incorporado (con nombre) parámetros. Los parámetros van precedidos por un ‘:’. NHibernate define muchos métodos helper (como SetString utilizado en el ejemplo) para asignar valores de varios tipos para esos parámetros. Finalmente mediante el uso de NHibernate UniqueResult le digo que espero que sólo un registro para volver. Si hay más de un registro devuelto por la consulta HQL entonces se produce una excepción. Existe más información sobre HQL en línea e internet.

La segunda versión utiliza una consulta de criterios para buscar el producto solicitado. Es necesario agregar una referencia a NHibernate.Criterion en su página repositorio.

     public Product GetByName(string name)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                Product product = session
                    .CreateCriteria(typeof(Product))
                    .Add(Restrictions.Eq("Name", name))
                    .UniqueResult<Product>();
                return product;
            }
        }

Muchos usuarios de NHibernate piensan que este enfoque es más orientado a objetos. Por otro lado una consulta compleja escrita con la sintaxis de criterios rápidamente puede ser difícil de entender.

El último método a aplicar es GetByCategory. Este método devuelve una lista de productos. La prueba puede ser implementada de la siguiente forma:

  [Test]
        public void Can_get_existing_products_by_category()
        {
            IProductRepository repository = new ProductRepository();
            var fromDb = repository.GetByCategory("Fruits");

            Assert.AreEqual(2, fromDb.Count);
            Assert.IsTrue(IsInCollection(_products[0], fromDb));
            Assert.IsTrue(IsInCollection(_products[1], fromDb));
        }

        private bool IsInCollection(Product product, ICollection<Product> fromDb)
        {
            foreach (var item in fromDb)
                if (product.Id == item.Id)
                    return true;
            return false;
        }

Y el método debiera contener el siguiente código:

       public ICollection<Product> GetByCategory(string category)
        {
            using (ISession session = NHibernateHelper.OpenSession())
            {
                var products = session
                    .CreateCriteria(typeof(Product))
                    .Add(Restrictions.Eq("Category", category))
                    .List<Product>();
                return products;
            }
        }

 

Resumen 

En  esta entrada se ha visto como implementar un ejemplo básico. Se ha definido el mapeo hacia la base de datos y cómo configurar NHibernate para que sea capaz de almacenar el dominio de  objetos persistentes en la base de datos. Se ha mostrado como escribir y testear métodos CRUD para los objetos del dominio. Hemos seleccionado una base de datos de ejemplo MS SQL Edicion Compact, pero como he comentado puede dar soporte a otras bases, para ello es necesario cambiar el fichero hibernate.cfg.xml

En fín, espero que esto os sirva para introduciros en el mundo ORM y NHibernate. A mi me sirve de aprendizaje, conocimiento y repaso. Es realmente, una extensión personalizada en Castellano de la ayuda oficial inicial, considero que existe poca documentación en este idioma sobre el tema.

Salu2, ya podéis Hibernar …

Otro video: http://www.youtube.com/watch?v=tJ49yXWXwow

Esta documentación corresponde con la oficial, he traducido y ampliado términos al castellano-Español  de:
Mi primera aplicación HNhibertate , web oficial
http://nhforge.org/wikis/howtonh/your-first-nhibernate-based-application.aspx

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.

Home Bases de datos NHibernate, mi primera aplicación ORM, caso práctico.
© www.palentino.es, desde el 2012 - Un Blog para compartir conocimientos ...

Uso de cookies en mi sitio palentino.es

Este sitio web utiliza cookies para que tengamos la mejor experiencia de usuario. Si continúas navegando estás dando tu consentimiento para la aceptación de las mencionadas cookies y la aceptación de la política de cookies

ACEPTAR
Aviso de cookies