NHibernate загружает один и тот же объект несколько раз - пожалуйста, помогите!

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

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

  1. Кеш ISession не работает должным образом
  2. Каждый раз, когда пользователя запрашивают, он загружается с использованием другого ISession

Предполагаю, что проблема номер 2). Я использую Castle Windsor для управления жизненными циклами объектов, поэтому я опубликовал часть кода, который использую, на случай, если кто-то может помочь обнаружить проблему. Классы, которыми управляет Castle Windsor, являются:

  1. MooseUserRepository - класс репозитория для управления экземплярами MooseUser (то есть пользователем страницы в данном случае)
  2. KctcUnitOfWork - обертка для ISession

MooseUserRepository имеет зависимость конструктора от KctcUnitOfWork, например:

public MooseUserRepository(IUnitOfWork unitOfWork)
    {

    }

Конфигурационный файл выглядит так:

<component id="KctcUnitOfWork" service="Kctc.BusinessLayer.Kctc.IUnitOfWork,Kctc.BusinessLayer" type="Kctc.NHibernate.Kctc.UnitOfWork,Kctc.NHibernate" lifestyle="PerWebRequest"/>
<component id="MooseUserRepository" service="Kctc.BusinessLayer.Kctc.Repositories.IMooseUserRepository,Kctc.BusinessLayer" type="Kctc.NHibernate.Kctc.Repositories.MooseUserRepository,Kctc.NHibernate" lifestyle="PerWebRequest"/>

Обратите внимание на PerWebRequest образ жизни.

Контейнер Castle Windsor - это просто статическое свойство своего рода служебного класса под названием Moose.Application, поэтому он всегда там:

private static IWindsorContainer _windsorContainer;

    public static IWindsorContainer WindsorContainer
    {
      get
      {
        if (_windsorContainer == null)
        {
          _windsorContainer = new WindsorContainer(new XmlInterpreter(HttpContext.Current.Server.MapPath("~/CastleWindsorConfiguration.xml")));
        }
        return _windsorContainer;
      }
    }

На самой странице есть экземпляр IMooseUserRepository, подобный этому:

private IMooseUserRepository _mooseUserRepository;
private IMooseUserRepository MooseUserRepository
  {
    get
    {
      if (_mooseUserRepository == null)
      {
        _mooseUserRepository = Moose.Application.WindsorContainer.Resolve<IMooseUserRepository>();
      }
      return _mooseUserRepository;
    }
  }

Доступ к пользователю страницы осуществляется через свойство, которое выглядит следующим образом:

private MooseUser PageUser
  {
    get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }}

Похоже, что эти последующие вызовы PageUser вызывают дублирование команд SQL:

txtSubject.Enabled = PageUser.CanHandleLegalWorks;
    ddlDue.Enabled = PageUser.CanHandleLegalWorks;

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

Может ли кто-нибудь рискнуть предположить, что происходит не так?


person David    schedule 07.01.2011    source источник
comment
Наследует ли вы каким-либо изменением свой класс от другого объекта? Опубликуйте свое отображение и класс сущности.   -  person jishi    schedule 07.01.2011
comment
в зависимости от параллелизма в вашем приложении вам следует позвонить по поводу кеширования или нет.   -  person Baz1nga    schedule 07.01.2011
comment
Запустите на нем NhProf и посмотрите, создается ли у вас несколько сеансов для каждого запроса - у него есть 30-дневная бесплатная пробная версия.   -  person DanB    schedule 10.01.2011
comment
Дэвид, не могли бы вы опубликовать, как выглядит Kctc.NHibernate.Kctc.UnitOfWork?   -  person DanB    schedule 10.01.2011


Ответы (4)


Вы заявляете следующее:

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

  1. Кеш ISession не работает должным образом
  2. Каждый раз, когда пользователя запрашивают, он загружается с использованием
    другого сеанса ISession.

Я думаю, вы можете спутать кеш первого (сеансового) уровня Nhibernate с кешем второго уровня.

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

Кэш второго уровня ограничен временем существования фабрики сеансов. Если кэш 2-го уровня включен, как только вы загружаете объект по его первичному ключу, он сохраняется в кеше 2-го уровня и может быть доступен для ВСЕХ сеансов без повторного обращения к базе данных, пока он не будет удален из кеша. Вам нужно будет явно включить кеширование для каждой сущности. Это то поведение, которое вы ищете.

Дальнейшее чтение:

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

Вам нужно будет выбрать поставщика кеша из проекта NHContrib. Возможно, вам понадобится SysCache2, который использует Asp .Net кеш, но вы можете использовать MemCached или Velocity или пару других, если хотите. Я также рекомендую вам попробовать Nhibernate Profiler. Я счел бесценным заглянуть под капот и узнать, что делает Nhibernate.

person DanB    schedule 07.01.2011
comment
Я определенно думаю о кеше первого уровня - хранилище кеша на ISession (веб-запрос). Я никогда не думал о настройке кеша второго уровня, но полагаю, что он может быть полезен для хранения пользовательских данных между запросами. (Я немного обеспокоен тем, что данные устареют.) Спасибо за мысль! - person David; 07.01.2011
comment
@ Дэвид: А, ладно. У вас возникают проблемы, когда в одном веб-запросе используются разные сеансы? - person DanB; 10.01.2011

Как вы заметили в своем вопросе, вы используете стиль жизни PerWebRequest для репозитория, поэтому ISession (используемый репозиторием) будет воссоздаваться при каждом запросе. Я считаю такое поведение правильным, ISession должен создаваться при каждом запросе, и каждая операция в NH должна выполняться.

Если вы хотите использовать один ISession как синглтон, вам следует объявить образ жизни репозиториев как синглтон.

Я думаю, у вас должен быть какой-то SessionProvider o SessionFactory в вашем приложении, возможно, вы могли бы поработать над этим для сеанса singleton.

HTH

person ema    schedule 07.01.2011
comment
Важно использовать PerWebRequest с ISession, иначе сеансы NHibernate разных пользователей будут перепутаны! Каждый раз, когда сеанс сбрасывается, вы не представляете, сколько данных пользователей сбрасывается или что они делают в процессе. - person David; 07.01.2011
comment
Вы действительно не хотите превращать сеанс в синглтон. - person DanB; 07.01.2011

Я понял, в чем проблема, и она довольно тонкая.

Я получаю пользователя с помощью следующего кода:

private MooseUser PageUser
  {
    get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }
}

ApplicationSettings.UsernameFromWeb извлекает имя текущего пользователя, если речь идет о ASP.NET. Имя пользователя - естественный ключ для таблицы Users, но не первичный ключ! Насколько мне известно, кеш первого уровня работает только для объектов, извлеченных по первичному ключу.

Изменить: Я решил эту проблему, создав свойство, которое заполняет загруженного пользователя в HttpContext.Current.Items и сначала проверяет его перед загрузкой согласно эта статья.

person David    schedule 07.01.2011
comment
Вы можете сохранить PK пользователей вместо имени пользователя в качестве имени пользователя в билете FormsAuth. - person DanB; 07.01.2011
comment
Спасибо за отличный совет, но в данном случае бесполезный. Я обычно использую аутентификацию NTLM для приложения (таким образом, имя пользователя - это имя пользователя для входа в Windows), но использую базу данных для хранения дополнительной информации о каждом пользователе. Таблица в базе данных использует суррогатный ключ. Я знаю, что могу создавать дополнительные свойства в AD, но не могу этого сделать по ряду других причин. - person David; 07.01.2011

Вы можете использовать natural-id (NaturalId в Fluent NH) в вашем сопоставлении, чтобы избежать истечения срока действия кеша второго уровня только в этой ситуации.

person Jamie Ide    schedule 07.01.2011
comment
Я не использую кеш второго уровня, но в противном случае это было бы фантастическим предложением. Почему natural-id делает объекты кешируемыми только во втором уровне кэша ??? - person David; 07.01.2011