Cómo no odiar a la session de NHibernate

6 pasos para llega a odiar la session de nh

Primer paso: pensar y usar la session de nh (de ahora en mas simplemente ‘session’) como si sólo fuese la una conexión a base de datos, es decir que la abrimos, la usamos y la cerramos.

Segundo paso: cuando empiezan las “LazyInitializationException” ponemos los lazy en false en un intento de que no lance más esa odiosa excepción.

Tercer paso: cuando queremos actualizar objetos empezamos a recibir “PersistentObjectException: detached entity passed to persist” o “HibernateException: reassociated object has dirty collection”. y aquí es donde tendemos a dejar una session abierta y usarla en toda la aplicación. Quizás no nos damos cuenta, pero ya podríamos volver a poner el lazy en true.

en este momento bastaría con poner “LazyInitializationException” o “PersistentObjectException: detached entity” o “NHibernate reassociated object has dirty collection” en google para saber que no estamos solos, ni somos los primeros en encontrarnos con este problema.

Cuarto paso: si seguimos con los lazy en false, vamos a tener problemas de performance cuando el volumen de datos sea considerable (afectando así a la escalabilidad), vamos a pedir una factura (para consultar la fecha por ejemplo) y nos va a cargar las líneas de la misma, sus tracks, su álbum, su artista, los otros tracks del álbum, etc. Aquí es donde decimos que “NHibernate no sirve para aplicaciones con un dominio complejo” o “no sirve para grandes aplicaciones como la nuestra”.

Quinto paso: si en algún momento optamos por una session dejándola abierta, se vuelve a complicar cuando la aplicación corre en múltiples hilos, como cuando un sitio web entra en testing ya que hay usuarios concurrentes, cosa que no pasaba en el ambiente de desarrollo local de cada PC. Aquí ya estamos “complicados”.

Sexto extra: como frutilla del postre, luego de que la session nos lanza una excepción de, por ejemplo, validación, notamos que la session empieza a tener un comportamiento extraño. No nos guarda los objetos que le decimos que nos guarde. Aquí llegamos al odio!

Algunos otros síntomas también pueden ser el uso de Session.Evict, Session.Clear, Session.Flush por todo nuestro código. Aquí se declaró la guerra!

Posiblemente pensemos que no tenemos idea que es la session, y quizás sea verdad, quizás no tengamos idea de que es la session. Aquí empiezan las soluciones, empezamos a buscar en google como se debe manejar la session de NH, encontramos algunos frameworks que lo hacen y un montón de post al respecto de este tema, quizás así llegaste a este.

Empezando el camino de la reconciliación

Lo primero que tenemos que hacer es entender qué es esta session. Cuando más o menos entendamos esto, vamos a poder decidir como manejarla. Sino vamos a estar pensando que manejamos un auto y estamos piloteando a un avión (con 200 pasajeros a bordo) y nos vamos a dar cuenta cuando estemos carreteando.

Algunos tips sobre que es la session:
- la session tiene información del estado original de los objetos, que luego va a usar para saber si los mismos fueron modificados.
- la session tiene una caché que nos garantiza que existe sólo una instancia de cada objeto dentro de ésta, y que no a volver a buscar en la base de datos un objeto que ya consultó a menos que se lo indiquemos específicamente.
- la session va a volver a ser usada cuando se carguen los lazy loading (ver ejemplo más abajo).

Podemos pensar a la session de nh como una conversación entre los objetos de nuestra aplicación y nuestra base de datos. Esta conversación puede durar lo que una llamada a un método, lo que un request dentro de un entorno web o estar asociada al ciclo de vida de otro objeto, etc. Lo importante en este punto es entender que el ciclo de vida de la session de nh depende del contenido de la conversación, no podemos cortar la conversación, seguir hablando y pretender entendernos. Si queremos introducir un objeto a una conversación (session) diferente a la que lo originó hay técnicas especificas para esto (reattach). Así que o trabajamos con la misma session o hacemos un reattach.

Si logramos entender este concepto, vamos a saber cuanto tiene que durar nuestra conversación, vamos a entender el ciclo de vida que debe tener nuestra session. Y aquí es donde podemos optar por algún framework que lo haga por nosotros, pero siempre le vamos a tener que decir qué ciclo de vida queremos que tenga en cada caso.

framworks que administran la session por nosotros: los mismos Context de NHibernate (como WebSessionContext y ThreadStaticSessionContext), unhaddinsNHibernate.Burrow, Castle, Spring.NET, Rhino Tools, etc.

Entendiendo el Lazy Loading con ejemplos

   1: Invoice invoice;
   2: using (ISession session = sessionFactory.OpenSession())
   3: {
   4:     invoice = session.Get<Invoice>(id);
   5:     Assert.IsNotNull(invoice);
   6:  
   7:     Assert.IsNotNull(invoice.Customer);
   8:     Assert.Greater(invoice.Customer.Id, 0);
   9: }
  10:  
  11: string firstName = string.Empty;
  12: Assert.Throws<LazyInitializationException>(() => firstName = invoice.Customer.FirstName);
  13:  
  14: Assert.IsEmpty(firstName);




como podemos ver en este test, en la línea 9 estamos cerrando la session (el dispose del using) y en la línea 12 estamos confirmando una excepción (de tipo LazyInitializationException) al intentar acceder a la propiedad FirstName de la propiedad Customer de Invoice. Esto significa que la propiedad Customer no fue cargada aún (de la base de datos) y necesitaba la session de nh para hacerlo, la misma session con que fue cargado el objeto. Además, como pueden observar, en las líneas 7 y 8 estamos haciendo uso de esta propiedad, pero como no es necesario (todavía) ir hasta la base de datos por estos datos es que nhibernate no los carga.

¿cuál sería el ciclo de vida correcto para este ejemplo?


   1: using (ISession session = sessionFactory.OpenSession())
   2: {
   3:     Invoice invoice = session.Get<Invoice>(id);
   4:     Assert.IsNotNull(invoice);
   5:  
   6:     Assert.IsNotNull(invoice.Customer);
   7:     Assert.Greater(invoice.Customer.Id, 0);
   8:  
   9:     string firstName = invoice.Customer.FirstName;
  10:  
  11:     Assert.AreEqual("Nelo", firstName);
  12: }


El objeto invoice (y los objetos relacionados por sus propiedades) se usan dentro del ciclo de vida de la session.

Les dejo una lectura recomendada (obligatoria diría) aquí.

9 comentarios:

  1. Nelo

    Me encuentro justamente en este tema (Administrar la sesion), y mi inquietud es la siguiente

    En tu codigo haces uso de ThreadStaticSessionContext, Fabio comenta que esto es lo optimo para test.
    Para el Web requieres WebSessionContext e implementar IHttpModule, pero revizando el codigo no logro encontrar esta implementacion

    Cualquier comentario estoy agradecido

    ResponderBorrar
  2. Porque está desarrollado con el manejo de session a nivel de los llamados al servicio de aplicación y trabajando con DTO de dicho servicio hacía el frontend me fue bastante simple. Pensá en AutoMapper para la transformación de tus objetos de negocio a DTO y listo. Igual, Fabio sabe mucho mas que yo de nhibernate así que si tenes que optar, quedate con su opinión. El HttpModule para hacer session per request me parece bien, aunque no me convence para la transacción ya que en el PostRequestHandlerExecute el response ya está renderizado y es complicado incluir mensajes. ¿me explico?. El session per call al que se refiere Fabio con antipattern es cuando lo hacés a nivel de DAO / repository.

    ResponderBorrar
  3. Este comentario ha sido eliminado por el autor.

    ResponderBorrar
  4. Gracias Nelo, si estoy revizando DTO (conceptos, implementaciones, etc) y AutoMapper, veo en el foro de alnet,que por ejemplo dtos tambien son llamados viewmodels que opinas tu ?

    Independientemente del nombre, me estoy dando cuenta que debo si o si utilizarlo ya que no debo exponer mis entidades en mis controllers como se me indico anteriormente

    Por otro lado veo aqui http://jfromaniello.blogspot.com/search/label/Asp.Net%20MVC, por ejemplo que no implementa IHttpModule, ni tampoco utiliza dentro nada que haga referencia NHibernate.Context. por ejemplo.

    Y solo pone en esa seccion "managed_web", aunque en el libro de nhiA, comentan que solo se debe poner web, ya que lo anterior es deprecate

    En fin espero tus comentarios mientras sigo viendo el codigo

    ResponderBorrar
  5. Es por esta razon (creo) que no utilizaste el uNhAddIns

    http://www.mail-archive.com/nhibernate-hispano@googlegroups.com/msg08892.html

    ResponderBorrar
  6. respecto a "model", es una palabra con muchos significados últimamente, estoy tratando de dejar de usarla.
    Respecto a managed_web, quizás sea posterior al libro que mencionás. Fijate bien.
    Respecto a porque no utilizé unhaddins, te paso por mail privado el ActionFilter que usé (por recomendación de Fabio)

    ResponderBorrar
  7. Gracias Nelo ya tengo el codigo en mi correo, Resumo lo siguiente
    Para manejar session-per-request la mejor forma en una aplicacion asp net mvc seria:
    1.- en el file de configuracion de nh poner web en current_session_context_class
    2.- implementar ActionFilterAttribute, codigo enviado
    3.- por medio de un atributo (con lo creado en el paso2) colocamos ya sea en el controller o en la accion que requiere persistencia

    y eso es todo, me olvido de implementar IHttpModule, o utilizar cualquier otro administrador de sessiones o meter mano el global.asax (tal cual lo implementa José)

    Gracias nuevamente por tus comentarios

    ResponderBorrar
  8. jajaj... me reí mucho con los comentarios :D
    pasé por lo mismo...
    buen dato el de los frameworks

    ResponderBorrar