| Hola, Despues de mucho tiempo de ausencia , empezaré a publicar una serie de post sobre Nhibernate sobre aplicaciones web, iremos desarrollando una aplicación de manera evolutiva, desde una aplicación simple hasta una aplicación basada en N-capas. Lo primero para esta demo utilizaremos: - SQL Server Express 2005 - NHibernate 2.0, si aún no lo descargas , descárgatelo de aqui - ASP.NET 2.x 1. Crearemos una aplicación web con ayuda del visual studio 2005. 2. Distribuiremos nuestra aplicación en las carpetas: - Entities, nuestra carpetas de entidades. - Lib, Librerias de Nhibernate y las demás que utiliza. - Mappings, Archivos de mapeo (hbm.xml). - Archivo de configuración de Nhibernate (hibernate.cfg.xml) 3. Esta aplicación es un mantenimiento básico de un Login, en donde tenemos 3 tablas, Users, Roles, UserRole. 4. Nuestro Diagrama de clases muestra las clases: User: Que mapea la tabla Users Role: Que mapea la tabla Roles Estas se relaciónan mediante "Asociación" atravez de la entidad UserRole, que el modelo de datos actua como una tabla intermedia(esto para romper la relación de muchos a muchos, existente entre Users y Roles). |
5. La entidad Role: | using System; using System.Collections.Generic; namespace nhweb { public class Role : IEquatable<Role> { private int idRole; private string role; private IList<UserRole> userroleList = new List<UserRole>(); public virtual int IdRole { get { return idRole; } set { idRole = value; } } public virtual string RoleName { get { return role; } set { role = value; } } public virtual IList<UserRole> UserRoleList { get { return userroleList; } set { userroleList = value; } } #region Miembros de IEquatable<Role> public virtual bool Equals(Role other) { return other.IdRole.Equals(this.IdRole ); } #endregion } } | 6. La Entidad User: | using System; using System.Collections.Generic; namespace nhweb { public class User : IEquatable<User> { private int idUser; private string login; private string password; private IList<UserRole> userroleList = new List<UserRole>(); public virtual int IdUser { get { return idUser; } set { idUser = value; } } public virtual string Login { get { return login; } set { login = value; } } public virtual string Password { get { return password; } set { password = value; } } public virtual IList<UserRole> UserRoleList { get { return userroleList; } set { userroleList = value; } } public virtual bool Equals(User obj) { return obj.Equals(this.IdUser); } } } | 7. La entidad UserRole: | using System; namespace nhweb { public class UserRole : IEquatable<UserRole> { private int idUserRole; private User objUser = new User(); private Role objRole = new Role(); public virtual int IdUserRole { get { return idUserRole; } set { idUserRole = value; } } public virtual User FkUser { get { return objUser; } set { objUser = value; } } public virtual Role FkRole { get { return objRole; } set { objRole = value;} } #region Miembros de IEquatable<UserRole> public virtual bool Equals(UserRole other) { if (other.IdUserRole.Equals(this.IdUserRole)) return true; return false; } #endregion } } | 8. Los archivos de mapeo Role.hbm.xml, hay que recordar que estos deben de estar como recursos incrustados. | <?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nhweb" namespace="nhweb"> <class name="Role" table="Roles"> <id name="IdRole" column="IdRole"> <generator class="identity"></generator> </id> <property name="RoleName" column="Role" type="String"/> <bag name="UserRoleList" cascade="save-update" inverse="true"> <key column="IdRole"/> <one-to-many class="UserRole"/> </bag> </class> </hibernate-mapping> | 9. Los Archivos de mapeo User.hbm.xml, hay que recordar que al igual que el elemento anterior también debe de estar incrustado. | <?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nhweb" namespace="nhweb"> <class name="User" table="Users"> <id name="IdUser" column ="IdUser" type="int"> <generator class="identity"/> </id> <property name="Login" type="String" not-null="true"/> <property name="Password" type="String" not-null="true"/> <bag name="UserRoleList" cascade="save-updat" inverse="true" lazy="true"> <key column="IdUser"/> <one-to-many class="UserRole"/> </bag> </class> </hibernate-mapping> | 10. El Archivo de mapeo UserRole.hbm.xml | <?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="nhweb" namespace="nhweb"> <class name="UserRole" table="UserRole" lazy="true"> <id name="IdUserRole"> <generator class="identity"></generator> </id> <many-to-one name="FkUser" class="User" column="IdUser"/> <many-to-one name="FkRole" class="Role" column="IdRole"/> </class> </hibernate-mapping> | 11. El archivo de configuración de Nhibernate esta definido por: - Driver - Dialecto - Cadena de Conección, entre los principales.*
*Hay que recordar que la propiedad "show_sql", indica que se mostrarán las consultas SQL que se generarán por Nhibernate, están consultas pueden ser visualizadas una vez configurado un framework de log, como Log4Net por ejm, la propiedad "current_session_context_class" indica que la aplicación tiene una configuración para web y que NH usa la sesion de ASP.NET(HttpContext) para mantener la sesion de NHibernate. NHHelper Esta clase abstracta es un Template ó plantila , esto quiere decir que en tiempo de ejecución adopta el tipo que se le indica. Esta clase es la que realiza casi todo el trabajo, tanto con los metodos de acceso a datos y los de logica de negocio. Si bien es cierto, es aun muy simple esta clase ,pues transaccionabilidad es solo a nivel de objeto, esto lo mejoraremos para la prox. versión , donde la transaccionalidad sera vital.( lo dejaré para una serie de post que publicaré en adelante) por ejm nuestra clase seria: | using System; using System.Collections.Generic; using NHibernate.Type; using NHibernate; using NHibernate.Cfg; namespace nhweb.Logic { public abstract class NHHelper<T> { protected Configuration GetConfigurarion() { Configuration cfg = new Configuration(); cfg.Configure("hibernate.cfg.xml"); return cfg; } protected void Save(T entity) { ISessionFactory sessionFactory = GetConfigurarion().BuildSessionFactory(); ISession session = sessionFactory.OpenSession(); ITransaction transaction = null; try { transaction = session.BeginTransaction(); session.Save(entity); transaction.Commit(); session.Flush(); session.Close(); } catch (Exception ) { if (transaction != null) transaction.Rollback(); throw; } } protected void Update(T entity) { ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession sesion = sesiones.OpenSession(); ITransaction transaccion = null; try { transaccion = sesion.BeginTransaction(); sesion.SaveOrUpdate(entity); transaccion.Commit(); sesion.Flush(); sesion.Close(); } catch (Exception ) { if (transaccion != null) transaccion.Rollback(); throw; } } protected void Delete(T entity) { ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession sesion = sesiones.OpenSession(); ITransaction transaccion = null; try { transaccion = sesion.BeginTransaction(); sesion.Delete(entity); transaccion.Commit(); sesion.Flush(); sesion.Close(); } catch (Exception ) { if (transaccion != null) transaccion.Rollback(); throw; } } protected void Delete(string myQuery, string[] parameters, object[] values) { ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession sesion = sesiones.OpenSession(); ITransaction transaccion = null; try { transaccion = sesion.BeginTransaction(); IQuery consulta = sesion.CreateQuery(myQuery); for (int i = 0; i < parameters.Length; i++) { consulta.SetParameter(parameters[i], values[i]); } sesion.Delete(consulta); transaccion.Commit(); sesion.Flush(); sesion.Close(); } catch (Exception ) { if (transaccion != null) transaccion.Rollback(); throw; } } protected void Delete(string myQuery, object[] values, IType[] myType) { ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession sesion = sesiones.OpenSession(); ITransaction transaccion = null; try { transaccion = sesion.BeginTransaction(); sesion.Delete(myQuery, values, myType); transaccion.Commit(); sesion.Flush(); sesion.Close(); } catch (Exception ) { if (transaccion != null) transaccion.Rollback(); throw; } } protected IList<T> List() { List<T> orders = new List<T>(); ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession session = sesiones.OpenSession(); ICriteria criteria = session.CreateCriteria(typeof(T)); criteria.List(orders); return orders; } protected IList<T> List(string myQuery, string[] parameters, object[] values) { List<T> orders = new List<T>(); ISessionFactory sesiones = GetConfigurarion().BuildSessionFactory(); ISession session = sesiones.OpenSession(); IQuery consulta = session.CreateQuery(myQuery); for (int i = 0; i < parameters.Length; i++) { consulta.SetParameter(parameters[i], values[i]); } consulta.List(orders); return orders; } } } | La clase NHHelper, posee en primera instancia los metodos denominados CRUD, Create, Read, Update and Delete, Ademas de estos metodos tambien están los metodos de Listado (List), Listado por una consula, donde se pasan por parametos , la consulta HQL, en nombre de los parametros y su valor; Tambien un método de eliminación por una consulta HQL. UserLogic En esta demo , al no tener una capa de acceso de datos , ni una clase de Logica de negocios , por ser un ejemplo pequeño, todo el CRUD y principales funciones de negocios las implementaremos en esta clase. Ahora,esta clase lo que hace es heredad de la clase abstract NHHelper y pasarle como tipo "User". Los métodos "GetUserById", realiza una búsqueda con HQL atravéz del "Id" del Usuario, pasando como parámetros IdUser, La consulta HQL es en modo Texto y la asignación del parámetro se hace atravéz del operador ":". Es muy impornate aqui el papel que juegan los Generics y Templates. | using System.Collections.Generic; namespace nhweb.Logic { public class UserLogic : NHHelper<User> { public void InsertUser(User user) { Save(user); } public void UpdateUser(User user) { Update(user); } public void DeleteUser(User user) { Delete(user); } public IList<User> ListUsers() { return List(); } public User GetUserById(int IdUser) { string query = "from User u where u.IdUser=:IdUser"; string[] paramString = new string[1]; paramString[0] = "IdUser"; object[] arrObject = new object[1]; arrObject[0] = IdUser; return List(query, paramString, arrObject)[0]; } public bool Validate(string Login,string Password) { string query = "from User u where u.Login=:Login and u.Password=:Password"; string[] paramString = new string[2]; paramString[0] = "Login"; paramString[1] = "Password"; object[] arrObject = new object[2]; arrObject[0] = Login; arrObject[1] = Password; return List(query, paramString, arrObject).Count>0; } public User GetUser(User user) { if (Validate(user.Login,user.Password)) { string query = "from User u where u.Login=:Login and u.Password=:Password"; string[] paramString = new string[2]; paramString[0] = "Login"; paramString[1] = "Password"; object[] arrObject = new object[2]; arrObject[0] = user.Login; arrObject[1] = user.Password; return List(query, paramString, arrObject)[0]; } else { return null; } } } } | Nuestra presentación:
| using System; using nhweb.Logic; using nhweb.Entities; namespace nhweb { public partial class _Default : System.Web.UI.Page { private readonly UserLogic userLog = new UserLogic(); private User objUser = new User(); protected void btnLogin_Click(object sender, EventArgs e) { if (userLog.Validate(txtLogin.Text, txtPassword.Text)) { objUser.Login = txtLogin.Text; objUser.Password = txtPassword.Text; User user = userLog.GetUser(objUser); Session.Add("id", user.IdUser); Response.Redirect("/frmMain.aspx?IdUser=" + user.IdUser); } } } } | El código muestra la validación de un usuario, através de su login y contraseña, una vez pasada la validación obtenemos el usuario atravéz de los datos ingresados; y enviamos por la url el Id de usuario logeado. También tenemos un Master Page que contiene nuestras paginas ASPX. Despues de la validación , enviado una vez el Id del Usuario por la URL , al cargar la página maestra, obtenemos el IdUser mediante el Metodo QueryString lo casteamos a un entero y recuperamos el Usuario a travéz de su id, de esta forma se tiene accedo a todas las propiedades del Usuario. Tambien despues de esto , podria optar por guardar el Nombre de usuario en una variable de session, Aunque esto no es muy recomendable , pero para efectos de demo , lo haremos.
Ahora una Edición de un usuario (solo por efectos de demostración del campo mostraremos el password) En el formulario de Inserción/edicion: | using System; using nhweb.Entities; using nhweb.Logic; namespace nhweb { public partial class frmEditUser : System.Web.UI.Page { private User objUser = new User(); private UserLogic objLogUser = new UserLogic(); protected void Page_Load(object sender, EventArgs e) { if(Request.QueryString["state"]=="edit") { objUser = objLogUser.GetUserById(Int32.Parse(Request.QueryString["IdUser"])); EditUser(); } } protected void btnSave_Click(object sender, EventArgs e) { NewUser(); if(Request.QueryString["state"]=="edit") { objLogUser.UpdateUser(objUser); } else { objLogUser.InsertUser(objUser); } } private void EditUser() { txtLogin.Text = objUser.Login; } private void NewUser() { objUser.Login = txtLogin.Text ; objUser.Password = txtPassword.Text; } } } | Bueno, lo importante aquí es señalar el uso de la clase Helper(NHHelper) que al estilo de C++ , usa templates , con lo cual a puede optar el tipo que se le pase como parametro. Espero les haya ayudado. Saludos desde Lima , peru Jose Fabricio Rojas
|