Table of Contents
1. Introduction
In this article, I explain how NHibernate 2nd level cache is implemented. As an Object Relational Mapper (ORM) when NHibernate is used often we see developers try to improve performance using NHibernate caching feature. In this article I focus on this area where 2nd level cache plays rule for better performance. Second level cache will help to overcome 1st level cache limitations. Anyone who are also interested to know how 2nd level caching can be implemented in any project, this article is for them too .
2. Prerequisite
- NHibernate basics
- 1st level caching
3. Background
Entity level caching is a very important technique and it improves performance of application. Occasionally we introduce caching layer in n-layer architecture which exists just before data access layer. The main responsibility of this layer is cache business and supply business entities to the business layer from its cache when business layer demands.
Sometimes we cache entities in web context using ASP.NET web components like:
- ViewState,
- Session,
- Application,
- Cache object
When anyone use NHibernate and its 2nd level caching feature then further it will not be needed to cache entities in other layers. NHibernate provides various caching and its expiration policy. I try to explain it in a very easy way so that any one can easily use it in their application.
4. Prerequisite Components
If anyone wants to use NHibernet in his applications, then few components must be needed and he should add as a reference of those components to his projects. Components are:
-
NHibernate
-
Lesi.Collections
If you prefer fluent configuration (I prefer fluent mapping configuration over xml mapping configuration), then you can take reference of another:
FluentNHibernate
These are the core components of NHibernate.
5. 2nd Level Cache Providers
If I want to use 2nd level cache feature, at first I need to add at least one 2nd level cache provider reference to my project.
Many cache providers are exists in market. Few popular providers are:
MemCache
Velocity
SysCache
SysCache2
Hash Table
I prefer
SysCache2
because it supports database change notification. If your applications need distributed cache, then you can check with Velocity. For adding
SysCache2
component reference to my project, I prefer
NuGet package
cmd lets. You can use:
Install-Package NHibernate.Caches.SysCache2
cmd let from Visual Studio's Package Manager window. It will download/add reference syscache2 assembly to your projects.
Note that it will not add reference of FluentNHibernate
component. You manually need to add that.
6. Configuration
After adding component reference you need to configure your
- Database and
- Application
so that it can work perfectly.
6.1. Database Configuration
If you want to take database change notification service then you need to setup SQL Server 2005 or higher version (I found various online sources there said SqlServer 2000 also supports it, but never tried that). Next, we need to enable SQL Server Service Broker feature. TSQL statement is:
ALTER DATABASE <databasename> SET ENABLE_BROKER;
Note that, you can enable service broker feature in a database for only once. We can also disable service broker feature at any time. TSQL statement is:
ALTER DATABASE <databasename> SET DISABLE_BROKER;
Next, you need to use aspnet_regsql
tool.
You need to execute command from Visual Studio command prompt:
aspnet_regsql –S <sqlserver instance name> -U <database username>
-P <database user password> -d <database name> -ed
If you execute aspnet_regsql /? command from Visual Studio command prompt, then you know the details of this command and its options.
If you first open SQL Server Profiler, then execute the above tsql statement, then you will see that it creates AspNet_SqlCacheTablesForChangeNotification
table with 5 stored procedures(SP).
Procedures are:
AspNet_SqlCachePollingStoredProcedure
AspNet_SqlCacheRegisterTableStoredProcedure
AspNet_SqlCacheUpdateChangeIdStoredProcedure
AspNet_SqlCacheUnRegisterTableStoredProcedure
AspNet_SqlCacheQueryRegisteredTablesStoredProcedure
and also it will create one database user role named aspnet_ChangeNotification_ReceiveNotificationsOnlyAccess
Next, you need to register tables that you want to allow change notifications.
The command is:
Aspnet_regsql –S <sqlserver instance name> -U <database username>
-P <database user password> -d <database name> -t <tablename> -et <sql cache dependency tables>
You can create a single batch (.bat) file with all the necessary commands listed above. It will make your life more easier.
6.2. Application Configuration
You may think that NHibernate 2nd level cache is worked with only in web context. But it is not true. You can easily use that feature in your Console, Windows Service application, Windows Application too.
In your configuration file (if web application then it will be web.config, if other then web then it will be app.config) you need to register syscache2
section inside <configSections>
node:
<configSections>
<section name="syscache2" type="NHibernate.Caches.SysCache2.SysCacheSection,
NHibernate.Caches.SysCache2" requirePermission="false"/>
</configSections>
then you need to register sqlCacheDependency
inside <system.web>
node. If you use App.config, then you need to add <system.web>
node under <configuration>
node and also add a reference to the System.Web
component. SqlCacheDependency
object actually resides in this component and it is .NET Framework component. For that reason it will work in your application that does not have any web context.
<system.web>
<caching>
<sqlCacheDependency enabled="true" pollTime="1000">
<databases>
<add name="db2" connectionStringName="db"/>
</databases>
</sqlCacheDependency>
</caching>
</system.web>
Q. Why do we need to register sqlCacheDependency
?
A. Actually syscache2
expires its cache based on database change notification (Well, not always but, only when you configure the "regions
" that way). SqlCacheDependency
object is actually manage that and SysCache2
use it.
One important attribute is pollTime
(in milliseconds). The value of pollTime indicate time interval when it visit database periodically to check if there are any changes.
SysCache2
region registers in the web/app config. <syscache2>
node will be registered under <configuration>
node.
<syscache2>
<cacheRegion name="tableDependency" priority="High">
<dependencies>
<tables>
<add name="one" databaseEntryName="db2" tableName="MyTable1" />
</tables>
</dependencies>
</cacheRegion>
</syscache2>
You can see that there is a node "cacheRegion
" named “tableDependency
”. CacheRegion
is actually an independent cache expiration policy. From our Entity Mapping configuration, we can refer to that policy. databaseEntryName
attribute of <tables>
node refer to <add name=db2>
under <databases>
& <sqlDependency>
node. This databaseEntryName
attribute sometimes creates confusion and that is, what will be its value. So carefully, we should set that value.
Another <cacheRegion>
is registered for cache expiration on certain time:
<syscache2>
<cacheRegion name="ExpireAfterCertainTime" timeOfDayExpiration="22:25:00" priority="High"/>
</syscache2>
The above region reference cache will be expire on 08:00:00 PM. The attribute value should be 24 hour time format.
Another <cacheRegion>
is registered for cache expiration on certain time interval.
<syscache2>
<cacheRegion name="FiveSecondTimeInterval" relativeExpiration="5" priority="High"/>
</syscache2>
The above region reference cache will be stored in cache in 5 seconds. After that period, it will automatically expire.
7. NHibernate Session
In NHibernate, we need to create a SessionFactory
class. Using that class we can open a new session and start communicating with database. Create SessionFactory
object is costly and for that reason we should create it as a singleton session. That means per application context it will create ony one timme. SessionFactory
object creation code sample:
private static ISessionFactory _sessionFactory;
public static ISessionFactory GetSessionFactory()
{
if (null == _sessionFactory)
{
FluentConfiguration nhConfig = Fluently
.Configure()
.Database(MsSqlConfiguration
.MsSql2008.ConnectionString(c => c
.Database(_conn.InitialCatalog)
.Server(_conn.DataSource)
.Username(_conn.UserID)
.Password(_conn.Password)
)
)
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Developer>()
.Conventions.Add(DefaultLazy.Never()));
nhConfig.Cache(c => c.ProviderClass<SysCacheProvider>().UseSecondLevelCache());
_sessionFactory = nhConfig
.ExposeConfiguration(v => new SchemaExport(v).Create(false, false))
.BuildSessionFactory();
}
return _sessionFactory;
}
And we can create and opened session like the following:
public static ISession CreateSession()
{ ISessionFactory factory = GetSessionFactory();
return factory.OpenSession();
}
If you want to enable/disable second level cache by configuration, then instead of using above code you need to create SessionFactory
object using following code:
public static ISessionFactory GetSessionFactory()
{
FluentConfigurationnhConfig = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(
c => c.Database(conn.InitialCatalog)
.Server(conn.DataSource)
.Username(conn.UserID).
Password(conn.Password)))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Organization>()
.Conventions.Add(DefaultLazy.Never()));
if (Config.IsSecondLevelCachingEnabled)
nhConfig = nhConfig.Cache(c => c.ProviderClass<SysCacheProvider>()
.UseSecondLevelCache());
_sessionFactory = nhConfig.ExposeConfiguration(v => new SchemaExport(v)
.Create(false, false)).BuildSessionFactory();
return _sessionFactory;
}
8. Entity Mapping
Without cache configuration from entity mapping classes, 2nd level cache would not work. So be careful about that.
Q. How can we configure cache from entity mapping class?
A. See the code below. Here I use fluent configuration. If you want you can use xml based configuration too. Developer
class:
public class Developer
{
public virtual int Id { get; set; }
public virtual string FullName { get; set; }
public virtual Department Department { get; set; }
public Developer(){}
}
And Developer
class’s fluent mapping class:
public class DeveloperMap : ClassMap<Developer>
{
public DeveloperMap()
{
base.Cache.NonStrictReadWrite().Region("FiveSecondTimeInterval");
base.Table("Developers");
base.Id(c => c.Id).Column("Id");
base.Map(c => c.FullName).Column("FullName");
base.References(c => c.Department).Column("DepartmentId");
}
}
In the above code, the DeveloperMap
class's constructor, first line declares that it needs to support Cache and configure its cache expiration policy based on Region
declaration. There are many types of Cache usage pattern that are supported, for example ReadOnly
, NonStrictReadWrite
, etc. If you search on the web, you can find more information regarding that.
9. Conclusion
NHibernate 2nd level caching is a very unique feature and it has capability to improve application performance. Point to be noted that it is a caching Technic similar to other caching. Before you apply it must carefully think and plan against entities which you need to support 2nd level cache and define its expiration policy. If you make a mistake to choose proper entities to cache and its best expiration policy, then it will not provide any benefits, moreover this may create many bugs. So before planning/configuring your entities for taking advantage from 2nd level cache, spend time to rethink and revise and verify and share it to your team members so that no error found in your plan/configuration/entity selection for cache.