I'm using the second level cache provider SysCache2 with Fluent NHibernate, using a standard fluent configuration (with query cache enabled as seems to be the general advice), and having table based dependencies defined in the usual manner.
Fluent:
config.Cache(c => c
.ProviderClass<NHibernate.Caches.SysCache2.SysCacheProvider>()
.UseQueryCache()
.UseSecondLevelCache()
.UseMinimalPuts()
)
Web.Config:
<cacheRegion name="User" relativeExpiration="7200">
<dependencies>
<tables>
<add name="User" databaseEntryName="OldClient" tableName="tbl_users" />
</tables>
</dependencies>
User Mapping:
public class UserMap : ClassMap<User>
{
public UserMap()
{
Table("Users");
....
// caching
Cache.IncludeAll().ReadWrite().Region("Users");
}
}
Everything runs as expected for single requests, ie:
Session<User>.Get()
as would be expected from the way that NHibernate caches by ID. Subsequent requests don't hit the database, and invalidating the record in the database causes the Entity to be invalidated, resulting in a subsequent SQL call during the next request. All ok.
The problem is with query caching. In the first instance, everything works fine. Executing a call such as:
Session<User>.Query(item => item.Active == true)
results in a SQL call as you would expect (SELECT * FROM Users WHERE Active = true). And subsequent executions result in no SQL. Great. Until such a time when a single record from the query set is changed in the database. Executing the same query then results in a SELECT N+1:
SELECT * FROM Users WHERE ID = 1
SELECT * FROM Users WHERE ID = 2
SELECT * FROM Users WHERE ID = 3
SELECT * FROM Users WHERE ID = 4
...
I've found references to this elsewhere, although no solutions:
StackOverflow - How do I make NHibernate cache fetched child collections? See the 'one more point' section
Ayende Caching Strategies he mentions ensuring 'entities are also cached' at the end
The only way I can avoid this at the moment is to clear the query cache after every request - which almost renders it useless. What I need to for the query cache to clear everything for a particular Entity when that entity is invalidated - not just the individual records.
Any ideas?