Подтвердить что ты не робот

Безопасность нитей NHibernate с сеансом

Я уже некоторое время использую 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

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

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

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.

4b9b3361

Ответ 1

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

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

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

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

  • ISession не является потокобезопасным! Никогда доступ к той же ISession в двух параллельные потоки. ISession - это как правило, только одна единица работы!

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

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

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

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

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

EDIT:

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

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

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

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

Ответ 2

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

Я уверен, что такие шаблоны, как "Open session view", были внедрены в .NET где-то.

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

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

Ответ 3

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