NHibernate, и странное "Сессия закрыта!" ошибки - PullRequest
18 голосов
/ 03 апреля 2010

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


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

Я также использую ninject в качестве МОК, что может / не может быть важным.

Хорошо, итак, сначала мои 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'.

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

Я проверил, что Dispose на моем SessionFactoryProvider и на SessionProvider вызываются в конце запроса, а не раньше, чем на моем Дао будет сделан вызов Save.

Так что теперь я немного застрял. Несколько вещей приходят на ум.

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

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

Ответы [ 3 ]

19 голосов
/ 03 апреля 2010

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

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

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(), чтобы получить существующий сеанс, или привязать новый сеанс к фабрике, если таковой не существует.Ниже я собираюсь вырезать + вставить свой код для открытия и закрытия сессии.

    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();
            }
        }
    }        
7 голосов
/ 11 декабря 2014

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

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

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

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

3 голосов
/ 03 апреля 2010

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

...