NH Request Per Session - "Сессия закрыта!" - PullRequest
8 голосов
/ 09 июля 2009

NHibernate Версия: 2.1

Я использую, как представляется, довольно стандартный подход HttpModule для реализации сеансов по запросу в приложении ASP.NET + NHibernate. Я пытаюсь использовать WebSessionContext, но, похоже, он работает неправильно. В частности, все отлично работает для первого запроса к приложению, но дополнительные запросы приводят к «Сессии закрыты!» исключение каждый раз, когда сеанс используется. Сброс пула приложений позволяет выполнить еще один запрос, а затем «Сессия закрыта!».

Есть несколько движущихся частей, но я не знаю достаточно о том, как контекст управляется, чтобы сузить его, так что ... вот и все!

В web.config:

<property name="current_session_context_class">
  NHibernate.Context.WebSessionContext, NHibernate
</property>

(я тоже пытался установить его на «сеть» с тем же результатом.)

Модуль, подтвержденный для правильной настройки:

public class NHibernateSessionModule : IHttpModule
{
    public void Dispose() { }

    public void Init(HttpApplication context)
    {
        Debug.WriteLine("NHibernateSessionModule.Init()");
        context.BeginRequest += context_BeginRequest;
        context.EndRequest += context_EndRequest;
    }

    void context_BeginRequest(object sender, EventArgs e)
    {
        Debug.WriteLine("NHibernateSessionModule.BeginRequest()");
        var session = NHibernateHelper.OpenSession();
        session.BeginTransaction();
        CurrentSessionContext.Bind(session);
    }

    void context_EndRequest(object sender, EventArgs e)
    {
        Debug.WriteLine("NHibernateSessionModule.EndRequest()");
        var session = NHibernateHelper.GetCurrentSession();
        if (session != null)
        {
            try
            {
                if (session.Transaction != null && session.Transaction.IsActive)
                    session.Transaction.Commit();
            }
            catch (Exception ex)
            {
                session.Transaction.Rollback();
                throw new ApplicationException("Error committing database transaction", ex);
            }
            finally
            {
                session.Close();
            }
        }
        CurrentSessionContext.Unbind(NHibernateHelper.SessionFactory);
    }
}

И мой маленький помощник:

public class NHibernateHelper
{
    public static readonly ISessionFactory SessionFactory;

    static NHibernateHelper()
    {
        try
        {
            Configuration cfg = new Configuration();
            cfg.AddAssembly(Assembly.GetCallingAssembly());
            SessionFactory = cfg.Configure().BuildSessionFactory();
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex);
            throw new ApplicationException("NHibernate initialization failed", ex);
        }
    }

    public static ISession GetCurrentSession()
    {
        return SessionFactory.GetCurrentSession();
    }

    public static ISession OpenSession()
    {
        return SessionFactory.OpenSession();
    }
}

Ответы [ 3 ]

2 голосов
/ 09 ноября 2009

Пример для NHibernate 1.2 (из NHibernate в действии) показывает, что открепление выполняется до закрытия.

Помогает ли это изменение порядка?

var session = NHibernateHelper.GetCurrentSession();
CurrentSessionContext.Unbind(NHibernateHelper.SessionFactory);
...
session.Close();
1 голос
/ 16 июля 2009

Я использую следующий менеджер сессий NHibernate. (Это было первоначально из статьи CodeProject, которую я изменил, чтобы она была немного более устойчивой.) В Global.asax нет инициализации, только через параметры конфигурации в web.config / hibernate.xml.cfg.

using System.Runtime.Remoting.Messaging;
using System.Web;
using NHibernate;
using NHibernate.Cache;
using NHibernate.Cfg;

/// <summary>
/// Handles creation and management of sessions and transactions.  It is a singleton because 
/// building the initial session factory is very expensive. Inspiration for this class came 
/// from Chapter 8 of Hibernate in Action by Bauer and King.  Although it is a sealed singleton
/// you can use TypeMock (http://www.typemock.com) for more flexible testing.
/// </summary>
public sealed class NHibernateSessionManager
{
    #region Thread-safe, lazy Singleton

    /// <summary>
    /// Gets an instance via a thread-safe, lazy singleton.
    /// </summary>
    /// <remarks>
    /// See http://www.yoda.arachsys.com/csharp/singleton.html for more details about its implementation.
    /// </remarks>
    public static NHibernateSessionManager Instance
    {
        get
        {
            return Nested.NHibernateSessionManager;
        }
    }

    /// <summary>
    /// Prevents a default instance of the NHibernateSessionManager class from being created.
    /// Initializes the NHibernate session factory upon instantiation.
    /// </summary>
    private NHibernateSessionManager()
    {
        this.InitSessionFactory();
    }

    /// <summary>
    /// Assists with ensuring thread-safe, lazy singleton
    /// </summary>
    private class Nested
    {
        private Nested()
        {
        }

        internal static readonly NHibernateSessionManager NHibernateSessionManager = new NHibernateSessionManager();
    }

    #endregion

    private void InitSessionFactory()
    {
        this.sessionFactory = new Configuration().Configure().BuildSessionFactory();
    }

    /// <summary>
    /// Allows you to register an interceptor on a new session.  This may not be called if there is already
    /// an open session attached to the HttpContext.  If you have an interceptor to be used, modify
    /// the HttpModule to call this before calling BeginTransaction().
    /// </summary>
    public static void RegisterInterceptor(IInterceptor interceptor)
    {
        ISession session = ContextSession;

        if (session != null && session.IsOpen)
        {
            throw new CacheException(new System.Resources.ResourceManager(typeof(NHibernateSessionManager)).GetString("RegisterInterceptor_CacheException"));
        }

        GetSession(interceptor);
    }

    /// <summary>
    /// Gets a session (without an interceptor). This method is not called directly; instead,
    /// it gets invoked from other public methods.
    /// </summary>
    /// <returns></returns>
    public static ISession GetSession()
    {
        return GetSession(null);
    }

    /// <summary>
    /// Gets a session with or without an interceptor. This method is not called directly; instead,
    /// it gets invoked from other public methods.
    /// </summary>
    /// <remarks>
    /// Throws <see cref="HibernateException"/> if a reference to a session could not be retrieved.
    /// </remarks>
    private static ISession GetSession(IInterceptor interceptor)
    {
        ISession session = ContextSession;

        if (session == null)
        {
            if (interceptor != null)
            {
                session = Instance.sessionFactory.OpenSession(interceptor);
            }
            else
            {
                session = Instance.sessionFactory.OpenSession();
            }

            ContextSession = session;
        }

        if (session == null)
        {
            throw new HibernateException("Session was null");
        }

        return session;
    }

    /// <summary>
    /// Flushes anything left in the session, committing changes as long as no <see cref="NHibernate.AssertionFailure">NHibernate.AssertionFailure's</see> are thrown.
    /// </summary>
    /// <exception cref="System.Data.SqlClient.SqlException"></exception>
    public static void FlushSession()
    {
        ISession session = ContextSession;

        if (session != null && session.IsOpen)
        {
            // Due to a bug in Hibernate (see http://forum.hibernate.org/viewtopic.php?p=2293664#2293664) make sure Flush() is wrapped in a transaction
            if (!HasOpenTransaction())
            {
                BeginTransaction();
            }

            try
            {
                session.Flush();
            }
            catch (NHibernate.AssertionFailure af)
            {
                if (af.Message == "null id in entry (don't flush the Session after an exception occurs)")
                {
                    System.Diagnostics.Trace.TraceError("NHibernate.AssertionFailure: " + af.Message);
                }
                else
                {
                    throw;
                }
            }
            CommitTransaction();
        }

        ContextSession = null;
    }

    /// <summary>
    /// Flushes anything left in the session and closes the connection.
    /// </summary>
    public static void CloseSession()
    {
        ISession session = ContextSession;

        if (session != null && session.IsOpen)
        {
            FlushSession();
            session.Close();
        }

        ContextSession = null;
    }

    /// <summary>
    /// Begin an ITransaction (if one is not already active)
    /// </summary>
    public static void BeginTransaction()
    {
        ITransaction transaction = ContextTransaction;

        if (transaction == null)
        {
            transaction = GetSession().BeginTransaction();
            ContextTransaction = transaction;
        }
    }

    /// <summary>
    /// Begin an ITransaction (if one is not already active)
    /// </summary>
    /// <param name="isolationLevel"></param>
    public static void BeginTransaction(System.Data.IsolationLevel isolationLevel)
    {
        ITransaction transaction = ContextTransaction;

        if (transaction == null)
        {
            transaction = GetSession().BeginTransaction(isolationLevel);
            ContextTransaction = transaction;
        }
    }

    /// <summary>
    /// Commit transaction, if a transaction is currently open. Automatic rollback if commit fails.
    /// </summary>
    public static void CommitTransaction()
    {
        ITransaction transaction = ContextTransaction;

        try
        {
            if (HasOpenTransaction())
            {
                try
                {
                    transaction.Commit();
                }
                catch (NHibernate.AssertionFailure af)
                {
                    if (af.Message == "null id in entry (don't flush the Session after an exception occurs)")
                    {
                        System.Diagnostics.Trace.TraceError("NHibernate.AssertionFailure: " + af.Message);
                    }
                    else
                    {
                        throw;
                    }
                }
                ContextTransaction = null;
            }
        }
        catch (HibernateException)
        {
            RollbackTransaction();
            throw;
        }
    }

    /// <summary>
    /// Checks for an open <see cref="ITransaction"/>.
    /// </summary>
    /// <returns></returns>
    public static bool HasOpenTransaction()
    {
        ITransaction transaction = ContextTransaction;

        return transaction != null && transaction.IsActive && !transaction.WasCommitted && !transaction.WasRolledBack;
    }

    /// <summary>
    /// Rollback transaction, closing the <see cref="ContextSession"/> if successful.
    /// </summary>
    public static void RollbackTransaction()
    {
        ITransaction transaction = ContextTransaction;

        try
        {
            if (HasOpenTransaction())
            {
                transaction.Rollback();
            }

            ContextTransaction = null;
        }
        finally
        {
            if (ContextSession != null)
            {
                ContextSession.Close();
                ContextSession = null;
            }
        }
    }

    /// <summary>
    /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
    /// specific <see cref="CallContext" />.  Discussion concerning this found at 
    /// http://forum.springframework.net/showthread.php?t=572.
    /// </summary>
    private static ITransaction ContextTransaction
    {
        // this should be here, but it starts a chain of having to mark this ALL over. So we're ignoring it here.
        // [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, SkipVerification = true)]
        get
        {
            if (IsInWebContext())
            {
                return (ITransaction)HttpContext.Current.Items[TRANSACTION_KEY];
            }
            else
            {
                return (ITransaction)CallContext.GetData(TRANSACTION_KEY);
            }
        }
        // this should be here, but it starts a chain of having to mark this ALL over. So we're ignoring it here.
        // [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, SkipVerification = true)]
        set
        {
            if (IsInWebContext())
            {
                HttpContext.Current.Items[TRANSACTION_KEY] = value;
            }
            else
            {
                CallContext.SetData(TRANSACTION_KEY, value);
            }
        }
    }

    /// <summary>
    /// If within a web context, this uses <see cref="HttpContext" /> instead of the WinForms 
    /// specific <see cref="CallContext" />.  Discussion concerning this found at 
    /// http://forum.springframework.net/showthread.php?t=572.
    /// </summary>
    private static ISession ContextSession
    {
        // [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, SkipVerification = true)]  // this should be here, but it starts a chain of having to mark this ALL over. So we're ignoring it here.
        get
        {
            if (IsInWebContext())
            {
                return (ISession)HttpContext.Current.Items[SESSION_KEY];
            }
            else
            {
                return (ISession)CallContext.GetData(SESSION_KEY);
            }
        }
        // [System.Security.Permissions.SecurityPermission(System.Security.Permissions.SecurityAction.LinkDemand, SkipVerification = true)]  // this should be here, but it starts a chain of having to mark this ALL over. So we're ignoring it here.
        set
        {
            if (IsInWebContext())
            {
                HttpContext.Current.Items[SESSION_KEY] = value;
            }
            else
            {
                CallContext.SetData(SESSION_KEY, value);
            }
        }
    }

    private static bool IsInWebContext()
    {
        return HttpContext.Current != null;
    }

    private const string TRANSACTION_KEY = "CONTEXT_TRANSACTION";
    private const string SESSION_KEY = "CONTEXT_SESSION";
    private ISessionFactory sessionFactory;
}
0 голосов
/ 10 октября 2009

Просто предположение, но что произойдет, если вы поместите свой CurrentSessionContext.Unbind в область finally, сразу после session.Close ()? Я не могу точно вспомнить, но я верю, что выполнение завершается после того, как будет завершен блок finally, поэтому, если это так, сеанс по-прежнему связан с контекстом и, следовательно, никогда не удаляется ..?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...