Para abrir el blog no elegí mejor tema que el grandioso framework de persistencia, de cual puedo estar hablando sino es NHibernate.
La idea principal de esta publicación será ni más ni menos que proporcionar un ejemplo integral de varias características que mucha falta hacen y que tanto cuesta aprender al comenzar con esta herramienta. Este artículo no tiene intención de ser detallista ni explicar puntualmente cada aspecto implementado, para ello podrá encontrar artículos mas destacados como por ejemplo el excelente articulo publicado en el Blog de Dario Quintana. La idea final es simplemente proporcionar un ejemplo que permita ver varias características funcionando juntas.Aspectos implementados
Entre las características implementadas encontraran los siguientes puntos:
- Modelo completamente tipado
- Relaciones many-to-one, one-to-many y many-to-many
- Persistencia de enumerados
- Persistencia de campos nText (type="StringClob")
- Herencia table per class hierarchy <subclass>, table per subclass <joined-subclass>
- Herencia table per class hierarchy <subclass>, utilizando una formula en el <discriminator>.
- Consultas hql entre relaciones many-to-many
[C#] |
Diagramas del Dominio
Diagrama del dominio de la Institución
Diagrama del dominio de reserva de libros
Diagramas de persistencia en SQL Server 2005
Diagrama de Base de Datos de la Institución
Diagrama de Base de Datos de las Reservas de libros
Persistencia de Enumerados
Un punto mas que importante para brinda consistencia al modelo, es la utilización de enumerados en al definición del dominio de la aplicación. En la clase ClaseEntity, se observara un atributo de nombre FormaPago definido como un enumerado, por supuesto en la definición de su mapper (Clase.hbm.xml) encontraran la definición de de la propiedad<property type="NHib.Infrastructure.GenericEnumMapper`1[[NHib.Domain.Entities.enumFormaPago, NHib.Domain]], NHib.Infrastructure" name="FormaPago" column="FormaPago" />
Se observa claramente la utilización de un mapper de enumerado (el cual esta alojado dentro del NHib.Infrastructure), el cual permitirá recuperar el número asignado al ítem del enumerado asignado.
Persistencia de campos nText Un tema interesante para probar es la persistencia de atributos espaciales, como es el caso de comentarios, los cuales deben contener texto que escapa a un simple descripción. Es en estos casos que se utilizara en nText, existiendo en NHibernate una forma especial de definir este tipo de mapeo. En la clase LibroEntity, es justamente donde se hace uso de este, en el atributo Comentarios definido en el mapper como:<property column="Comentario" type="StringClob" name="Comentario" />
Es en la clase de test TestLibro.cs -> LargoComentarios(), donde se realiza una prueba de su correcta persistencia, comprobando que no se trunca el valor del atributoHerencia
En la aplicación encontraran tres tipos de herencia implementada, utilizando:Tabla para todas las clases
Este primer tipo de implementación lo ubicaran en la entidad LibroEntity, en esta se remarca la definición de los atributos discriminator-value el cual toma un valor numérico cuyo tipo es definido en el tag <discriminator>
Se aclara que el tipo de dato por defecto definido es del tipo de datos string, esto a simple vista pareciera no tener mayor implicancia, pero al momento de utilizar un <discriminator> con valores numéricos, todas las clases deberán tener definido un discriminator-value, aunque la clase principal no sea utilizada.
En el ejemplo la definción del tag <class>, al estar utilizando la interface ILibroEntity, debe definir un discriminator-value, con un valor que no es utilizado por el modelo como diferenciador en la herencia de clases, esto debe hace con el simple objetivo de evitar un error de tipos de datos.
Tabla para cada subclase
La implementación se podrá observar en la clase ReservaEntity, a diferencia del ejemplo anterior que utiliza una sola clase, no existe el discriminator-value, y es por medio de la relación uno a uno de las tablas que se mapean como se obtiene el tipo de objeto.
En este caso se hará uso de un tag de mapeo: <joined-subclass>, la cual definirá cual es el atributo relación entre las tablas, siendo esta la razón de utilizar <key column="" />
Herencia utilizando formulas en el discriminator
Esta será utilizada en los casos en donde se pretenda generalizar entidades que pueden ser agrupadas desde otro nivel de abstracción que requiere el modelo analizado.
En el ejemplo sucedió con los convenios, (definido en la entidad ConvenioEntity) el cual describe la existencia de varios tipos de talleres y excursiones, que se pueden asociar a una institución.
Pero el modelo solo necesitaba diferenciar entre convenios del tipo taller y excursión, para esto necesita agrupar distintos valores en un discriminator. Además para aumentar la complejidad el atributo esta definido con un carácter mientras que el mapeo hará uso de un valor numérico.Es por medio del atributo formula que se transformara el valor del discriminator de una valor del tipo carácter a uno numérico.
Se debe aclarar que este tipo de definiciones presentan un inconveniente, solo podrán recuperarse los distintos tipos de subclases definidas, pero no podrán persistirse, ya que es imposible por medio de la definición de la formula saber que atributo de tipo especificar, esto deberá realizarse implícitamente en la definición de la clase a persistir.
Es por esta razón que se notara la definición de un enumerado dentro de las subclases que permita diferenciar las distintas clases concretas de Convenios. La redefinición del constructor permite forzar la especificación al momento de crear una nueva subtipo de Convenio.
El framework necesita la especificación de un constructor sin parámetros, pero para evitar su utilización es que se define como protected. Al igual que la definición del atributo Tipo (también protected) que permitirá ser utilizado solamente por el mapper, permitiendo especificar el tipo concreto a persistir.
Además se debe entender que por tratarse de un campo del tipo char, se realiza por medio del “case” la asignación del valor correcto, si hubiera sido numérico se podría haber persistido el enumerado directamente como el caso visto en a persistencia de enumerados.
Consultas hql entre many-to-many
Si bien la definición del mapeo de la navegación entre entidades resulta de suma utilidad, hay ocasiones en donde se requiere obtener algún tipo de filtro aplicado al otro extremo de la relación, es en este punto donde se hará uso de las consultas HQL, y en ellas el operador JOIN.
Para visualizar un ejemplo de su implementación se podrán visitar varios puntos donde fueron utilizados, destacando:
- ProfesorRepository -> GetAlumnos(): Implementa un join en donde se recorren dos relaciones muchos a muchos. Esta podrá ser testeada desde TestProfesores -> RecuperoAlumnos()
- AlumnoRepository -> GetByClase(): Define varias sobrecargas con diferente filtros que son aplicados a un join.
Eliminar en cascada
Como es de suponer esta facilidad de actualizar la cascada de elementos relacionados es de muchísima importancia, pero durante la utilización de la navegación entre entidades me encontré con un pequeño detalle que quería remarcar. Se trata de la utilización de la propiedad inverse="true", esta es de suma importancia cuando se esta utilizando navegación bidireccional. De no especificarse esta propiedad al momento de producirse, por ejemplo, la eliminación de un objeto padre, sus hijos serán actualizados y al no encontrase la entidad superior generará una excepción que indicara la imposibilidad de insertar null en el campo de la tabla. Esto podría resolverse fácilmente marcando el campo de relación para que permita null en su contenido, pero no todos los tipos de relaciones en el modelo pueden hacer esto. Un ejemplo del uso de esta propiedad podrá encontrase en el achivo de mapeo de la entidad Institución (Institucion.hbm.xml), y la prueba de la eliminacion en cascada se encuentra en el test: TestInstituciones -> EliminoInstitucion()Nota: Si quieren probar como funciona esta propiedad elimínenla del archivo de mapeo Institucion.hbm.xml, de la relación con el Alumno por ejemplo, e intenten correr el test: TestInstituciones -> EliminoInstitucion(), para ver los resultados.
También podrán marcar el campos "Institución" de la tabla "Alumnos", observarán como se soluciona el problema.Implementación
En la aplicación encontraran que esta formada por varias capas entre ellas la de Aplicación y Presentación, pero estas no están implementadas, pues no era necesario para el objetivo de aprender NHibernate, simplemente con la utilización del proyecto de Test se pudo comprobar el correcto funcionamiento del framewok de persistencia. Se debe remarcar que el dominio debe ser tomado algo inventado, no perteneciente a un dominio existente, pensado simplemente para lograr el objetivo principal de verificar la funcionalidad de NHibernate. Si bien el código es sumamente útil con tan solo visualizar el código de los test, así como también el de los mapper, es posible ejecutarlo si se conecta apropiadamente a una base de datos. Para ellos, como en la mayoría de las aplicaciones, simplemente se debe modificar el app.config del proyecto de pruebas, apuntándolo a la DB que corresponda. Para crear la estructura de la base de datos se encontrar un proyecto con los script de creación de la estructura, o en caso contrario podrán hacer uso de los archivos .mdf y .ldf de la base de datos, adjuntándolos por medio de la opciones de attach del SQL Server 2005. Para la ejecución de los test hice uso de UnitRun, de esta forma podía ejecutarlos uno a uno en modo debug e ir analizando como se comporta cada uno.Conclusión
Espero antes que nada haber aportado un granito mas de arena a la investigación de este potentísimo framework, apuntando principalmente aspectos un poco mas avanzados de los cuales es complicado encontrar ejemplo cerrados que los integre. Quedan varios puntos todavía por probar, como ser el mapeo a store procedure, paginado, etc Espero les sea de utilidad y cualquier duda, modificación o error que encuentren serán bienvenidos.
Hola!! En tu experiencia, cuáles son las contras de NHibernate y que cosas no puede hacer? Saludos!
ResponderEliminarHola Mauro, que tal.
ResponderEliminarEl punto mas flojo que veo esta referido a como trabaja con claves compuestas, la verdad este punto me ha dado bastante dolor de cabeza, especialmente si trabajas con diseños de bases existentes y queres modelizarlas en objetos lo mejor que se pueda.
Recomendacion: implementar algun tipo de herencia con claves compuestas no te voy a decir imposible, pero casi.
Lo importante aca es que tengas un poco de libertar para rediseñar la estructura de datos, si es asi, la verdad no tenes limite, ahora si la estructura de la db es fija, bueno ahi la cosa cambia un poco, y de seguro estes limitado en algunos aspectos.
Hola Leandro!
ResponderEliminarGracias por el ejemplo, y felicitaciones por estrenar blog!
Bien, ahora estoy trabajando, pero espero ver en mas detalle tu ejemplo. Me gusta que hayas encarado el tema de herencia, y de discriminador, asi como el campo clob.
Nos leemos!
Angel "Java" Lopez
http://www.ajlopez.com/
Hola Leandro
ResponderEliminarQueria felicitarte por el excelente ejemplo que publicaste.
Despues de mucho buscar, por fin encuentro un ejemplo bien hecho, con un muy buen diseño.
Realmente fundamental para poder empezar a entender el tema de NH
Pregunta, podrias explicar la diferencia entre el Flush y el Clear de una Session? y porque NH bloquea un objeto (Read Mode) cuando lo obtuvo, si si se quiere hacer un IList y asignar ese IList a una grilla, se produce un fallo de Reflection. Como que NH no permite acceder a objetos previamente cargados. Que habria que hacer, un Flush o un Clear antes de traer un List?
No se si logré explicar el problema.
Un fuerte abrazo y adelante !!!
Daniel Laco
Leandro, muy bueno tu ejemplo.
ResponderEliminarExplicas muy bien los distintos casos/formas de mappings, etc. Voy a agregarlo en mi lista de links, referencias.
Lo unico que no estoy muy de acuerdo es con el tema del Session Management, en WinForms.
En tu caso estas usando static para la session lo cual no resulta muy practico en WinForms. (*)
Yo acabo de arrancar un ejemplo (fijate en mi BLOG) donde trato de profundizar al menos en este tema.
Nota: Mi ejemplo esta en pañales pero bienvenido cualquier comentario!
Los Link que estan al principio no funcionan, donde puedo descargar esto?
ResponderEliminarEl enlace no funciona, es una pena porque tiene buena pinta
ResponderEliminarExcelente post...con esto creo que entendere mejor este tema, que ya llevo con muchas ganas de aprender...
ResponderEliminarGracias.
Hola...
ResponderEliminarEstuve echando un vistazo al artículo, y me entró una duda. Se puede usar NHibernate para usar con compact framework?
Me parece que no se puede, es así?
hola
ResponderEliminarDebo decirte que dudo mucho que puedas usar NHibernate en tu desarrollo mobile
NHibernate on the .net compact framework
NHibernate for .NET Compact Framework
Ademas si lo piensas te daras cuanta de porque no se puede, mas alla que las dll de NHibernate puedas referenciarlas, este framework usa por debajo las librerias de ado.net para conectarse y trabajar contra la db, por ejemplo si desarrollas para conectarte contra Sql Server u Oracle estas librerias no podrias usarlas en tu desarrollo en compact framework.
Algo que si podrias usar si te animas es db4o, esta es una base de datos orientada a objetos, si te gusta diseñar objetos y luego pesistirlos, similar a como harias con NHibernate, es una buena alternativa.
db4o Compact Framework Database
ademas es una db de libre uso
saludos
Los Link's de los ejemplos estan rotos, por favor podrias arreglarlos
ResponderEliminarhola Marcelo
ResponderEliminarYa esta actualizado el link de descarga.
Igualmente un comentario, te en cuenta que este codigo fue desarrollado con VS2005 y se uso UnitRun porque aun no se contaban con test en el VS, puede que este un poco desactualizado, aunque los conceptos de base de NHibernate aun deberia ser utiles.
saludos
saludos Leandro he leido tu articulo donde haces referencia al Inverse
ResponderEliminarcito:"De no especificarse esta propiedad al momento de producirse, por ejemplo, la eliminacion de un objeto padre, sus hijos seran actualizados y al no encontrase la entidad superior generará una exception que indicara la imposibilidad de insertar null en el campo de la tabla. "
Tengo una clase Persona y una clase Solicitud donde la primera tiene una lista de la 2da y en el mapeo uso HasMany. Si no uso inverse, en la tabla de solicitud en el campo de iD de Persona se ubican bien los respectivos id de Persona pero cuando intento borrar una persona me da un error que no me permite borrarla pues existe una referncia a ella en la tabla solicitud. Pero si le pongo el Inverse entonces cuando inserto la persona con su listado de Solicitudes en la tabla Solicitud en el campo Id de Pesona no se guarda el Id de la persona a las que esas solicitudes pertenecen. Por tanto de nada me sirbe la relacion. Alguna Idea????
hola Banzai
ResponderEliminarSuena logico lo que se produce en el primr planteo, si quiere eliminar una entidad drelacionada, salvo que habilites la eliminacion en cascada, esta bien que lance la exception.
Ahora el segundo planeo es raro, habia que analizar como se relacionana nivel de objeto estas entidades, o sea la entidad Persona le has definido una propiedad de Solicitudes, para navegar esta entidfad y para agtregar nuevos items y persistir.
La verdad estoy algo oxidado en el tema, hace tiempo que no trato con NHibernate, recomendaria si puedes plantear la consulta en el foro
https://groups.google.com/group/nhibernate-hispano
alli hay expertos que estan todo el dia con este ORM, seguro puedan aportar algo mas puntual
saludos
Muy chevere el ejemplo, pero tengo una inquietud, no se si es ignorancia o el formulario aspx no esta definido solo esta el default vacio , sera que lo tiene hecho.. es que soy nuevo y quiero ver como hacen el uso y el llamado de los metodos para que ingrese, modifique y elimine datos
ResponderEliminarhola Juan D. Delgado
ResponderEliminarla idea original del ejemplo era implementar todas las capas, pero como vi que se hacia largo solo quedo en los test
unir las entidades a presentacion deberia ser un paso simple, el ejemplo solo apuntaba a la persistencia
saludos
Excelente aporte, por los modelos y por que sale andando al primer intento.
ResponderEliminarVeo que es de hace unos años pero bajé el ejemplo, tiré dos lineas y salió andando sin ningún inconveniente.
public void GuardarInstitucion(string descripcion, int cuit)
{
InstitucionEntity institucion = new InstitucionEntity();
institucion.Cuit=cuit;
institucion.Descripcion=descripcion;
InstitucionRepository.Save(institucion);
}
consulta, al guardar siempre me pisa los datos de la tabla con los nuevos datos de la sessión, como puedo hacer algo tipo append en la base?
ResponderEliminarhola Fer
ResponderEliminarno entendi
o sea quieres hacer un INSERT en una tabla?
porque si es asi debes crear una nueva instancia de la entidad
Class1 inst = new Class1
//aqui completas propiedades
y envias a persistir a inst
saludos
Hola Leandro.
ResponderEliminarEl inconveniente es que creo una nueva entidad y lo que ocurre cuando hago el persist/save es que agrega el registro por ejemplo con un id 1, cuando en la base ya existia un registro con el mismo id=1, e incluso un 2 y 3.
lo único raro que tiene es que el id en la entidad viene de herencia de una clase entidad...
incluso antes de hacer commit el valor del id ya cambió...es decir, toma el nuevo objeto como un update...
ResponderEliminarahora, si creo tres instancias, hago el save de una, luego de la otra, etc si inserta 3...
raro
hola
ResponderEliminarcomo defines en el xml de mapeo el campo id que imagine es la key de la tabla, no?
indicas a este campo como del tipo identity, o sea autonumerico
o eras tu desde codigo que genera el secuencial
saludos
id name="ID">
ResponderEliminargenerator class="identity" />
column name="ID" sql-type="int" not-null="true" />
</id
dejé incompletos los tags porque sino no se veia.
Igual he probado con varias formas, incluso al instanciar con el valor id en null, pero lo que parece estar pasando es que al levantar la sesión o al persistir desconoce lo que ya está en la tabla pues arranca desde el primer id como si fuese una tabla sin registros.
así como está entiendo que debería tener en la sessión todos los objetos de la tabla para que detecte un nuevo registro...
ResponderEliminarListo. ya vi que era.
ResponderEliminarCuando armé la clase que generaba la instancia de la session estaba manteniendo estaba recreando la estructura de la tabla en cuestión como en tu ejemplo.
Muchas Gracias, buen blog!
Saludos!
Hola Leandro, quisiera hacerte una consulta. No se si este el espacio indicado, pero bueno, no conozco otro.
ResponderEliminarMi consulta es esta: Tengo 3 tablas, llamemoslas ventas, items e ingresos. Una venta tiene un conjunto de items.
Un ingreso tiene un conjunto de items. Podria utilizar una FK en items para relacionarla tanto con ventas como
con ingresos y mediante un campo que haga las veces de discriminante decidir si esa FK apunta a ventas o a
ingresos? de ser posible esto, que permite extender esta FK a tantas tablas como quiera, como seria el mapeo
en NH? Estos tipos de relaciones en la DB se me presentan habitualmente y la verdad no se como resolverlos...
Gracias de antemano Leandro.
hola MP
ResponderEliminarEse concepto tal como lo describes no se puede realizar a nivel de diseño de base de datos y por ende luego llevar al mapeo con NHibernate
Lo que si podrias hacer es aplicar el concepto de herencia, o sea definir en la tabla de item que tipo se trata, si es un ingreso o una venta.
Entonces las tablas de ventas, y la de ingresos, se relacionan ambas a la de items, esta ultima va a tener al menos 3 campos, el idventa (nullable), idingreso(nulable) y tipo (venta o ingreso), con eso se genera la relacion
Igualmente me parece que seria aun mejor si creas dos tablas de items, un ItemsVenta y un ItemsIngreso relacionado con cada tabla de forma separada porque cada item tiene conceptos difetentes, digo no se, seria para analizar al menos
saludos
Hola Leandro...
ResponderEliminarEstuve leyendo tu blog, especificamente el tema de los campos CLOB lo que en este momento me esta dando algunos dolores de cabeza.
Te explico: tengo una aplicacion donde se usa NHibernate. Esta app recupera informacion de, y guarda informacion en, una base de datos Oracle 11g pero especificamente cuando se intenta actualizar un campo de cierta tabla con la data correspondiente en un campo en particular de una de las tablas de la base de datos se origina una excepcion debido a que no puede guardar ese valor porque el campo es LONG. Vale decir que la data que intento guardar es una cadena muy muy larga.
Haciendo una inspeccion del mapping de la entidad dicho campo esta como STRING cuando en realidad en la base de datos Oracle el campo es CLOB.
He buscado y buscado en internet una solucion pero no logro dar con algo concreto y ENTENDIBLE para poder implementarlo y probar.
OJO: Soy principiante en NHibernate, apenas 3 meses, y de muchas cosas que he aprendido esta es una de las cosas que no he podido resolver.
Pregunto: Como se resuelve ese problema?. Hay algun articulo que se parezca a mi problema que pueda leer o si necesitas el codigo con mucho gusto puedo pasar el mapping de la entidad y la definicion de la misma en C#.
Saludos y espero puedas ayudarme...
hola Dirimo
ResponderEliminarlo que no me quedo claro es porque dices: intento guardar es una cadena muy muy larga.
o sea si es una cadena no deberia ser numerico el campo, porque es un string
habias revisado este link:
Mapping a long text string in Oracle and NHibernate
saludos
Hola Leandro,
ResponderEliminarDisculpa estoy iniciándome en esta tecnología de NHibernate y quiero trabajar con la versión 3.3.3 de NHibernate, este ejemplo puedo usarlo con esa versión?.
Saludos gracias
hola sadi++
ResponderEliminarla version que utilice aqui es bastante antigua, seguramente los conceptos no hayan cambiado y se pueda seguir usando de base, pero creo que NHibernate aplico mejoras que deberias, o al menos seria recomendable analizar, como ser la configuracion por medio de fluent
http://www.nuget.org/packages/FluentNhibernate
creo que mapear por fluent en lugar de xml es muchisimo mejor
NHibernate 3.0 Tutorial with Fluent NHibernate and Linq 2 NHibernate
analiza si esto no te resulta util, digo ya que vas a usar una version mas actualizada de NHibernate
actualmente estoy usando mas Entity Framework Code First, este se configura por fluent es es una maravilla
saludos
Buenas tardes Leandro.
ResponderEliminarMe estoy iniciando en el desarrollo de aplicaciones con NHibernate, pero me surge una duda que no he podido resolver.
Como almacenar un objeto en Caché, sin guardarlo aún en la base de datos, esto es porque tengo que desarrollar una aplicación que genere un objeto usuario y que el objeto usuario navegue en el sitio sin guardarlo en la base de datos hasta que haya terminado su participación entonces si deberá guardar su registro en base de datos. Quiero evitar tener que serializar el objeto para pasarlo entre las paginas ya que NHibernate es un ORM de persistencia, entonces me debería permitir almacenar el objeto en algún lugar del Caché de NHibernate sin guardar en la base de datos, esto es posible?????
Gracias por tu ayuda.
hola Carlos
ResponderEliminarque tipo de aplicacion estas desarrollando, es web o desktop ?
lo pregunto porque quizas no necesitas que sea NHibernate quien realice esto, sino que podrias usar la Session para conservar el usuario y accederlo desde diferentes paginas sin persistirlo
saludos
Hola Leandro, buen día, gracias por tu respuesta, mi aplicacion es web, por que no recomiendas persistir el objeto con nhibernate y como lo manejarias con sessiones???
ResponderEliminarhola Carlos
ResponderEliminarrecuerda que Nhibernate trabaja con el concepto de session, o sea puedes mantener un objeto en cache pero mientras la session este activa, lo cual no se mentiene entre request
si en el evento de un boton pones algo en la session de nhibernate pero no realizas flush para enviarlo a la tabla y se persista lo perderas para el siguiente request, porque imaginos la session no la mantienes entre request sino que creas una nueva, por eso el cache solo dura lo que el request este activo
podrias quizas evaluar
Habilitar cache de segundo nivel en NHibernate
NHibernate Second Level Caching Implementation
pero bueno habria que ver si serian util en este caso que planteas
Nota: cuando mencione Session no es la de asp.net, apuntaba a la de nhibernate
saludos
Carlos
ResponderEliminarpodrias plantear la duda aqui
NHibernate foro
quizas tengan alguna otra idea de como manejar los datos en este caso que planteas
saludos