Безопасность потока NHibernate с сеансом - PullRequest
12 голосов
/ 10 марта 2009

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

Я думал, что это был мой класс, поэтому я попытался использовать метод, отличный от этого сообщения в блоге http://pwigle.wordpress.com/2008/11/21/nhibernate-session-handling-in-aspnet-the-easy-way/, но у меня все еще возникают те же проблемы. Фактическая ошибка, которую я получаю:

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

Либо тот, либо нет хранилища данных, но это главный виновник.

Я разместил свой класс управления сессиями ниже, кто-нибудь может определить, почему у меня могут быть эти проблемы?

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;
        }
    }

}

А фактический магазин, которым я пользуюсь, таков:

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

Я инициализирую SessionFactory в Application_Start с:

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

Обновление

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

Я создаю сеанс для каждого запроса по мере необходимости, но в моем global.asax я удаляю сеанс в Application_EndRequest. Однако я обнаружил, что Application_EndRequest запускается более одного раза, пока я нахожусь в состоянии отладки в конце загрузки страницы. Я подумал, что событие должно запускаться только один раз в самом конце запроса, но если это не так, и некоторые другие элементы пытаются использовать Сессию (на что жалуется ошибка) по какой-то странной причине, которая могла бы быть моей проблемой, и Сессия все еще поточно-безопасна, она просто удаляется на ранний срок.

У кого-нибудь есть идеи? Я сделал Google и увидел, что сервер разработки VS вызывает подобные проблемы, но я запускаю его через IIS.

Ответы [ 3 ]

24 голосов
/ 10 марта 2009

Несмотря на то, что я не видел всю вашу кодовую базу или проблему, которую вы пытаетесь решить, может потребоваться переосмысление того, как вы используете NHibernate. Из документации :

Вы должны соблюдать следующее практики при создании NHibernate Сессии:

  • Никогда не создавайте более одного одновременного ISession или ITransaction экземпляр в подключение к базе данных.

  • Будьте предельно осторожны при создании более одной ISession для каждой базы данных за транзакцию. Сама сессия отслеживает обновления, сделанные для загруженных объекты, поэтому другая ISession может см. устаревшие данные.

  • Сеанс IS не потокобезопасен! Никогда получить доступ к одной и той же сессии в двух параллельные темы. ISession is обычно только единица работы !

Этот последний бит наиболее актуален (и важен в случае многопоточной среды) для того, что я говорю. ISession должен использоваться один раз для небольшой атомарной операции, а затем утилизироваться. Также из документации:

ISessionFactory - это дорогой в создании, потокобезопасный объект предназначен для общего пользования всеми темы приложений. ISession - это недорогой, не поточечный объект это должно быть использовано один раз, для одного бизнес-процесс, а затем отбрасывается.

Объединяя эти две идеи, вместо того, чтобы хранить саму сессию, сохраняйте фабрику сессий, поскольку это «большой» объект. Затем вы можете использовать что-то вроде SessionManager.GetSession () в качестве оболочки, чтобы получить фабрику из хранилища сеансов, создать экземпляр сеанса и использовать его для одной операции.

Эта проблема также менее очевидна в контексте приложения ASP.NET. Вы статически ограничиваете объект ISession, что означает, что он является общим для всего домена приложений. Если два разных запроса страницы создаются в течение времени жизни этого домена приложений и выполняются одновременно, теперь у вас есть две страницы (разные потоки), которые касаются одного сеанса IS, который является не безопасным.

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

EDIT:

Хорошо, я вижу, куда вы пытаетесь пойти с этим. Похоже, вы пытаетесь реализовать шаблон Open Session In View, и есть несколько различных маршрутов, по которым вы можете пойти по этому пути:

Если добавление другого фреймворка не является проблемой, обратите внимание на что-то вроде Spring.NET . Он модульный, поэтому вам не нужно использовать все это, вы можете просто использовать вспомогательный модуль NHibernate. Он поддерживает открытую сессию в виде шаблона. Документация здесь (заголовок 21.2.10. «Управление веб-сессиями»).

Если вы предпочитаете снимать свои собственные, ознакомьтесь с публикацией этого проекта Билла МакКафферти: "NHibernate Best Practices" . В конце он описывает реализацию шаблона через пользовательский модуль IHttpModule. Я также видел сообщения в Интернете о реализации шаблона без IHttpModule, но это может быть то, что вы пытались.

Мой обычный шаблон (и, возможно, вы уже здесь пропустили) - это сначала использовать фреймворк. Это устраняет много головных болей. Если он слишком медленный или не соответствует моим потребностям, я пытаюсь настроить или настроить его. Только после этого я пытаюсь накатить свой, но YMMV. :)

2 голосов
/ 10 марта 2009

Я не могу быть уверен (так как я являюсь парнем из Java Hibernate) в NHibernate, но в Hibernate объекты Session не являются поточно-ориентированными. Вы должны открывать и закрывать сеанс и никогда не позволять ему выходить за рамки текущего потока.

Я уверен, что такие шаблоны, как «Открыть сеанс», были где-то реализованы.

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

Надеюсь, это поможет. Gareth

1 голос
/ 20 июля 2009

Проблема закончилась тем, что моя библиотека для инверсии управления не управляла объектами, создаваемыми в контексте HTTP, правильно, поэтому я получал ссылки на объекты, которые не должны были быть доступны для этого контекста. Это было с использованием Ninject 1.0, после того как я обновился до Ninject 2.0 (бета), проблема была решена.

...