2009-03-10 17 views
12

He estado usando NHibernate desde hace un tiempo y he encontrado de vez en cuando que si trato de solicitar dos páginas simultáneamente (o lo más cerca que pueda) ocasionalmente se produce un error. Así que asumí que era porque mi gestión de sesión no era segura para subprocesos.NHibernate thread safety con la sesión

Pensé que era mi clase, así que traté de usar un método diferente de esta publicación de blog http://pwigle.wordpress.com/2008/11/21/nhibernate-session-handling-in-aspnet-the-easy-way/, sin embargo, sigo teniendo los mismos problemas. El error real que estoy recibiendo es:

Server Error in '/AvvioCMS' Application. 
failed to lazily initialize a collection, no session or session was closed 
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: NHibernate.LazyInitializationException: failed to lazily initialize a collection, no session or session was closed 

O eso, o ninguna datareader está abierto, pero este es el principal culpable.

He colocado mi clase de gestión de sesión a continuación, ¿alguien puede detectar por qué puedo tener estos problemas?

public interface IUnitOfWorkDataStore 
{ 
    object this[string key] { get; set; } 
} 


    public static Configuration Init(IUnitOfWorkDataStore storage, Assembly[] assemblies) 
    { 
     if (storage == null) 
      throw new Exception("storage mechanism was null but must be provided"); 

     Configuration cfg = ConfigureNHibernate(string.Empty); 
     foreach (Assembly assembly in assemblies) 
     { 
      cfg.AddMappingsFromAssembly(assembly); 
     } 

     SessionFactory = cfg.BuildSessionFactory(); 
     ContextDataStore = storage; 

     return cfg; 
    } 

    public static ISessionFactory SessionFactory { get; set; } 
    public static ISession StoredSession 
    { 
     get 
     { 
      return (ISession)ContextDataStore[NHibernateSession.CDS_NHibernateSession]; 
     } 
     set 
     { 
      ContextDataStore[NHibernateSession.CDS_NHibernateSession] = value; 
     } 
    } 

    public const string CDS_NHibernateSession = "NHibernateSession"; 
    public const string CDS_IDbConnection = "IDbConnection"; 

    public static IUnitOfWorkDataStore ContextDataStore { get; set; } 

    private static object locker = new object(); 
    public static ISession Current 
    { 
     get 
     { 
      ISession session = StoredSession; 

      if (session == null) 
      { 
       lock (locker) 
       { 
        if (DBConnection != null) 
         session = SessionFactory.OpenSession(DBConnection); 
        else 
         session = SessionFactory.OpenSession(); 

        StoredSession = session; 
       } 
      } 

      return session; 
     } 
     set 
     { 
      StoredSession = value; 
     } 
    } 

    public static IDbConnection DBConnection 
    { 
     get 
     { 
      return (IDbConnection)ContextDataStore[NHibernateSession.CDS_IDbConnection]; 
     } 
     set 
     { 
      ContextDataStore[NHibernateSession.CDS_IDbConnection] = value; 
     } 
    } 

} 

Y la tienda real que estoy usando es el siguiente:

public class HttpContextDataStore : IUnitOfWorkDataStore 
{ 
    public object this[string key] 
    { 
     get { return HttpContext.Current.Items[key]; } 
     set { HttpContext.Current.Items[key] = value; } 
    } 
} 

Me inicializar la SessionFactory en Application_Start con:

NHibernateSession.Init(new HttpContextDataStore(), new Assembly[] { 
       typeof(MappedClass).Assembly}); 

Actualizar

Hola chicos, gracias por su consejo, he intentado algunos diferentes cosas para intentar y simplificar el código, pero todavía me encuentro con los mismos problemas y puedo tener una idea de por qué.

Creo la sesión cada vez que la solicito, pero en mi archivo global.asax dispongo de la sesión en Application_EndRequest. Sin embargo, estoy descubriendo que Application_EndRequest se activa más de una vez mientras estoy en depuración al finalizar la carga de una página. Pensé que el evento solo debía disparar una vez al final de la solicitud, pero si no es así y algunos otros elementos están tratando de usar la sesión (que es de lo que se queja el error) por cualquier razón extraña que podría ser mi problema y la Sesión aún está libre de subprocesos; solo se está desechando a tiempo.

¿Alguien tiene alguna idea? Hice un google y vi que el servidor de desarrollo VS causa problemas como ese, pero lo estoy ejecutando a través de IIS.

Respuesta

24

Si bien no he visto toda la base de código o el problema que está tratando de resolver, es posible que deba replantearse cómo está utilizando NHibernate.Desde el documentation:

Debe observar las siguientes prácticas al crear NHibernate Sesiones:

  • Nunca crear más de un concurrente ISession o ITransaction instancia por conexión de base de datos.

  • Tenga mucho cuidado al crear más de una ISession por base de datos por transacción. La propia ISession realiza un seguimiento de las actualizaciones realizadas en los objetos cargados, por lo que una ISession diferente podría ver datos obsoletos.

  • La ISession es no threadsafe! Nunca acceda a la misma ISession en dos hilos simultáneos . ¡Una ISession es generalmente solo una unidad de trabajo individual!

Esto último es el más relevante (e importante en el caso de un entorno multiproceso) a lo que me refiero. Una ISession debe usarse una vez para una pequeña operación atómica y luego eliminarse. Además de la documentación:

Un ISessionFactory es un cara-a-crear, para las hebras objeto destinada a ser compartida por todos los hebras de la aplicación. Una ISession es un objeto económico, no seguro para la rosca que se debe usar una vez, para un solo proceso de negocio , y luego se descarta.

Combinando estas dos ideas, en lugar de almacenar la ISession, almacene la fábrica de la sesión, ya que es el objeto "grande". A continuación, puede emplear algo como SessionManager.GetSession() como un contenedor para recuperar la fábrica del almacén de sesiones y crear una instancia de una sesión y usarla para una sola operación.

El problema también es menos obvio en el contexto de una aplicación ASP.NET. Estás explorando de forma estática el objeto ISession, lo que significa que se comparte en el dominio de la aplicación. Si se crean dos solicitudes de página diferentes dentro de la vigencia de ese Dominio de aplicación y se ejecutan simultáneamente, ahora tiene dos páginas (diferentes subprocesos) que tocan la misma ISesión que es no segura.

Básicamente, en lugar de tratar de mantener una sesión el mayor tiempo posible, trate de deshacerse de ellos lo antes posible y vea si tiene mejores resultados.

EDIT:

Ok, puedo ver que usted está tratando de ir con esto. Parece que está intentando implementar el patrón Abrir sesión en vista, y hay un par de rutas diferentes que puede realizar:

Si agregar otro marco no es un problema, busque algo como Spring.NET. Es modular, por lo que no tiene que usar todo, solo puede usar el módulo auxiliar NHibernate.Es compatible con la sesión abierta en el patrón de vista. Documentación here (encabezado 21.2.10. "Gestión de la sesión web").

Si prefiere enrollar la suya, consulte este publicación del proyecto de código por Bill McCafferty: "NHibernate Best Practices". Hacia el final, describe la implementación del patrón a través de un IHttpModule personalizado. También he visto publicaciones en Internet para implementar el patrón sin un IHttpModule, pero eso podría ser lo que has estado intentando.

Mi patrón habitual (y quizás ya se haya salteado aquí) es utilizar primero un marco. Elimina muchos dolores de cabeza. Si es demasiado lento o no se ajusta a mis necesidades, trato de ajustar la configuración o personalizarla. Solo después de eso trato de rodar el mío, pero YMMV. :)

+0

Gracias stuart, solo estoy tratando de mantener la sesión durante todo el tiempo que la solicitud a la página está activa, lo que me hubiera parecido bastante corto. Puedo intentar crear una sesión para cada llamada a DB, pero eso parece un poco pesado para mí. –

+1

Ok, veo a dónde vas con esto. Ver editar. Sin embargo, como nota al margen, como mencioné al declarar que ISession es estático, significa que se mantendrá durante más tiempo que una sola solicitud de página (probablemente). –

+0

@StuartChilds ¿me puede apuntar a una implementación sin ningún IHttpModule? Además, ¿cómo gestiona las transacciones y descarga/cierra las sesiones? – Rahatur

2

No puedo estar seguro (ya que soy un tipo Hibernate de Java) en NHibernate pero en hibernación Los objetos Session no son seguros para subprocesos. Debe abrir y cerrar una sesión y nunca permitir que se salga del alcance del hilo actual.

Estoy seguro de que patrones como 'Vista de sesión abierta' se han implementado en .Net en alguna parte.

El otro aspecto interesante es cuando coloca una entidad de hibernación en la sesión. El problema aquí es que la sesión a la que está asociada se cerrará (o debería estar) en la finalización de la solicitud. Debe volver a conectar la entidad a la sesión nueva (hibernación) si desea navegar por cualquier asociación no cargada. Esto en sí mismo causa un problema nuevo si dos solicitudes intentan hacer esto al mismo tiempo, ya que algo explotará si intentas adjuntar una entidad a dos sesiones.

Espero que esto ayude. Gareth

1

El problema terminó siendo que mi biblioteca para la inversión de control no administraba los objetos que se creaban en el contexto HTTP correctamente, así que estaba obteniendo referencias para objetos que no deberían estar disponibles para ese contexto. Esto fue usando Ninject 1.0, una vez que actualicé a Ninject 2.0 (beta) el problema fue resuelto.