NHibernate Caching
There are two levels of caching.
First level cache
First level cache is the session's identity map of entities - it is local to the session and destroyed with it. StatelessSessions don't use it.
session.Get and session.Load check the first level cache (Get will save a database hit).
Second level cache
Second level cache is common to all sessions (in asp.net terms, equivalent to Cache or Application objects). It doesn't cache the entities, only a map of ids and property values.
- You need a provider
- You need to explicitly turn it on.
- You need to explicitly mark which classes and queries are cached
Providers
- You need a provider. There is an inbuilt NHibernate.Cache.HashtableCacheProvider which is memory only.
- Download other caches from NHContrib.
- SysCache uses asp.net's Cache
- SysCache2 is Sqlserver only (but does allow SqlDependencies)
- For 32-bit 2-server web farms, NCache Express is free.
- There is an memcache and a velocity/AppFabric driver.
After you've referenced the provider, you need to configure it.
Configure second level cache=true
For Syscache and other providers, you have priorities 1-5 (low to high, 3 is normal) and can specify "regions" with different expirations.
In config xml:
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2" >
<session-factory>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="dialect">NHibernate.Dialect.MsSql2008Dialect</property>
<property name="connection.connection_string_name">Northwind</property>
<property name="current_session_context_class">web</property>
<property name="show_sql">true</property>
<!-- use caching -->
<property name="cache.provider_class">NHibernate.Caches.SysCache.SysCacheProvider, NHibernate.Caches.SysCache</property>
<property name="cache.use_second_level_cache">true</property>
<property name="cache.use_query_cache">true</property>
</session-factory>
</hibernate-configuration>
In Fluent NHibernate:
public static ISessionFactory FluentlyConfigure()
{
return Fluently.Configure()
//which database
.Database(
MsSqlConfiguration.MsSql2008
//connection string from app.config
.ConnectionString(
cs => cs.FromConnectionStringWithKey("Northwind"))
.ShowSql())
//2nd level cache
.Cache(
c => c.UseQueryCache()
.UseSecondLevelCache()
.ProviderClass<NHibernate.Caches.SysCache.SysCacheProvider>())
//find the mappings
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<CategoryMapping>())
.BuildSessionFactory();
}
Configure classes
You can cache an entire class (e.g. a reference class that seldom changes).
Caching can be read-only, read-write (NB: doesn't lock, doesn't work in a webfarm and only on transaction.Commit) and nonstrict-read-write (expect stale data)
For read-only caches, the classes should be mutable=false
Important: if the cached classes have lazy associations, you can easily get N+1 SQL being fired on every session. Either don't map the associations, or cache the entire object graph.
In hbm.xml
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="Northwind" assembly="Northwind">
<class name="Category" table="`Categories`" schema="`dbo`" mutable="false">
<!-- read-only, read-write, nonstrict-read-write -->
<cache usage="read-only" region="Hour" />
In Fluent NHibernate:
public CategoryMapping()
{
Table("Categories");
Cache.ReadOnly();
ReadOnly(); //mutable=false
Remember that use_second_level_cache=true only means session.Get and session.Load use the cache. For queries to use the cache, you must set use_query_cache=true.
Query caching
Common queries can also be cached (specifically, it stores the result ids- so you could easily have a select N+1 here if the class is not cached).
Remember to set use_query_cache=true
var activeProducts =
session.Query<Product>()
.Where(x => x.Discontinued == false)
.Cacheable();
var activeCriteria =
session.CreateCriteria<Product>()
.Add(Restrictions.Eq("Discontinued", false))
.SetCacheable(true)
.List<Product>();