NHibernate загружает один и тот же объект несколько раз - пожалуйста, помогите!
Я только что прочитал трассировку для одной из моих страниц ASP.NET и заметил, что пользователь страницы загружается из базы данных каждый раз, когда пользователь требуется. Так как каждый ISession
должен кешировать объекты, я действительно озадачен этим.
Логично, что проблема, безусловно, должна заключаться в одной из следующих двух вещей:
ISession
кеш не работает должным образом- Каждый раз, когда пользователь запрашивается, он загружается с использованием другого
ISession
Я предполагаю, что проблема № 2). Я использую Castle Windsor для управления жизненными циклами объектов, поэтому я разместил часть кода, который я использую на тот случай, если кто-то может помочь выявить проблему. Классы, которыми управляет Замок Виндзор:
MooseUserRepository
- класс репозитория для управления экземплярами MooseUser (т.е. в этом случае пользователь страницы)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
должен сделать это для меня.
Может ли кто-нибудь рискнуть догадаться о том, что идет не так?
4 ответа
Я понял, в чем проблема, и она довольно тонкая.
Я извлекаю пользователя с помощью следующего кода:
private MooseUser PageUser
{
get { return MooseUserRepository.Load(ApplicationSettings.UsernameFromWeb); }
}
ApplicationSettings.UsernameFromWeb
извлекает имя текущего пользователя в отношении ASP.NET Имя пользователя пользователя является естественным ключом для таблицы Users, но это не первичный ключ! Насколько я знаю, кэш первого уровня работает только для объектов, полученных по первичному ключу.
Редактировать: я решил эту проблему, создав свойство, которое загружает загруженного пользователя в HttpContext.Current.Items и проверяет его перед загрузкой в соответствии с этой статьей.
Вы заявляете следующее:
Логично, что проблема, безусловно, должна заключаться в одной из следующих двух вещей:
- Кэш ISession не работает должным образом
- Каждый раз, когда пользователь запрашивается, он загружается с помощью
другая сессия
Я думаю, что вы, возможно, путаете кэш первого (сессионного) уровня Nhibernate с кэшем второго уровня.
Сессии дешевы в изготовлении и выбрасывают. В веб-приложении вы обычно используете один сеанс на запрос. Как только вы получаете или загружаете объект в первый раз, он помещается в кэш первого уровня, который ограничен временем жизни сеанса. Когда сеанс закрывается и удаляется в конце запроса, у вас больше не будет доступа к объектам в кэше уровня сеанса. Действительно, каждый пользователь загружается разными сессиями - это совершенно нормально.
Кэш второго уровня ограничен временем жизни фабрики сеансов. Если кэш 2-го уровня включен, то после загрузки объекта по его первичному ключу он сохраняется в кэш-памяти 2-го уровня и может быть доступен для ВСЕХ сеансов без повторного обращения к базе данных, пока он не будет удален из кеша. Вам нужно будет явно включить кэширование для каждого объекта. Это поведение, которое вы ищете.
Дальнейшее чтение:
- https://web.archive.org/web/20110514214657/http://blogs.hibernatingrhinos.com/nhibernate/archive/2008/11/09/first-and-second-level-caching-in-nhibernate.aspx
- http://davybrion.com/blog/2009/02/quickly-setting-up-and-using-nhibernates-second-level-cache/
- https://web.archive.org/web/20150813192002/http://nhibernate.info/blog/2009/02/09/quickly-setting-up-and-using-nhibernate-s-second-level-cache.html
редактировать
Вам нужно будет выбрать Cache Provider из проекта NHContrib. Возможно, вам нужен SysCache2, который использует кэш Asp.Net, но вы можете использовать MemCached или Velocity или пару других, если хотите. Я также рекомендую вам попробовать Nhibernate Profiler. Я нашел это неоценимым в том, чтобы соваться под капотом и узнавать, чем занимается Nhibernate.
Вы можете использовать natural-id (NaturalId в Fluent NH) в своем отображении, чтобы обойти истечение срока действия кэша второго уровня только для этой ситуации.
Как вы заметили в своем вопросе, вы используете образ жизни PerWebRequest для хранилища, поэтому ISession (используемый хранилищем) будет воссоздан при каждом запросе. Я считаю, что это правильное поведение, ISession должен создаваться при каждом запросе, и каждая операция с NH должна выполняться.
Если вы хотите использовать одну ISession как синглтон, вы должны объявить образ жизни репозиториев как синглтон.
Я думаю, что в вашем приложении должен быть какой-то SessionProvider или SessionFactory, возможно, вы могли бы поработать над этим для одноэтапного сеанса.
НТН