I've got a fairly simple criteria query that fetches child collections, like so:
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>();
Using NH Prof, I've verified that this makes just one round trip to the database (as expected) with a cold cache; however, on successive executions, it retrieves only the Order
from the cache and then hits the database with a SELECT(N+1) for every child entity in the graph, as in:
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;
And so on and so forth. Clearly it's not caching the whole query or graph, only the root entity. The first "cached query" line actually has all of the join
conditions that it's supposed to - it's definitely caching the query itself correctly, just not the entities, apparently.
I've tried this using the SysCache, SysCache2, and even HashTable cache providers and I always seem to get this same behaviour (NH version 3.2.0).
Googling turned up a number of ancient issues, such as:
- NH-195: Child collections are not being stored in the second level cache
- Syscache2 2nd level cache: Child coll. objects requeried
- Weird differences between SysCache and SysCache2
- NHibernate – Beware of inadvisably applied caching strategies (Ayende - of course he only bothers to mention what not to do, not how to fix it...)
However, these all seem to have been fixed a long time ago, and I get the same bad behaviour regardless of which provider I use.
I've read through the nhibernate.info documentation on SysCache and SysCache2 and there doesn't seem to be anything I'm missing. I've tried adding cacheRegion
lines to the Web.config
file for all tables involved in the query, but it doesn't change anything (and AFAIK those elements are just to invalidate the cache, so they shouldn't matter anyway).
With all of these super-old issues that all seem to be fixed/resolved, I figure this can't possibly still be a bug in NHibernate, it must be something that I'm doing wrong. But what?
Is there something special I need to do when combining fetch instructions in NHibernate with the second-level cache? What am I missing here?