Voy a intentar de explicar esta excepción con NHibernate, para que veamos como podemos llegar a reproducirla y que debemos hacer para poder evitarla y en el caso de no poder modificar mucho la arquitectura de la aplicación como podemos solucionar este problema haciendo uso de la implementación de los proxy con Castle.
Illegal attempt to associate a collection with two open sessions
Esta excepción sucede normalmente en aplicaciones de escritorio ya que las aplicaciones web se suele hacer uso del patrón OpenSessionInview, que simplifica mucho el manejo de las sesiones con NHibernate. Como explique anteriormente.
En el caso de aplicaciones de escritorio que la duración de una sesión viene determinada por la arquitectura que tengamos empleada y no por cada petición puede saltar la siguiente excepción al intentar guardar o eliminar un objeto de la bb.dd.
NHibernate.HibernateException: Illegal attempt to associate a collection with two open sessions.
Arquitectura recomendada
Voy a poner un ejemplo de arquitectura que nos solventaría estos problemas y recomiendo para el uso de aplicaciones de escritorio donde el manejo de sesiones sea un poco mas peliagudo.
Me gusta tener la capa de dominio aislada porque esto me permite que mi capa de datos sea totalmente abstracta haciendo uso de Generics y ajena al negocio con lo cual la puede tener como una librería independiente que asocio a mi aplicación cuando quiero hacer uso de ella.
Mi capa de Negocio es la cual contiene toda la lógica característica del desarrollo. y en la cual definiré mis unidades básicas de trabajo o “Servicios”. Cada uno de estos servicios serán los encargados de hacer uso de la capa de datos y del dominio y pasar las validaciones devolver consultas especificas… Puede ser que un servicio tenga muchos métodos y se vuelva algo inmanejable pero para ello hago uso de las clases parciales dividiéndola en varios archivos, uno para consultas, otro validaciones…Esto cada uno como quiera.
Bien lo mas importante de estos servicios es saber acotarlos bien ya que estos serán nuestras Unit Of Work. Cada uno de ellos manejara una sesión y para guardar datos haciendo uso de varios servicios estos tendrán llamadas entre si.
public Entity1 Save(Entity1 entity) { ServiceFactory.OtherService.DoSomething(entity.Collection); return GenericDao.SaveOrUpdate<Entity1>(entity); }
Como habéis podido ver esto nos permite ver claramente las transacciones como en el caso que ambas sentencias. Las cuales se harían dentro de una misma transacción.
Ademas como habéis podido ver el acceso a los servicios lo hago a través de una factoría que se encarga de que cada servicio sea instanciado una sola vez mejorando el uso de caches.
Por ultimo la capa de presentación se encargaría de hacer llamadas a nuestros servicios y mostrar los datos. Lo cual nos facilitaría el cambio de una presentación a otra ya sea web, wpf, windows forms. Ya que toda la lógica estaría bien aislada.
Saliendo del paso con Castle
Si estamos en un avanzado estado del proyecto, no podremos tocar mucho la arquitectura del mismo, pero si podemos evitar esta excepción, haciendo uso del ByteCodeProvider de Castle.
Para ello en la configuración hay que indicarle a NHibernate que los proxy los manejara Castle indicando en su variable:
Proxyfactory.Factory_Class, que haga uso de NHibernate.ByteCode.Castle.ProxyFactoryFactory, NHibernate.ByteCode.Castle
También será necesario hacer referencia a las siguientes DLLs en el proyecto
- Castle.Core
- Castle.DinamicProxy2
- NHibernate.ByteCode.Castle
Por último en nuestra capa de datos obtendremos el proxy de nuestra entidad a persistir y si tiene una sesión activa haremos un Evict de esta para asociarla a la nueva sesión y evitar el error de la siguiente forma:
<Transaction(TransactionPropagation.Required, IsolationLevel.RepeatableRead)> _ Public Overloads Sub Delete(Of T)(ByVal entity As T) Implements IGenericDao.Delete Try 'En el caso de que haya sido leído con otra sesión esta queda asociada al objeto 'por eso debemos hacer un detach de esta y ya podemos eliminar normalmente- If entity.GetType().GetProperty("HibernateLazyInitializer") IsNot Nothing Then Dim proxy As NHibernate.Bytecode.Castle.LazyInitializer =
entity.GetType().GetProperty("HibernateLazyInitializer").GetValue(entity, Nothing) proxy.Session.CloseSessionFromDistributedTransaction() End If HibernateTemplate.Delete(entity) Catch ex As Exception Throw ex End Try End Sub
Buenos y esto es todo, con esto podremos salir del paso en el caso que nos de la molesta excepción :
Illegal attempt to associate a collection with two open sessions
PD: Los gráficos los he hecho haciendo uso de Gliffy, una gran pagina que me recomendó un amigo. Ya que con el VS2010 aun no me atrevo 🙂
[tweetmeme source=”mingue” only_single=false] |