08/01/08

NHibernate: Aplicando Patrones de diseño a una aplicación que utiliza NHibernate – Parte 1

Hola! en este post aplicaremos patrones de diseño para nuestra aplicación, en concreto dos patrones , Abstract Factory y Data Access Object (DAO).

Primero un poco de teoría , a una pequeña definición de:

Patrones de Diseño:

Segun Wikipedia:

“Los patrones de diseño (design patterns) son la base para la búsqueda de soluciones a problemas comunes en el desarrollo de software y otros ámbitos referentes al diseño de interacción o interfaces.”

Entonces como recordarán en el post anterior hicimos un helper, denominado NHelper que usaba el concepto de Generics y Templates, en este post y con su respectivo ejemplo extenderemos un poco más esto haciendo uso de estos patrones:

Abstract Factory, un ejemplo más concreto lo hemos visto usando el Bloque de Acceso de datos de Enterprise Library atravez de DbFactory.

Según Wikipedia:

(Fábrica Abstracta) es un patrón de diseño para el desarrollo de software.

El problema que intenta solucionar este patrón es el de crear diferentes familias de objetos.

Data Access Object (DAO), es un patrón de uso de común en nuestras aplicaciones , generalmente lo identificamos de manera equivocada con nuestra Capa de Datos, A cuyos objetos de “acceso a datos”, le implementados los metodos caracteristicos de mantenimento o tambíen denominados CRUD.

Según Wikipedia:

En software de computadores, un Data Access Object (DAO, Objeto de Acceso a Datos) es un componente de software que suministra una interfaz común entre la aplicación y uno o más dispositivos de almacenamiento de datos, tales como una Base de datos o un archivo.

Diseñando nuestra Arquitectura:

image

En la denominada Capa 3 (Capa de Datos) implementaremos el patrón DAO y Abstract Factory.

En la denominada Capa 2 ( Capa de Logica de Negocios) implementaremos solo el patrón Abstrac Factory.

Problema:

Sucede que erróneamente se suele implementar un Objeto DAO para entidad y luego implementar un objeto de negocio que hace una llamada directa al objeto DAO, siguiendo esta logica , habrian tantas llamadas desde la capa de negocios hasta la capa de datos como entidades en nuestro modelo, lo cual debe tratar de evitarse. pues sino la aplicación quedaria asi:

image

Una solución a este problema , es aplicando patrones de diseño, para nuestro caso Abstract factory y DAO , implementandolo en ambas capas podemos redudir el numero de nuestras llamadas,

Como lo hacemos?

GenericDAO , GenericBussiness , implementando estas fabricas abstractas a y haciendo uso de ellas en las llamadas de nuestros objetos de negocios hacia nuestros objetos de datos, entonces cambiaria de esta manera:

image

Nuestro Diseño de Soluciones y proyectos:

image

Extendiento nuesto NHelper: De NHelper a NHibernateHelper

Implementando: GenericNHibernateDAO , IGenericDAO, NHibbernateHelper.

Instanciemos graficamente para poder entenderlo rápido:

image

Primero definimos nuestra interface IGenericDAO:

Esta interfaz tendra cada uno de los metodos que utilizarán nuestras entidades, tales como:

  • FindAll(): Devuelve todos los elementos.
  • FindAllOrdered() : Devuelve todos elementos ,bajo un criterio de orden
  • FindByExample() : Devuelve todos los elementos que coincidan con el objeto de ejemplo enviado como parametro
  • FindbyId(): devuelve un ojeto a partir de su identificador , sea este un primitivo(string, int, etc.) o un objeto , para el caso de nuestras claves compuestas.
  • CRUD: Save(), Update(),MakeTransient() (Delete), etc.
01.using System.Collections;
02.using System.Collections.Generic;
03.namespace NHDataAccess
04.{
05.   public interface IGenericDao<T,ID>
06.    {
07.        T FindByid(Id id);
08.  
09.        IList<T> FindAll();
10.  
11.        IList<T> FindAllOrdered(IList<STRING> propertyNames);
12.  
13.        IList<T> FindByExample(T entity);
14.  
15.        IList<T> FindBydExampleOrdered(T entity, IList<STRING> propertyNames);
16.  
17.        IList<T> FindLikeExample(T entity);
18.  
19.        IList<T> FindLikeExampleOrdered(T entity, IList<STRING> propertyNames);
20.  
21.        IList<T> FindLikeExampleIgnoreCase(T entity);
22.  
23.        IList<T> FindLikeExampleIgnoreCaseOrdered(T entity, IList<STRING> propertyNames);
24.  
25.        IList<T> FindByQuery(string query);
26.  
27.        IList<T> FindByQuery(string query, string[] parameters, object[] values);
28.  
29.        IList<T> FindByNamedQuery(string query);
30.  
31.        IList<T> FindByNamedQuery(string query, string[] parameters, object[] values);
32.  
33.        IList FindObjectsByQuery(string query);
34.  
35.        IList FindObjectsByNamedQuery(string query);
36.  
37.        IList FindObjectsByNamedQuery(string query, string[] parameteres, object[] values);
38.  
39.        IList FindObjectsBySQLQuery(string query, string[] parameters, object[] values);
40.  
41.        T Save(T entity);
42.  
43.        T Update(T entity);
44.  
45.        void MakeTransient(T entity);
46.  
47.        Id GetMax(string propertyName);
48.  
49.    }
50.}  

Porque en unos casos usar IList<T> y en otros IList:

Para el primer caso cuando utilizamos IList<T> hacemos una consulta sobre una entidad en particular, los metodos que utilizen IList<T> solo devolverán un tipo en particular.

Para el segundo caso , se da cuando deseamos recuperar “Propiedades cruzadas” que quiere decir esto , imaginemos un join entre entidades , osea por ejm:

Class A –> propiedad A1 , propiedad A2

Clase B –> propiedad B1, propiedad B2

y quisieramos recuperar una lista que contenga la Propiedad A1 y propiedad B2, en este caso Nhibernate me devolvera una lista de arrays, en la cual yo podré iterar y recuperar lo que necesito, más adelante implementaremos un ejemplo de este caso.

GenericNHibernateDao:

Esta clase hereda la interfaz IGenericDAO e implementa sus métodos:

La implementación de estos metodos lo hacemos atreves del uso de criteria, si recuerdan en el post anterior para nuestra primera versión de NHelper , usabamos HQL a modo de emular “SQL”, en este Nuevo NHelper, GenericNHibernateDAO , NH genera las consultas SQL a partir de criteria.

(*) Para la presentación configuraremos Log4net para visualizar las consultas SQL generadas por NHibernate.

001.using System;
002.using System.Collections;
003.using System.Collections.Generic;
004.using System.Linq;
005.using System.Text;
006.using NHDataAccess;
007.using NHibernate;
008.using NHibernate.Criterion;
009.using NHibernate.Type;
010.namespace NHDataAccess
011.{
012.    public abstract class GenericNhibernateDao<T,ID> : IGenericDao<T,ID>
013.    {
014.        public T FindByid(Id id)
015.        {
016.            return NhibernateHelper.GetCurrentSession().Get<T>(id);
017.        }
018.  
019.        public IList<T> FindAll()
020.        {
021.            return NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).List<T>();
022.        }
023.  
024.        public IList<T> FindAllOrdered(IList<STRING> propertyNames)
025.        {
026.            var criteria = NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T));
027.            foreach (var propertyName in propertyNames)
028.            {
029.                criteria.AddOrder(Order.Asc(propertyName));
030.            }
031.            return criteria.List<T>();
032.        }
033.  
034.        public IList<T> FindByExample(T entity)
035.        {
036.            return NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(Example.Create(entity)).List<T>();
037.        }
038.  
039.        public IList<T> FindBydExampleOrdered(T entity, IList<STRING> propertyNames)
040.        {
041.            var criteria = NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(Example.Create(entity));
042.            foreach (var propertyName in propertyNames)
043.            {
044.                criteria.AddOrder(Order.Asc(propertyName));
045.            }
046.            return criteria.List<T>();
047.        }
048.  
049.        public IList<T> FindLikeExample(T entity)
050.        {
051.            return NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(Example.Create(entity).EnableLike()).List<T>();
052.        }
053.  
054.        public IList<T> FindLikeExampleOrdered(T entity, IList<STRING> propertyNames)
055.        {
056.            var criteria = NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(Example.Create(entity));
057.            foreach (var propertyName in propertyNames)
058.            {
059.                criteria.AddOrder(Order.Asc(propertyName));
060.            }
061.            return criteria.List<T>();
062.        }
063.  
064.        public IList<T> FindLikeExampleIgnoreCase(T entity)
065.        {
066.            return
067.                NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(
068.                    Example.Create(entity).EnableLike().IgnoreCase()).List<T>();
069.        }
070.  
071.        public IList<T> FindLikeExampleIgnoreCaseOrdered(T entity, IList<STRING> propertyNames)
072.        {
073.            var criteria =
074.                NhibernateHelper.GetCurrentSession().CreateCriteria(typeof(T)).Add(
075.                    Example.Create(entity).EnableLike().IgnoreCase());
076.            foreach (var propertyName in propertyNames)
077.            {
078.                criteria.AddOrder(Order.Asc(propertyName));
079.            }
080.            return criteria.List<T>();
081.        }
082.  
083.        public IList<T> FindByQuery(string query)
084.        {
085.            return NhibernateHelper.GetCurrentSession().CreateQuery(query).List<T>();
086.        }
087.  
088.        public IList<T> FindByQuery(string myquery, string[] parameters, object[] values)
089.        {
090.            var query = NhibernateHelper.GetCurrentSession().CreateQuery(myquery);
091.            for (var i = 0; i < parameters.Length-1; i++)
092.            {
093.                query.SetParameter(parameters[i], values[i]);
094.            }
095.            return query.List<T>();
096.        }
097.  
098.        public IList<T> FindByNamedQuery(string query)
099.        {
100.            return NhibernateHelper.GetCurrentSession().GetNamedQuery(query).List<T>();
101.        }
102.  
103.        public IList<T> FindByNamedQuery(string myquery, string[] parameters, object[] values)
104.        {
105.            var query = NhibernateHelper.GetCurrentSession().GetNamedQuery(myquery);
106.            for (var i = 0; i < parameters.Length-1; i++)
107.            {
108.                query.SetParameter(parameters[i], values[i]);
109.            }
110.            return query.List<T>();
111.        }
112.  
113.        public System.Collections.IList FindObjectsByQuery(string query)
114.        {
115.            return NhibernateHelper.GetCurrentSession().CreateQuery(query).List();
116.        }
117.  
118.        public System.Collections.IList FindObjectsByNamedQuery(string query)
119.        {
120.            return NhibernateHelper.GetCurrentSession().GetNamedQuery(query).List();
121.        }
122.  
123.        public System.Collections.IList FindObjectsByNamedQuery(string myquery, string[] parameteres, object[] values)
124.        {
125.            var query = NhibernateHelper.GetCurrentSession().GetNamedQuery(myquery);
126.            for (var i = 0; i < parameteres.Length-1; i++)
127.            {
128.                query.SetParameter(parameteres[i], values[i]);
129.            }
130.            return query.List();
131.        }
132.  
133.        public IList FindObjectsBySQLQuery(string query)
134.        {
135.            return NhibernateHelper.GetCurrentSession().CreateSQLQuery(query).List();
136.        }
137.  
138.        public IList FindObjectsBySQLQuery(string myquery, string[] parameters, object[] values)
139.        {
140.            var query = NhibernateHelper.GetCurrentSession().CreateSQLQuery(myquery);
141.            for (int i = 0; i < parameters.Length-1; i++)
142.            {
143.                query.SetParameter(parameters[i], values[i]);
144.            }
145.            return query.List();
146.        }
147.  
148.        public T Save(T entity)
149.        {
150.            NhibernateHelper.GetCurrentSession().Save(entity);
151.            return entity;
152.        }
153.  
154.        public T Update(T entity)
155.        {
156.            NhibernateHelper.GetCurrentSession().Update(entity);
157.            return entity;
158.        }
159.  
160.        public void MakeTransient(T entity)
161.        {
162.            NhibernateHelper.GetCurrentSession().Delete(entity);
163.        }
164.  
165.        public Id GetMax(string propertyName)
166.        {
167.            return NhibernateHelper.GetCurrentSession().CreateCriteria(GetType()).SetProjection(Projections.ProjectionList().Add(Projections.Max(propertyName))).UniqueResult<ID>();
168.        }
169.        protected void DeleteObjectByQuery(string query, object[] values, IType[] types)
170.        {
171.            NhibernateHelper.GetCurrentSession().Delete(query, values, types);
172.        }
173.    }
174.}

NhibernateHelper:

Esta clase administra las sessiones de Nhibernate, con esto quiero decir que crea , borra , inicializa transacciones , cierra , commitea y hacer rollback, etc.

Los métodos de esta Clase son estaticos, una vez inicializada la session de NH , la guardan en el session Current del objeto Session de ASP.NET, para poder así mantener la session activa.

01.using System;
02.using System.IO;
03.using NHibernate;
04.using Configuration=NHibernate.Cfg.Configuration;
05.  
06.namespace NHDataAccess
07.{
08.    public class NhibernateHelper
09.    {
10.        private const string CurrentSessionKey = "nhibernate.current_session";
11.        public static ISessionFactory SessionFactory;
12.        static NhibernateHelper()
13.        {
14.            try
15.            {
16.                log4net.Config.XmlConfigurator.Configure();
17.                var cfg = new Configuration();
18.                var configurationSection = NhibernateConfigurationSection.CurrentConfiguration;
19.                cfg.Configure(configurationSection.HibernateFile);
20.                var path = new DirectoryInfo(configurationSection.MappingPath);
21.                cfg.AddDirectory(path);
22.                SessionFactory = cfg.BuildSessionFactory();
23.            }
24.            catch (Exception ex)
25.            {
26.                throw new Exception("Falló Inicialización de Nhibernate",ex);
27.            }
28.        }
29.  
30.        public static ISession OpenSession()
31.        {
32.            return SessionFactory.OpenSession();
33.        }
34.  
35.        public static ISession GetCurrentSession()
36.        {
37.            var context = System.Web.HttpContext.Current;
38.            var currentSession = (ISession) (context.Items[CurrentSessionKey]);
39.            if(currentSession == null)
40.            {
41.                currentSession = SessionFactory.OpenSession();
42.                context.Items[CurrentSessionKey] = currentSession;
43.            }
44.            return currentSession;
45.        }
46.  
47.        public static void CloseSession()
48.        {
49.            var context = System.Web.HttpContext.Current;
50.            var currentSession = (ISession) (context.Items[CurrentSessionKey]);
51.            if(currentSession == null)
52.            {
53.                return;
54.            }
55.            currentSession.Close();
56.            context.Items.Remove(CurrentSessionKey);
57.        }
58.        public static void CloseSessionFactory()
59.        {
60.            if(!(SessionFactory == null))
61.            {
62.                SessionFactory.Close();
63.            }
64.        }
65.  
66.        public static void BeginTransaction()
67.        {
68.            if(GetCurrentSession().Transaction.IsActive)
69.            {
70.                GetCurrentSession().BeginTransaction(); 
71.            }
72.        }
73.        public static void EndTransaction()
74.        {
75.            GetCurrentSession().Transaction.Commit();
76.        }
77.        public static void RollBackTransaction()
78.        {
79.            GetCurrentSession().Transaction.Rollback();
80.        }
81.    }
82.}

Nuestras Entidades:

Igual que en nuestro ejemplo anterior utilizaremos solo 3 entidades , Usuario, Rol , y la entidad intermedia , UsuarioRol

image

La diferencia es en el uso de claves compuestas para el caso de “UsersRoles” esto con el efecto de utilizarla en la demostración

01.using System;
02.using System.Collections;
03.namespace NHEntity
04.{
05.    [Serializable()]
06.    public class RoleEntity : IEquatable<RoleEntity>
07.    {
08.        public virtual int IdRole{ get; set;}
09.  
10.        public virtual  string RoleName{ get; set;}
11.  
12.        public virtual IList UserRoleList { get; set; }
13.  
14.        #region Miembros de IEquatable<ROLE>
15.  
16.        public virtual bool Equals(RoleEntity other)
17.        {
18.            return other.IdRole.Equals(IdRole );
19.        }
20.        #endregion
21.    }
22.}
01.using System;
02.namespace NHEntity
03.{
04.    [Serializable]
05.    public class UserRoleEntity : IEquatable<UserRoleEntity>
06.    {
07.        private PKUserRole id = new PKUserRole();
08.        public virtual PKUserRole IdUserRole
09.        {
10.            set
11.            {
12.                id = value;
13.            }
14.            get
15.            {
16.                return id;
17.            }
18.        }
19.  
20.        public virtual bool Equals(UserRoleEntity other)
21.        {
22.            return other.IdUserRole.Equals(this.IdUserRole);
23.        }
24.    }
25.}
01.using System;
02.using System.Collections;
03.using System.Collections.Generic;
04.namespace NHEntity
05.{
06.    [Serializable]
07.    public class UserEntity : IEquatable<UserEntity>
08.    {
09.        private IList userRoleList;
10.        public UserEntity()
11.        {
12.            userRoleList = new List<UserRoleEntity>();
13.        }
14.  
15.        public virtual int IdUser
16.        {
17.            set;
18.            get;
19.        }
20.  
21.        public virtual string Login
22.        {
23.            set;
24.            get;
25.        }
26.  
27.        public virtual string Password
28.        {
29.            set; get;
30.        }
31.  
32.        public virtual IList UserRoleList
33.        {
34.            get
35.            {
36.                return userRoleList;
37.            }
38.            set
39.            {
40.                userRoleList = value;
41.            }
42.        }
43.  
44.        public virtual bool Equals(UserEntity other)
45.        {
46.            return other.IdUser.Equals(IdUser);
47.        }
48.    }
49.}

Objetos PK

Para el uso de las claves compuestas, se necesita implementar una clase que denominaremos Objetos PK

01.using System;
02.using System.Collections;
03.  
04.namespace NHEntity.PK
05.{
06.    [Serializable]
07.    public class PKUserRole : IEquatable<PKUSERROLE>
08.    {
09.        private UserEntity idUser = new UserEntity();
10.        private RoleEntity idRole = new RoleEntity();
11.        readonly CaseInsensitiveComparer objComparer  = new CaseInsensitiveComparer();
12.        public virtual UserEntity IdUser
13.        {
14.            set
15.            {
16.                idUser = value;
17.            }
18.            get
19.            {
20.                return idUser;
21.            }
22.        }
23.        public virtual RoleEntity IdRole
24.        {
25.            set
26.            {
27.                idRole = value;   
28.            }
29.            get
30.            {
31.                return idRole;
32.            }
33.        }
34.        public bool Equals(PKUserRole obj)
35.        {
36.            return objComparer.Compare(this, obj) == 0;
37.        }
38.        public override bool Equals(object obj)
39.        {
40.            if(obj.Equals(this))
41.            {
42.                return true;   
43.            }
44.            return false;
45.        }
46.        public override int GetHashCode()
47.        {
48.            return base.ToString().ToLowerInvariant().GetHashCode() + 1;
49.       
50.    }
51.}

Objetos DAO

image

  • UserDao
  • RoleDao
  • UserRoleDao
UserDao:
01.using NHDao.Interfaces;
02.using NHDataAccess;
03.using NHEntity;
04.  
05.namespace NHDao.Implementations
06.{
07.    public class UserDao : GenericNhibernateDao<UserEntity, int>, IUserDao
08.    {
09.    }
10.}
RoleDao:
01.using NHDao.Interfaces;
02.using NHDataAccess;
03.using NHEntity;
04.  
05.namespace NHDao.Implementations
06.{
07.    public class RoleDao : GenericNhibernateDao<RoleEntity,INT>,IRoleDao
08.    {
09.    }
10.}
UserRoleDao:
01.using NHDao.Interfaces;
02.using NHDataAccess;
03.using NHEntity;
04.using NHEntity.PK;
05.  
06.namespace NHDao.Implementations
07.{
08.    public class UserRoleDao : GenericNhibernateDao<UserRoleEntity, PKUserRole>, IUserRoleDao
09.    {
10.    }
11.}
Interfaces IUserDao:
1.using NHDataAccess;
2.using NHEntity;
3.namespace NHDao.Interfaces
4.{
5.    public interface IUserDao : IGenericDao<UserEntity, int>
6.    {
7.    }
8.}
IRoleDao:
1.using NHEntity;
2.using NHDataAccess;
3.namespace NHDao.Interfaces
4.{
5.    public interface IRoleDao : IGenericDao<RoleEntity,INT>
6.    {
7.    }
8.}
UserRoleDao:
01.using NHDataAccess;
02.using NHEntity;
03.using NHEntity.PK;
04.  
05.namespace NHDao.Interfaces
06.{
07.    public interface IUserRoleDao : IGenericDao<UserRoleEntity, PKUserRole>
08.    {
09.    }
10.}

DAOFactory

Como la clase DAOFactory es una clase abstracta(al ser abstracta no podemos instanciarla)

para poder hacer esto implementamos un metodo estático “Instance()” que me creara una

instancia de un tipo, en este caso de la clase abstracta DAOFactory

01.using System;
02.using NHDao.Interfaces;
03.namespace NHDao
04.{
05.    public abstract  class DaoFactory
06.    {
07.        public static Type DAO_FACTORY = typeof (FrameworkDaoFactory);
08.        public static DaoFactory Instance(Type factory)
09.        {
10.            try
11.            {
12.                return (DaoFactory) Activator.CreateInstance(factory);
13.                  
14.            }catch(Exception ex)
15.            {
16.                throw new Exception("No se crear Factory de DAO:"+factory);
17.            }
18.        }
19.        public abstract IRoleDAO GetRoleDAO();
20.        public abstract IUserDAO GetUserDAO();
21.        public abstract IUserRoleDAO GetUserRoleDAO();
22.    }
23.}

FrameworkDaoFactory

01.using NHDao.Implementations;
02.using NHDao.Interfaces;
03.namespace NHDao
04.{
05.    public class FrameworkDaoFactory : DaoFactory
06.    {
07.        public override IRoleDAO GetRoleDAO()
08.        {
09.            return new RoleDAO();
10.        }
11.        public override IUserDAO GetUserDAO()
12.        {
13.            return new UserDAO();
14.        }
15.        public override IUserRoleDAO GetUserRoleDAO()
16.        {
17.            return new UserRoleDAO();
18.        }
19.    }
20.}

Hasta Aquí su primera parte , en unos dias publícare la segunda parte , y despues la tercera parte.

Espero hasta aportado , si tienen dudas ,no duden en escribirme y trataré de responder en lo más rápido que me sea posible.

(*) Actualización:

Bueno

 

José Fabricio Rojas