Manejo de sesiones de nHibernate
27 Dec 10
Recientemente me he visto la VAN de Alt.Net Hispano sobre estrategias de uso de la sesión con nHibernate, en el que Nelo Pauselli (@nelopauselli) nos habla de la importancia de manejar correctamente las sesiones para que nuestra aplicación no comience a tener comportamientos extraños.
Voy a resumir con ejemplos las características y comportamientos más importantes de las sesiones de nHibernate que he aprendido. Para los ejemplos usaré el framework de test unitarios xUnit y C#.
¿Qué es una sesión de nHibernate?
En pocas palabras, podríamos decir que es el marco de trabajo que nos ofrece nHibernate en el cual se establece una conversación entre la aplicación y la base de datos para consultar y persistir información. Internamente implementa el patrón UnitOfWork, al igual que hace el ObjectContext de EntityFramework.
La sesión también nos provee de un primer nivel de cache en el que si pedimos un objeto de base de datos que previamente fue cargado en la sesión, nHibernate devolverá el objeto cacheado y no hará una nueva consulta a la base de datos. En nuestra aplicación podemos abrir tantas sesiones como necesitemos y cada una tendrá su propia cache de primer nivel.
La clase que usaremos para crear nuevas sesiones será la SessionFactory, de la cuál sólo tendremos una instancia para toda la aplicación.
Síntomas del mal manejo de la sesión en nuestra aplicación
En muchas ocasiones los desarrolladores echan la culpa a los framework por el mal comportamiento o rendimiento de sus aplicaciones. En ocasiones tendrán razón, pero si se trata de un framework de calidad, como es el caso de nHibernate, seguramente el problema radicará en el mal uso o en el desconocimiento del propio framework. Con nHibernate, los problemas más típicos que suelen florecer son:
- Al intentar acceder a ciertas propiedades de una entidad cargada se lanza una excepción del tipo LazyLoadingException.
- Persistencia extraña, a veces no se persisten datos o se duplican datos en memoria.
- La aplicación no funciona bien con más de un usuario simultáneo.
La mejor forma de combatir estos problemas es conocer el funcionamiento de la sesión de nHibernate. Para ello, vamos a ver algunas de sus principales características.
Por defecto, Lazy Load
El funcionamiento por defecto de nHibernate en cuanto a la carga de entidades asociadas a una entidad (sus relaciones con otras entidades) es mediante la carga baga. Esto significa que, si tengo una entidad “categoría” y esta tiene asociada una “categoría padre”, nHibernate cargará de la base de datos la categoría padre en el momento en el que intentemos acceder a una de sus propiedades. Para que esto sea posible es necesario mantener abierta la sesión que se usó para cargar la categoría padrea, de lo contrario recibiremos una excepción del tipo LazyLoadingException. Veamos un ejemplo:
[Fact]
public void LazyLoadConSessionCerradaNoFunciona()
{
ProductCategory category;
using (ISession session = SessionFactory.OpenSession())
{
category = session.Get<ProductCategory>(8);
}
Assert.Throws<LazyInitializationException>(() => category.ParentProductCategory.Name);
}
Este test demuestra como nHibernate lanza la excepción si intentamos acceder a la categoría padre una vez la sesión ha sido cerrada.
Otra forma de trabajar con la carga de entidades relacionadas es mediante la carga temprana de las mismas (eager loading). La carga baga de entidades es un arma de doble filo ya que puedes tener problemas de rendimiento dependiendo de tu escenario. Puedes leer más sobre la carga temprada de entidades (eager loading) con nHibernate en mi anterior post.
La caché de la sesión
Cada sesión de nHibernate que abras, tiene su propia cache de objetos. Si un objeto es cargado por una sesión, la próxima vez que se intente cargar ese mismo objeto en la sesión se obtendrá de la caché, sin que sea lanzada una consulta a la base de datos. Sólo hay una instancia de un objeto para una misma entidad en una misma sesión en memoria. En el caso que intente cargar esa misma entidad, pero en otra sesión, si que se realizaría una nueva consulta a la base de datos y tendría en memoria dos objetos de la misma entidad, pero cada uno en una sesión diferente.
Este aspecto es muy importante tenerlo en cuenta suele ser el principal quebradero de cabeza con nHibernate cuando no se aplica la estrategia de uso de la sesión adecuada a tu aplicación. Nelo Pauselli realizó un webcast sobre las diferentes estrategias de uso de la sesión en diferentes entornos como son las aplicaciones Windows Forms, WPF, WCF, Servicios, ASP.NET y ASP.NET MVC.
Vamos a ver unos ejemplos de código para entender mejor esta característica:
[Fact]
public void MismaEntidadPadreEnSesionesDistintasSonDiferentes()
{
ProductCategory category1;
ProductCategory category2;
using (ISession session = SessionFactory.OpenSession())
{
category1 = session.Get<ProductCategory>(8);
}
using (ISession session = SessionFactory.OpenSession())
{
category2 = session.Get<ProductCategory>(8);
}
Assert.NotEqual(category1, category2);
}
En este ejemplo, hemos cargado la misma entidad en diferentes sesiones de nHibernate y lo que obtenemos son dos objetos de la misma entidad en memoria. Cada uno de ellos se encuentra en la cache de una sesión diferente.
[Fact]
public void MismaEntidadHijaEnSesionesDistintasSonDiferentes()
{
//NOTA: La categoría 8 y 9 tienen el mismo padre asociado
ProductCategory parent1;
ProductCategory parent2;
using (ISession session = SessionFactory.OpenSession())
{
ProductCategory productCategory = session.Get<ProductCategory>(8);
parent1 = productCategory.ParentProductCategory;
}
using (ISession session = SessionFactory.OpenSession())
{
ProductCategory productCategory = session.Get<ProductCategory>(9);
parent2 = productCategory.ParentProductCategory;
}
Assert.NotEqual(parent1, parent2);
}
Aquí podemos observar como a la misma categoría padre de diferentes entidades en diferentes sesiones también obtenemos dos objetos de la misma entidad en memoria.
En cambio, si escribimos este mismo ejemplo pero usando para la carga la misma sesión, el resultado es diferente:
[Fact]
public void MismaEntidadHijaEnMismaSesionSonIguales()
{
//NOTA: La categoría 8 y 9 tienen el mismo padre asociado
ProductCategory parent1;
ProductCategory parent2;
using (ISession session = SessionFactory.OpenSession())
{
ProductCategory productCategory = session.Get<ProductCategory>(8);
parent1 = productCategory.ParentProductCategory;
ProductCategory productCategory2 = session.Get<ProductCategory>(9);
parent2 = productCategory2.ParentProductCategory;
}
Assert.Equal(parent1, parent2);
}
En este caso, sí que obtenemos la misma referencia al objeto de la categoría padre.
Una sola sesión para toda la aplicación, generalmente mala idea
En este momento se te puede estar ocurriendo que la solución al problema de las caches y las sesiones pueda ser usar una única sesión para toda mi aplicación. De esta forma me olvido de tener varios objetos de una misma entidad en memoria, por ejemplo. Generalmente no es la solución correcta para una aplicación por muchos motivos. Veamos un ejemplo de lo que nos puede ocurrir:
[Fact]
public void EntidadModificadaPedidaEnLaMismaSesionDevuelveLaEntidadModificada()
{
ProductCategory category1Session1;
string category1Session1OriginalName;
ProductCategory category2Session1;
ProductCategory category3Session2;
using (ISession session1 = SessionFactory.OpenSession())
{
category1Session1 = session1.Get<ProductCategory>(8);
category1Session1OriginalName = category1Session1.Name;
category1Session1.Name = "new name";
category2Session1 = session1.Get<ProductCategory>(8);
}
using (ISession session2 = SessionFactory.OpenSession())
{
category3Session2 = session2.Get<ProductCategory>(8);
}
Assert.NotEqual(category1Session1OriginalName, category2Session1.Name);
Assert.Equal(category1Session1.Name, category2Session1.Name);
Assert.Equal(category1Session1, category2Session1);
Assert.Equal(category1Session1OriginalName, category3Session2.Name);
Assert.NotEqual(category1Session1, category3Session2);
}
Nos encontramos con dos sesiones. En la primera cargamos la categoría 8, le modificamos el nombre y la volvemos a cargar en otra variable. Como estamos en el ámbito de la misma sesión, en el primer intento de carga se consulta a la base de datos pero en el segundo intento, nHibernate nos devuelve en objeto de la caché. Nos encontramos con que tenemos dos variables (category1Session1 y category2Session1) que son referencias al mismo objeto en memoria, por lo que category2Session1 tiene como nombre “new name” y no el nombre original de la base de datos.
En cambio, la misma categoría cargada en la segunda sesión, si que realiza una nueva consulta a la base de datos y obtenemos un nuevo objeto de la entidad en memoria.
Este problema suele florecer en aplicaciones con usuarios concurrentes. Las estrategias del ciclo de vida de las sesiones más utilizadas en aplicaciones son:
- Session per request, para aplicaciones asp.net
- Session per action. para aplicaciones asp.net mvc
- Session per form, para aplicaciones windows forms y wpf
- Session per call, para aplicaciones de servicios
- Session per invoke, para servicios WCF
- Conversation per bussiness trasaction, para aplicaciones web y windows
Para más información sobre estas estrategias, te recomiendo que veas el webcast de Alt.Net Hispano.
Otras características de las sesiones a tener en cuenta
Cuando trabajes con nHibernate, ten en cuenta lo siguiente:
- Crear una nueva sesión de nHibernate no implica el abrir siempre una nueva conexión a la base de datos.
Este trabajo con el pool de conexiones lo gestiona internamente nHibernate. - La sesión de nHibernate no es Thread Safe.
Esto significa que no debes de compartir la sesión entre diferentes hilos de tu aplicación. Lo único que es Thread Safe es el SessionFactory. - Si una sesión lanza una excepción, se vuelve inestable y lo mejor es reemplazarla por una nueva.
No es recomendable seguir usando una sesión que ha generado una excepción. Imagínate si en una aplicación Windows Forms o WPF usaras una única sesión para toda tu aplicación. En cuanto la sesión lanzara una excepción de cualquier tipo (consulta SQL, problemas de conexión a la base de dato, etc), toda tu aplicación se volvería inestable hasta que se reiniciara o crearas una nueva sesión.
Carga temprana de colecciones (eager loading) con nHibernate
20 Dec 10
Unos de los aspectos que más me ha costado encontrar documentación ha sido sobre cómo realizar la carga temprana de colecciones hijas de una entidad. Hay escenarios en el que la carga vaga de entidades no es viable, ya sea porque la arquitectura física de tu aplicación no te lo permite, porque te ha emergido el típico problema de “SELECT N+1” o por cualquier otra razón de tu proyecto.
Partamos del siguiente escenario. Este escenario es una versión simplificada de la base de datos de pruebas AdventureWorks de Microsoft:

Vamos a ir viendo diferentes métodos para realizar la precarga de las entidades hijas utilizando MultiCriteria, Future<> y LINQ.
Si quieres usar HQL para realizar la precarga, puedes encontrar un ejemplo al respecto en el blog “The nHibernate FAQ“.
Hagamos una carga del producto con ID 707 y carguemos sus SalesOrdelDetails y su ProductModel usando ICriteria:
[Fact]
public void CargaTempranaConModeloYDetalleMetodo1()
{
using (ISession session = _sessionFactory.OpenSession())
{
ICriteria criteria = session.CreateCriteria<Product>();
Product product = criteria.Add(Restrictions.Eq("ProductId", 707))
.SetFetchMode("ProductModel", FetchMode.Eager)
.SetFetchMode("SalesOrderDetail", FetchMode.Eager)
.UniqueResult<Product>();
Assert.True(NHibernateUtil.IsInitialized(product.ProductModel));
Assert.True(NHibernateUtil.IsInitialized(product.SalesOrderDetail));
}
}
SetFechMode me permite carga de forma temprana la colección de elementos hijos de la propiedad definida. Lo que genera este método es un “LEFT OUTER JOIN” en la consulta SQL. La consulta generada es la siguiente:
SELECT ...
FROM SalesLT.[Product] this_
left outer join SalesOrderDetail salesorder2_ on this_.ProductId = salesorder2_.ProductId
left outer join [ProductModel] productmod3_ on this_.ProductModelID = productmod3_.ProductModelId
WHERE this_.ProductId = @p0;@p0 = 707
Puedes observar los dos “LEFT OUTER JOIN” generados, uno para el modelo y otro para el detalle de la venta.
Pero cuidado, si vamos a cargar más de un nivel de nuestra entidad como es en este ejemplo, el realizar la consulta con varios “LEFT OUTER JOIN” en la misma consulta nos puede generar una cantidad ingente de resultados en todas sus combinaciones por el producto cartesiano generado al unir las tablas. En este ejemplo sencillo no notaríamos merma de rendimiento ya que es muy simple, pero en escenarios más complejos puede ser un problema muy grave.
Carga temprana mediante un MultiCriteria
Para estos casos, nHibernate no da otros mecanismos para realizar la consulta. Uno de ellos es el MultiCriteria. En una misma consulta a la base de datos, se ejecutar dos SELECT, una para cada nivel. Veamos el código:
[Fact]
public void CargaTempranaConModeloYDetalleMetodo2()
{
using (ISession session = _sessionFactory.OpenSession())
{
DetachedCriteria criteria1 =
DetachedCriteria.For<Product>()
.Add(Restrictions.Eq("ProductId", 707))
.SetFetchMode("ProductModel", FetchMode.Eager);
DetachedCriteria criteria2 =
DetachedCriteria.For<Product>()
.Add(Restrictions.Eq("ProductId", 707))
.SetFetchMode("SalesOrderDetail", FetchMode.Eager);
IList result = session.CreateMultiCriteria()
.Add(criteria1)
.Add(criteria2)
.List();
Product product = (Product)((IList)result[0])[0];
Assert.True(NHibernateUtil.IsInitialized(product.ProductCategory));
Assert.True(NHibernateUtil.IsInitialized(product.ProductModel));
}
Creamos un objecto DetachedCriteria por cada nivel que queremos cargar, definiéndole dentro un FetchMode para el nivel.
Posteriormente creamos el MultiCriteria añadiéndolo los ICriteria creados y lo ejecutamos con el método List(). En este ejemplo, result contiene 2 elementos, una lista de productos del resultado de ejecutar el primer DetachedCriteria y otra lista de productos por ejecutar el segundo DetachedCriteria.
La sentencia SQL generada es la siguiente:
SELECT ... FROM [Product] this_ left outer join SalesLT.[ProductModel] productmod2_ on this_.ProductModelID=productmod2_.ProductModelId WHERE this_.ProductId = @p0; SELECT ... FROM [Product] this_ left outer join SalesLT.SalesOrderDetail salesorder2_ on this_.ProductId=salesorder2_.ProductId WHERE this_.ProductId = @p1 @p0 = 707, @p1 = 707
La asociación de objetos de cada consulta la realiza nHibernate en memoria.
Carga temprana mediante Future<>
nHibernate nos da otra alternativa para realizar la consulta en código, para ello deberemos de usar el método Future<>(), ahí va un ejemplo:
[Fact]
public void CargaTempranaDeCategoriaPadreYModeloMetodo2()
{
using (ISession session = _sessionFactory.OpenSession())
{
IEnumerable<Product> result =
session.CreateCriteria<Product>()
.Add(Restrictions.Eq("ProductId", 680))
.SetFetchMode("ProductModel", FetchMode.Eager)
.Future<Product>();
session.CreateCriteria<Product>()
.Add(Restrictions.Eq("ProductId", 680))
.SetFetchMode("SalesOrderDetail", FetchMode.Eager)
.Future<Product>();
IEnumerator<Product> productEnumerator = result.GetEnumerator();
Assert.True(productEnumerator.MoveNext());
Assert.True(NHibernateUtil.IsInitialized(productEnumerator.Current.SalesOrderDetail));
Assert.True(NHibernateUtil.IsInitialized(productEnumerator.Current.ProductModel));
Assert.False(productEnumerator.MoveNext());
}
}
Si te fijas, nos quedamos únicamente con el resultado de la primera consulta, ignorando el resto de consultas. Otra diferencia respecto al ejemplo anterior es que aquí se nos devolverá una colección IEnumerable<> en vez de IList<>
El producto cartesiano en la unión de diferentes tablas sólo es problemático cuando la relación es de one-to-many. Cuando la relación es de many-to-one, podemos crear un único Criteria con todos los FetchMode de tipo many-to-one juntos y un Criteria por cada relación one-to-many o many-to-many que queramos precargar.
Carga temprana mediante LINQ
nHibernate también soporta LINQ para realizar consultas:
[Fact]
public void LinqCargaTempranaDeCategoriaPadre()
{
using (ISession session = _sessionFactory.OpenSession())
{
session.Linq<Product>().QueryOptions.RegisterCustomAction(
criteria => criteria.SetResultTransformer(new DistinctRootEntityResultTransformer()));
session.Linq<Product>()
.Expand("ProductModel")
.Where(p => p.ProductId == 680)
.ToList();
IList<Product> result = session.Linq<Product>()
.Expand("SalesOrderDetail")
.Where(p => p.ProductId == 680)
.ToList();
Assert.Equal(1, result.Count);
Assert.True(NHibernateUtil.IsInitialized(result[0].SalesOrderDetail), "SalesOrderDetail no inicializado");
Assert.True(NHibernateUtil.IsInitialized(result[0].ProductModel), "ProductModel no inicializado");
}
}
En esta ocasión, si que se realizan dos conexiones a la base de datos, una por cada consulta.
Si unas nHibernate 3 en tu proyecto, una de las mejoras en las consultas LINQ que han añadido es que trae programados nuevos métodos para la carga temprana de entidades.
El aspecto de la sentencia LINQ con nHibernate 3 sería:
var products = session.Query<Product>()
.FetchMany(c => c.SalesOrderDetail)
.ThenFetchMany(o => o.ProductModel).ToList();
Puedes leer más al respecto en el blog de Mike Hadlow:
http://mikehadlow.blogspot.com/2010/08/nhibernate-linq-eager-fetching.html
Realiza peticiones web http o https con Python
17 Dec 10
Como muchas tareas en python, realizar peticiones web a una url es muy sencillo. Sólo tienes que usar la clase HTTPConnection o HTTPSConnection que se encuentra en el módulo httplib, dependiendo de si es una petición http o https.
Ejemplo de una petición POST a la url https://www.paypal.com/nvp/:
import httplib, urllib
connection = httplib.HTTPSConnection("www.paypal.com")
params = urllib.urlencode({"USER":"usuario",
"PWD":"12345"})
headers = {"Content-type": "application/x-www-form-urlencoded",
"Accept": "text/plain"}
connection.request("POST", "/nvp", params, headers)
response = connection.getresponse()
data = response.read()
connection.close()
La petición comienza al llamar al método request pasándole el tipo de petición (POST, GET, HEAD, PUT), la página de la petición, los parámetros y las cabezeras. Los parámetros y las cabeceras son opciones. La cabezera “Content-length” la añade automáticamente python.
Para obtener la respuesta debemos de obtener el objeto HTTPResponse llamando al método getresponse() de la conexión que contiene toda la información referente a la respuesta. Desde este objeto podremos obtener el string de la respuesta de la petición.
El método urlencode te permitirá formatear la cadena de parámetros de una forma sencilla, despreocupándote de concatenarlos y codificarlos.
Un ejemplo de una petición GET:
import httplib
connection = httplib.HTTPConnection("www.rubenbernardez.com")
connection.request("GET","/blog/")
response = connection.getresponse()
data = response.read()
connection.close()
!Hola WordPress! Comenzando mi etapa bloguera
16 Dec 10
2 horas para elegir hosting, 20 min. para instalar wordpress, de los cuales fueron 15 subiendo por ftp los ficheros (si, si, no hace falta más!), unas horas para elegir un skin (lo admito, me cuesta decidirme), otras tantas cacharreando con esta fantástica herramienta y demasiado tiempo para decidirme a comenzar a escribir… Pero al fin lo he hecho, sólo me hacía falta un pequeño empujón (gracias @CarlosBle
).
Aún me quedan aspectos por configurar, plugins por probar, widgets por cacharrear, pero creo que ya se le puede dar el visto bueno y comenzar a escribir un poco.
Lo que tengo en mente ir publicando principalmente en este blog son aquellos aspectos técnicos y metodológicos interesantes que vaya aprendiendo a lo largo de mi carrera profesional. Una de mezcla de cuaderno de bitácora y bloc de notas personal que me sirva como referencia futura de lo aprendido y que si además consigo que le aporte algo positivo aunque sea a una sola persona, me sentiré plenamente satisfecho
Te animo a que participes con tus comentarios y pongamos en común diferentes puntos de vista y opiniones enriqueciendo el tema.
¡Nos vemos!