Вопрос

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

Это было полезно?

Решение

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

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

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

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

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

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

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

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

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

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

Редактировать:

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

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

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

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

Другие советы

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

Я уверен, что такие шаблоны, как "Просмотр открытого сеанса", были реализованы где-то в .Net.

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top