Pregunta

He estado usando NHibernate por un tiempo y he descubierto de vez en cuando que si trato de solicitar dos páginas simultáneamente (o lo más cerca posible) ocasionalmente se producirá un error. Así que supuse que era porque la administración de mi sesión no era segura para subprocesos.

Pensé que era mi clase, así que intenté usar un método diferente al 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 obtengo 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 ningún lector de datos está abierto, pero este es el principal culpable.

Coloqué mi clase de administración de sesión a continuación, ¿alguien puede ver 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 esta:

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

Inicializo la SessionFactory en Application_Start con:

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

Update

Hola chicos, gracias por su consejo, he intentado algunas cosas diferentes para intentar 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 por solicitud cuando sea necesaria, pero en mi global.asax estoy eliminando la sesión en Application_EndRequest. Sin embargo, descubro que Application_EndRequest se dispara más de una vez mientras estoy depurando al final de la carga de una página. Pensé que se suponía que el evento solo se dispararía 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 pueda sea ??mi problema y la sesión todavía es segura para subprocesos, solo se está eliminando antes de 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.

¿Fue útil?

Solución

Si bien no he visto toda su base de código o el problema que está tratando de resolver, podría ser necesario repensar cómo está usando NHibernate. De la documentación :

  

Debe observar lo siguiente   prácticas al crear NHibernate   Sesiones:

     
      
  • Nunca cree más de una concurrente   Instancia de ISession o ITransaction por   conexión a la base de datos.

  •   
  • Tenga mucho cuidado al crear   más de una sesión por base de datos   por transacción La ISesión misma   realiza un seguimiento de las actualizaciones realizadas a cargado   objetos, por lo que una sesión diferente podría   ver datos obsoletos.

  •   
  • ¡La sesión es no segura para el hilo! Nunca   acceder a la misma sesión en dos   hilos concurrentes. Una sesión es   por lo general, solo una unidad de trabajo única !

  •   

Ese último bit es el más relevante (e importante en el caso de un entorno multiproceso) a lo que estoy diciendo. Una ISession debe usarse una vez para una pequeña operación atómica y luego desecharse. También de la documentación:

  

Un ISessionFactory es un   costoso de crear, objeto seguro para subprocesos   destinado a ser compartido por todos   hilos de aplicación. Una sesión es un   Objeto económico y no seguro   que debe usarse una vez, para un solo   proceso de negocio, y luego descartados.

Combinando esas dos ideas, en lugar de almacenar la ISession en sí misma, almacene la fábrica de sesiones ya que esa es la "grande". objeto. Luego puede emplear algo como SessionManager.GetSession () como un contenedor para recuperar la fábrica del almacén de sesiones e instanciar una sesión y usarla para una operación.

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

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

EDITAR:

Ok, puedo ver a dónde intentas ir con esto. Parece que está tratando de implementar el patrón Abrir sesión en vista, y hay un par de rutas diferentes que puede tomar en eso:

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 aquí (encabezado 21.2.10. & Quot; Administración de sesión web " ;).

Si prefiere lanzar el suyo, consulte esta publicación de proyecto de código de Bill McCafferty: " Mejores prácticas de NHibernate " . 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 tal vez ya te has saltado aquí) es usar primero un framework. Elimina muchos dolores de cabeza. Si es demasiado lento o no se ajusta a mis necesidades, trato de modificar la configuración o personalizarla. Solo después de eso intento rodar el mío, pero YMMV. :)

Otros consejos

No puedo estar seguro (ya que soy un tipo de Hibernate de Java) en NHibernate pero en hibernate Los objetos de sesión no son seguros para subprocesos por diseño. Debe abrir y cerrar una sesión y nunca dejarla fuera 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 problema interesante es cuando pones una entidad de hibernación en la sesión. El problema aquí es que la sesión a la que está adjunto se cerrará (o debería estar) cuando finalice la solicitud. Debe volver a conectar la entidad a la nueva sesión (hibernación) si desea navegar por las asociaciones no cargadas. Esto en sí mismo causa un nuevo problema si dos solicitudes intentan hacer esto al mismo tiempo que algo explotará si intenta adjuntar una entidad a dos sesiones.

Espero que esto ayude. Gareth

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top