NHibernate и нечетное "сеанс закрыт!" ошибки

Примечание. Теперь, когда я набрал это, я должен извиниться за сверхдолжный вопрос, однако, я думаю, что весь представленный здесь код и информация имеют какое-то значение.


Хорошо, я получаю странные ошибки "Session Is Closed", в случайных точках моего приложения веб-форм ASP.NET. Однако сегодня это происходит в одном и том же месте снова и снова. Я почти уверен, что ничто не избавляет или не закрывает сессию в моем коде, так как биты кода, которые используются, хорошо содержатся вне всего остального кода, как вы увидите ниже.

Я также использую ninject как мой IOC, который может/не быть важным.

Итак, сначала мои классы SessionFactoryProvider и SessionProvider:


SessionFactoryProvider

public class SessionFactoryProvider : IDisposable
{
    ISessionFactory sessionFactory;

    public ISessionFactory GetSessionFactory()
    {
        if (sessionFactory == null)
            sessionFactory =
                Fluently.Configure()
                        .Database(
                            MsSqlConfiguration.MsSql2005.ConnectionString(p =>
                                p.FromConnectionStringWithKey("QoiSqlConnection")))
                        .Mappings(m =>
                            m.FluentMappings.AddFromAssemblyOf<JobMapping>())
                        .BuildSessionFactory();

        return sessionFactory;
    }

    public void Dispose()
    {
        if (sessionFactory != null)
            sessionFactory.Dispose();
    }
}

SessionProvider

public class SessionProvider : IDisposable
{
    ISessionFactory sessionFactory;
    ISession session;

    public SessionProvider(SessionFactoryProvider sessionFactoryProvider)
    {
        this.sessionFactory = sessionFactoryProvider.GetSessionFactory();
    }

    public ISession GetCurrentSession()
    {
        if (session == null)
            session = sessionFactory.OpenSession();

        return session;
    }

    public void Dispose()
    {
        if (session != null)
        {
            session.Dispose();                
        }
    }
}

Эти два класса связаны с Ninject следующим образом:

NHibernateModule

public class NHibernateModule : StandardModule
{        
    public override void Load()
    {
        Bind<SessionFactoryProvider>().ToSelf().Using<SingletonBehavior>();
        Bind<SessionProvider>().ToSelf().Using<OnePerRequestBehavior>();
    }
}

и насколько я могу сказать работу, как ожидалось.

Теперь мой BaseDao<T> класс:


BaseDao

public class BaseDao<T> : IDao<T> where T : EntityBase
{
    private SessionProvider sessionManager;
    protected ISession session { get { return sessionManager.GetCurrentSession(); } }

    public BaseDao(SessionProvider sessionManager)
    {
        this.sessionManager = sessionManager;
    }        

    public T GetBy(int id)
    {
        return session.Get<T>(id);
    }

    public void Save(T item)        
    {
        using (var transaction = session.BeginTransaction())
        {
            session.SaveOrUpdate(item);

            transaction.Commit();
        }
    }

    public void Delete(T item)
    {
        using (var transaction = session.BeginTransaction())
        {
            session.Delete(item);

            transaction.Commit();
        }
    }

    public IList<T> GetAll()
    {
        return session.CreateCriteria<T>().List<T>();
    }

    public IQueryable<T> Query()
    {
        return session.Linq<T>();
    }        
}

Что связано в Ninject так:


DaoModule

public class DaoModule : StandardModule
{
    public override void Load()
    {
        Bind(typeof(IDao<>)).To(typeof(BaseDao<>))
                            .Using<OnePerRequestBehavior>();
    }
}

Теперь веб-запрос, вызывающий это, когда я сохраняю объект, это произошло не до тех пор, пока я не внес некоторые изменения модели сегодня, однако изменения в моей модели не изменили код доступа к данным в любом случае. Хотя это изменило несколько сопоставлений NHibernate (я могу опубликовать их тоже, если кому-то интересно)

Насколько я могу судить, вызывается BaseDao<SomeClass>.Get, тогда вызывается BaseDao<SomeOtherClass>.Get, тогда вызывается BaseDao<TypeImTryingToSave>.Save.

это третий вызов в строке в Save()

using (var transaction = session.BeginTransaction())

который терпит неудачу с "Сессия закрыта!" или, скорее, исключение:

Session is closed!
Object name: 'ISession'.

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: System.ObjectDisposedException: Session is closed!
Object name: 'ISession'.

И действительно, после прокрутки на Debugger показано, что в третий раз сеанс запрашивается из SessionProvider, он действительно закрыт и не подключен.

Я подтвердил, что Dispose на моем SessionFactoryProvider и на моем SessionProvider вызывается в конце запроса, а не до того, как вызов Save сделан на моем Dao.

Итак, теперь я немного застрял. Несколько сообразительных моментов.

  • Я делаю что-то явно неправильно?
  • Неужели NHibernate закрывает сеансы без моего запроса?
  • Любые обходные пути или идеи о том, что я могу сделать?

Заранее спасибо

Ответы

Ответ 1

ASP.NET является многопоточным, поэтому доступ к ISession должен быть потокобезопасным. Предполагая, что вы используете сеанс за запрос, самый простой способ сделать это - использовать NHibernate для обработки контекстных сеансов.

Сначала настройте NHibernate для использования класса контекста веб-сеанса:

sessionFactory = Fluently.Configure()
    .Database(
        MsSqlConfiguration.MsSql2005.ConnectionString(p =>
            p.FromConnectionStringWithKey("QoiSqlConnection")))
    .Mappings(m => m.FluentMappings.AddFromAssemblyOf<JobMapping>())
    .ExposeConfiguration(x => x.SetProperty("current_session_context_class", "web")
    .BuildSessionFactory();

Затем используйте ISessionFactory.GetCurrentSession() для получения существующего сеанса или привяжите новый сеанс к factory, если он не существует. Ниже я собираюсь вырезать + вставить мой код для открытия и закрытия сеанса.

    public ISession GetContextSession()
    {
        var factory = GetFactory(); // GetFactory returns an ISessionFactory in my helper class
        ISession session;
        if (CurrentSessionContext.HasBind(factory))
        {
            session = factory.GetCurrentSession();
        }
        else
        {
            session = factory.OpenSession();
            CurrentSessionContext.Bind(session);
        }
        return session;
    }

    public void EndContextSession()
    {
        var factory = GetFactory();
        var session = CurrentSessionContext.Unbind(factory);
        if (session != null && session.IsOpen)
        {
            try
            {
                if (session.Transaction != null && session.Transaction.IsActive)
                {
                    session.Transaction.Rollback();
                    throw new Exception("Rolling back uncommited NHibernate transaction.");
                }
                session.Flush();
            }
            catch (Exception ex)
            {
                log.Error("SessionKey.EndContextSession", ex);
                throw;
            }
            finally
            {
                session.Close();
                session.Dispose();
            }
        }
    }        

Ответ 2

Я периодически сталкивался с Session Closed/ObjectDisposedExceptions при выполнении интеграционных тестов для проекта веб-API, над которым я работал.

Ни один из советов здесь не решил, поэтому я построил отладочную версию NHibernate, чтобы узнать больше, и я увидел, что он выполняет запрос linq, когда функция контроллера Get возвращалась с IEnumerable объектов ответа.

Оказалось, что наш репозиторий выполняет запрос linq и возвращает IEnumerable объектов домена, но никогда не вызывает ToList(), чтобы принудительно выполнить оценку запроса. Служба, в свою очередь, вернула IEnumerable, а контроллер завершил перечисление с помощью объектов ответа и вернул его.

Во время этого возврата запрос linq, наконец, был выполнен, но сеанс NHibernate в какой-то момент закрылся, поэтому он исключил исключение. Решение заключалось в том, чтобы мы всегда вызывали .ToList() в репозитории внутри используемого сеансового блока, прежде чем мы вернемся к службе.

Ответ 3

Я предлагаю вам установить точку останова на SessionImpl.Close/SessionImpl.Dispose и посмотреть, кто ее вызывает через трассировку стека. Вы также можете просто построить отладочную версию NH для себя и сделать то же самое.