NHibernate Caching with Redis



So I've recently been playing a bit with Redis, and also NHibernate so I thought, why not put them together. NHibernate supports a so-called 2nd level cache. For which you can use a number of different providers such as Memcached or Microsoft AppFabric / Velocity. But more recently thanks to some great people you can also use Redis as your 2nd level cache.

What you'll need

  1. Redis - Windows Service edition (not recommended for production use)
  2. NHibernate
  3. Fluent NHibernate
  4. ServiceStack.Redis
  5. NHibernate Redis provider
  6. NHibernate Caching with Redis Tutorial source code
Step 1 - Unzip All the above libraries and put them in a lib folder.
My lib folder looks like this:


Step 2 - Setup NHibernate
Create a new Console Application and then add then add the following references to visual studio. You should end up with something like this:


You next need to setup all your mapping classes, (Shameless plug) I wrote another tutorial on Fluent NHibernate which goes into more detail on how to do this. Suffice to say if you have a simple Car entity class that looks like:
public class Car
    {
        public virtual int Id { get; set; }
        public virtual string Title { get; set; }
        public virtual string Description { get; set; }
        public virtual Make Make { get; set; }
        public virtual Model Model { get; set; }
    }
Then your Fluent NHibernate mapping class should look like this:

public class CarMap : ClassMap
    {
        public CarMap()
        {
            Id(x => x.Id);
            Map(x => x.Title);
            Map(x => x.Description);
            References(x => x.Make).Column("MakeId");
            References(x => x.Model).Column("ModelId");
            Table("Car");
            Cache.ReadWrite();
        }
    }

Step 3 - Configure NHibernates database connection

Next we need to Fluently configure NHibernate to talk to our database. At this point we haven't enabled the 2nd level cache.

using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate;
using NHibernate.Tool.hbm2ddl;

namespace SimpleNHibernateClient.ConsoleApplication
{
    public class NHibernateHelper
    {
        private static ISessionFactory _sessionFactory;

        private static ISessionFactory SessionFactory
        {
            get
            {
                if (_sessionFactory == null)
                    InitializeSessionFactory();
                
                return _sessionFactory;
            }
        }

        private static void InitializeSessionFactory()
        {
            _sessionFactory = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                              .ConnectionString(
                                  @"Server=localhost\SQLExpress;Database=SimpleNHibernate;Trusted_Connection=True;")
                              .ShowSql()
                )
                .Mappings(m =>
                          m.FluentMappings
                              .AddFromAssemblyOf())
                .ExposeConfiguration(cfg => new SchemaExport(cfg)
                                                .Create(true, true))
                .BuildSessionFactory();
        }

        public static ISession OpenSession()
        {
            return SessionFactory.OpenSession();
        }
    }
}
So you might need to create the mentioned SimpleNHibernate database and you might want to change the localhost\SQLExpress.

Step 4 - Hello Simple NHibernate Database

Ok so now we need to prove that we can indeed insert Cars into our database.

I put this code in my Main method. Not exactly industrial but it proves the simple point. Please note we're making use of the NHibernateHelper class described above.
var carId = 0;
            using (var session = NHibernateHelper.OpenSession())
            {
                using (var transaction = session.BeginTransaction())
                {
                    var car = new Car
                    {
                        Title = "Dans SimpleNHibernate Car"
                    };
                    session.Save(car);
                    transaction.Commit();
                    carId = car.Id;
                    Console.WriteLine("Created Car: " + car.Title);

                }
Simply opening an Nhibernate Session this a transaction creating the Car saving it to the NHibernate repository with session.Save(car) and critically  transaction.Commit(). the commiting of the transaction will become even more important in a minute, because NHibernate uses this in order to update the 2nd level cache. 

Now run the current profram and you should see a row in your database for the above car.

Step 5 - Start the Redis server

Unzip the Redis Server under your tools folder in your project. Mine looks like this:

Then simply double click redis-server.exe 


Excellent so this Redis server is now up and running.

Step 6 - Fluently Configure NHibernates 2nd Level Cache to use Redis
Ok so now we need to enable NHibernates 2nd level cache. You'll be pleased to know it's really easy to do so. Firstly we need to add a redis ConfigurationSection to our app.config or web.config if you're doing a webapp. This enables our Redis Cache Provider to find out where our Redis cache server lives in the world. So as you can see below it's running on my local machine and it's running on port 6379 which seems to be the default Redis port.

Now we need to change our NHibernateHelper class to enable the 2nd level cache.

_sessionFactory = Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                              .ConnectionString(
                                  @"Server=localhost\SQLExpress;Database=SimpleNHibernate;Trusted_Connection=True;")
                              .ShowSql()
                              .Cache(cache=>cache.ProviderClass(typeof(NHibernate.Caches.Redis.RedisProvider).AssemblyQualifiedName)
                              .UseQueryCache()
                              )
                )                
                .Mappings(m =>
                          m.FluentMappings
                              .AddFromAssemblyOf())
                .ExposeConfiguration(cfg => new SchemaExport(cfg)
                                                .Create(true, true))
                .BuildSessionFactory();

 

can I draw your attention to specifically:

 

.Cache(cache=>cache.ProviderClass(typeof(NHibernate.Caches.Redis.RedisProvider).AssemblyQualifiedName)
                              .UseQueryCache()

This is the real magic. We tell NHibernate to use the cache.ProviderClass or NHibernate.Caches.Redis.RedisProvider. and we then tell NHibernate to UseQueryCache() this will mean that NHibernate will check in the Redis cache via this provider before querying the database. If the query hasn't been run before, it'll hit the database. But if it has it'll get the results back from the cache.

Step 7 - Write a simple App test getting a Car from the Redis Cache

Ok so now let's write a simple application. Please note this isn't elegant or pretty, but hopefully it's quite illustrative. Initially lets put some Cars in our database

var carId = 0;
            using (var session = NHibernateHelper.OpenSession())
            {
                using (var transaction = session.BeginTransaction())
                {
                    var car = new Car
                    {
                        Title = "Dans SimpleNHibernate Car",
                        Description = "This car is blisteringly fast (when taken from cache)",
                        Make = "Ford",
                        Model = "Fiesta"                       
                    };
                    session.Save(car);

                    var fordFocus = new Car
                    {
                        Title = "Dans Ford Focus Car",
                        Description = "This car is blisteringly fast (when taken from cache)",
                        Make = "Ford" ,
                        Model = "Focus" 
                    };
                    session.Save(fordFocus);

                    var vauxhall = new Car
                    {
                        Title = "Vauxhall Car",
                        Description = "This car is blisteringly fast (when taken from cache)",
                        Make = "Vauxhall",
                        Model = "Astra" 
                    };                                        
                    session.Save(vauxhall);
                   
                    transaction.Commit();

                    carId = car.Id;
                    Console.WriteLine("Created Car: " + car.Title);
                    Console.WriteLine("Created Car: " + fordFocus.Title);
                    Console.WriteLine("Created Car: " + vauxhall.Title);

                }
            }

 

Then we'll get them back out again. Before running this make sure Redis is running otherwise it won't work.

 

for (int i = 0; i <= 5; i++)
            {
                using (var session = NHibernateHelper.OpenSession())
                {
                    var car = session.Get(carId); // Should get car from Cache
                    Console.WriteLine("Read Car: " + car.Title);
                }
            }

 

A simple loop above to get cars by there ID. The first should come from the Database, but all subsequent requests from that specific car id should come from the cache.

Now lets do the same only with a slightly more complicated query.

for (int i = 0; i <= 5; i++)
            {
                using (var session = NHibernateHelper.OpenSession())
                {

                    var query = session.Query();
                    query = query.Cacheable();

                    // Should get car from DB first then the NHibernate 2nd Level Redis Cache
                    var fordCars = (from car in query
                                    where car.Make == "Ford"
                                    select car);


                    foreach (var fordCar in fordCars)
                    {
                        Console.WriteLine("Read Make: " + fordCar.Title);
                    }


                }
            }

 

Again the first request should come from the database, but subsequent request should come from the cache.

Just to prove it, we can launch SQL Profiler and perform a trace. if we watch it, we see the inserts, but we only see 2 SELECT statements. All the others come from the cache.

That's it for our simple start. Have fun. But please do remember the Windows version of the Redis server isn't recommended for production use.

SimpleNHibernateClientWithRedis.zip (5.03 mb)


blog comments powered by Disqus

Search

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2014