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

Как сделать дочерние коллекции кэша NHibernate?

У меня есть довольно простой запрос критериев, который извлекает дочерние коллекции, например:

var order = Session.CreateCriteria<Order>()
    .Add(Restrictions.Eq("Id", id))
    .SetFetchMode("Customer", FetchMode.Eager)
    .SetFetchMode("Products", FetchMode.Eager)
    .SetFetchMode("Products.Category", FetchMode.Eager)
    .SetCacheable(true)
    .UniqueResult<Order>();

Используя NH Prof, я проверил, что это делает только одну поездку в оба конца в базу данных (как и ожидалось) с холодным кешем; однако при последовательных запусках он извлекает из кэша только Order и затем обращается к базе данных с помощью SELECT (N + 1) для каждого дочернего объекта на графике, как в:

Cached query: SELECT ... FROM Order this_ left outer join Customer customer2 [...]
SELECT ... FROM Customer WHERE Id = 123;
SELECT ... FROM Products WHERE Id = 500;
SELECT ... FROM Products WHERE Id = 501;
...
SELECT ... FROM Categories WHERE Id = 3;

И так далее и так далее. Очевидно, что это не кеширование всего запроса или графика, а только корневой объект. Первая строка "кэшированного запроса" на самом деле имеет все условия join, которые она предположила, - она ​​определенно правильно кэширует запрос, а не сущности, видимо.

Я пробовал это, используя SysCache, SysCache2 и даже поставщики кэша HashTable, и я всегда, кажется, получаю такое же поведение (NH версия 3.2.0).

В Googling появился ряд древних проблем, таких как:

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

Я прочитал документацию nhibernate.info на SysCache и SysCache2, и, похоже, я ничего не теряю, Я пробовал добавлять строки cacheRegion в файл Web.config для всех таблиц, участвующих в запросе, но ничего не меняет (и AFAIK эти элементы просто являются недействительными для кеша, поэтому они не должны иметь значения, так или иначе).

Со всеми этими супер-старыми проблемами, которые все, кажется, исправлены/решены, я полагаю, что это, возможно, все еще может быть ошибкой в ​​NHibernate, это должно быть то, что я делаю неправильно. Но что?

Есть ли что-то особенное, что мне нужно сделать, комбинируя команды извлечения в NHibernate с кешем второго уровня? Что мне здесь не хватает?

4b9b3361

Ответ 1

Мне удалось понять это, поэтому другие люди могут, наконец, получить прямой ответ:

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

  • Кэш запросов отслеживает, какие объекты испускаются запросом. Он не кэширует весь набор результатов. Это эквивалентно выполнению Session.Load на ленивом носителе; он знает/ожидает, что он существует, но не отслеживает какую-либо другую информацию об этом, если специально не спросил, и в какой момент он фактически загрузит реальный объект.

  • Кэш второго уровня отслеживает фактические данные для каждого объекта. Когда NHibernate необходимо загрузить любой объект по его идентификатору (в силу Session.Load, Session.Get, lazy-load relationship, или, в случае выше, сущность "ссылка", эта часть кэшированного запроса), она будет сначала посмотрите в кеш второго уровня.

Конечно, это имеет смысл в ретроспективе, но это не так очевидно, когда вы слышите, что термины "кеш запросов" и "кеш второго уровня" используются почти взаимозаменяемо во многих местах.

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

1. Включить оба кэша

В конфигурации XML это означает добавление следующих двух строк:

<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache" >true</property>

В Fluent NHibernate это:

.Cache(c => c
    .UseQueryCache()
    .UseSecondLevelCache()
    .ProviderClass<SysCacheProvider>())

Обратите внимание на UseSecondLevelCache выше, потому что (во время этой публикации) это никогда, упомянутое на странице Fluent NHibernate wiki; существует несколько примеров включения кеша запросов, но не кеша второго уровня!

2. Включить кеширование для каждого объекта

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

В XML это делается внутри элемента <class>:

<cache usage="read-write"/>

В Fluent NHibernate (non-automap) это делается в конструкторе ClassMap или где бы вы не поместили остальную часть вашего кода отображения:

Cache.ReadWrite().Region("Configuration");

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

И что это. Это действительно не так сложно сделать, но на удивление трудно найти хороший, полный пример, особенно для FNH.


Один последний момент: естественным следствием этого является то, что он делает очень страшные стратегии соединения/выборки очень непредсказуемыми при использовании с кешем запросов. Очевидно, если NHibernate видит, что запрос кэшируется, он будет не прилагайте никаких усилий, чтобы проверить, если все или даже какие-либо из фактических объектов будут кэшированы. Это в значительной степени просто предполагает, что они есть, и пытается загрузить каждую из них по отдельности.

Это причина катастрофы SELECT N + 1; это не было бы большой сделкой, если бы NH заметил, что сущности не находятся в кэше второго уровня и просто выполняли запрос, как обычно, с помощью феттей и фьючерсов и так далее. Но это не так; вместо этого он пытается загрузить каждую сущность и ее отношения, а также ее подзадачи и ее под-отношения и т.д. по одному за раз.

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

Ответ 2

в кэшированном запросе хранятся только идентификаторы объектов, а не значения объекта. в кэшированном объекте кэшируются только идентификаторы связанных объектов. поэтому, если вы не кэшируете все вовлеченные объекты, а также отмечаете связанные объекты как кешированные, вы можете в итоге выбрать n + 1.