Tuesday, March 5, 2013

Cached Repository

Caching in MVC is not all that difficult. But, you can put an interesting twist on it. This blog post will look at using a cached repository. This awesome idea came to my attention after surfing the web and landing at Steve Smith's blog:

Cached Repository

Since we will be using a repository here, let's set that up first. The POCO will be a Person class:
public class Person
{
   public int Id { get; set; }
   public string FirstName { get; set; }
   public string LastName { get; set; }
}

I do have certain patterns and techniques I like to use. Since some have been featured in past postings, the code will just be posted below...
using System.Data.Entity.ModelConfiguration.Configuration;

public interface IEntityConfiguration
{
   void AddConfiguraton(ConfigurationRegistrar registrar);
}

using System.Collections.Generic;
using StructureMap;

public class ContextConfiguration
{
   public IEnumerable<IEntityConfiguration> Configurations
   {
      get { return ObjectFactory.GetAllInstances<IEntityConfiguration>(); }
   }
}

The configuration class for our Person POCO:
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration;
using System.Data.Entity.ModelConfiguration.Configuration;

public class PersonConfig : EntityTypeConfiguration<Person>, IEntityConfiguration
{
   public PersonConfig()
   {
      HasKey(p => p.Id);
      Property(p => p.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity);
      Property(p => p.FirstName).HasColumnName("FirstName").HasColumnType("nvarchar").HasMaxLength(30).IsRequried();
      Property(p => p.LastName).HasColumnName("LastName").HasColumnType("nvarchar").HasMaxLength(30).IsRequired();

      ToTable("Person");
   }

   public void AddConfiguration(ConfigurationRegistrar registrar)
   {
      registrar.Add(this);
   }
}

Again, because I like to do things in a particular fashion, the setup using an IDbContext interface may seem overkill for this demo scenario, but it's still valid for when you build out full repositories. Suffice to say, is still retains value. Plus, some of us like seeing the different ways people set up their repositories and data access crud. Moving on:
using System.Data.Entity;
using System.Data.Entity.Infrastructure;

public interface IDbContext
{
   IDbSet<TEntity> Set<TEntity>() where TEntity : class;
   DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class;
   void SaveChanges();
   void Dispose();
}

using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.ModelConfiguration.Conventions;
using System.Data.Entity.Validation;

public class PersonContext : DbContext, IDbContext
{
   public PersonContext()
      : base("name=DefaultConnection")
   {
      this.Configuration.AutoDetectChangedEnabled = true;
      this.Configuration.ProxyCreationEnabled = true;
   }

   public IDbSet<Person> People { get; set; }

   public IDbSet<TEntity> Set<TEntity>() where TEntity : class
   {
      return base.Set<TEntity>();
   }

   public new DbEntityEntry<TEntity> Entry<TEntity>(TEntity entity) where TEntity : class
   {
      return base.Entry<TEntity>(entity);
   }

   public new void SaveChanges()
   {
      try
      {
         base.SaveChanges();
      }
      catch (DbEntityValidationException ex)
      {
         throw new Exception(ex.Message);
      }
   }

   protected new void Dispose()
   {
      base.Dispose();
   }

   protected override void OnModelCreating(DbModelBuilder modelBuilder)
   {
      modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
      Database.SetInitializer(new PersonInitializer());

      ContextConfiguration ctxConfig = new ContextConfiguration();

      foreach (IEntityConfiguration configuration in ctxConfig.Configurations)
      {
         configuration.AddConfiguration(modelBuilder.Configurations);
      }
   }
}

The PersonInitializer class sets up seed data which will be omitted. Also omitted is the StructureMap code since that has been covered in another post, just want to keep moving along here.

For the repository, I like to take the generic approach. We will have a Repository<T> class, a concrete PersonRepository class, an IRepository<T> interface, and an IPersonRepository interface. For this posting, I am not going to show a full blown repository implementation, perhaps that will come in a later post. Presented here will just be a very scaled down version to get the point of caching across:
public interface IRepository<T> where T : class
{
   IQueryable<T> AsQueryable();
   IEnumerable<T> GetAll();
}

public class Repository<T> : IRepository<T> where T : class
{
   private IDbContext iDbContext;

   public Repository(IDbContext context)
   {
      this.iDbContext = context;
   }

   public IQueryable<T> AsQueryable()
   {
      return this.iDbContext.Set<T>().AsQueryable();
   }
}

public interface IPersonRepository : IRepository<Person>
{
}

public class PersonRepository : Repository<Person>, IPersonRepository
{
   public PersonRepository(IDbContext iDbContext)
      : base(iDbContext)
   {
   }
}

Now the normal repository business is set up. Let's set up the Cachec Repository. First, we will need the generic ICachedRepository interface. You can put whatever methods you want to cache on this interface from the generic IRepository<T> interface. Sticking with our example, we have two, AsQueryable and GetAll:
public interface ICachedRepository<out T> where T : class
{  
   IQueryable<T> AsQueryable();
   IEnumerable<T> GetAll();
}

Of course the next step is the class that implements this interface. Remember, we are hooking into the ASP.NET cache here. Here is the CacheRepository class:
public class CachedRepository<T> : ICachedRepository<T> where T : class
{
   private IDbContext iDbContext;
   private static readonly object CacheLock = new object();

   public CachedRepository(IDbContext iDbContext)
   {
      this.iDbContext = iDbContext;
   }

   public IQueryable<T> AsQueryable()
   {
      string cacheKey = "AsQueryable";
      var result = HttpRuntime.Cache[cacheKey] as IQueryable<T>;

      if (result == null)
      {
         lock (CacheLock)
         {
            result = HttpRuntime.Cache[cacheKey] as IQueryable<T>;
            if (result == null)
            {
               result = this.iDbContext.Set<T>().AsQueryable();
               HttpRuntime.Cache.Insert(cachKey, result, null, DateTime.Now.AddSeconds(20), TimeSpan.Zero);
            }
         }
      }

      return result;
   }

   public IEnumerable<T> GetAll()
   {
      string cacheKey = "GetAll";
      var result = HttpRuntime.Cache[cacheKey] as IEnumerabl<T>;

      if (result == null)
      {
         lock (CacheLock)
         {
            result = HttpRuntime.Cache[cacheKey] as IEnumerable<T>;
            if (result == null)
            {
               result = this.iDbContext.Set<T>().AsEnumerable();
               HttpRuntime.Cache.Insert(cacheKey, result, null, DateTime.Now.AddSeconds(20), TimeSpan.Zero);
            }
         }
      }

      return result;
   }
}

In each method, we start out by defining a cache key, this cache key is put into the ASP.NET cache. Next, we check the cache for the cache key, if they cache key is null, no results for that given key have been stored in the cache. Now we lock the cache. It may seem strange to check the cache again for the result but this is very common in situations involving locking, it's a double check. We check the cache again for the cache key, and compare the result. Again, if the result is null, there is nothing in the cache for this query. If null, we query the DbContext for that entity and store the result, along with the cache key, cache dependencies (null), absolute expiration (DateTime.Now.Add(20)), and a sliding expiration (TimeSpan.Zero).
To set up a cached person repository, we just follow the patterns:
public interface ICachedPersonRepository : ICachedRepository<Person>
{
}

And the class to consume the interface:
public class CachedPersonRepository : CachedRepository<Person>, ICachedRepository
{
   public CachedPersonRepository(IDbContext iDbContext)
      : base(iDbContext)
   {
   }
}

What does this look like? When I was doing this demo, I was using the Paged List package which is a nice utility you can get on NuGet for formatting results with paging and all that good stuff. I won't go into that here but anyway, we have our Home Controller, the Index method is where the action is happening. The code below is not very important, moreso for illustration purposes. Also, the StructureMap configuration is not shown as well:
private ICachedPersonRespository cachedPersonRepository;

public HomeController(ICachedPersonRepository cachedPersonRepository)
{
   this.cachedPersonRepository = cachedPersonRepository;
}

public ActionResult Index(string search = null, int page = 1)
{
   IPagedList viewModels = this.cachedRepository.AsQueryable()
                                            .OrderByDescending(p => Id)
                                            .Where(p => search = null || p.FirstName.EndsWith(search))
                                            .Select(p => new PersonViewModel()
                                            {
                                               Id = p.Id,
                                               FirstName = p.FirstName,
                                               LastName = p.LastName
                                            }).ToPagedList(page, 5);

   return View(viewModels);
}

When the application first loads and we hit this method, the cached repository will be checked with the call to AsQueryable(). If we were to step through the code upon page load, here is the breakdown:
public IQueryable<T> AsQueryable()
{
   // upon page load, define the cache key
   string cacheKey = "AsQueryable";

   // look in cache for the cach ekey
   var result = HttpRuntime.Cache[cacheKey] as IQueryable<T>;

   // first page load, result will be null, nothing in cache
   if (result == null)
   {
      // lock the cache
      lock (CacheLock)
      {
         // double check
         result = HttpRuntime.Cache[cacheKey] as IQueryable<T>;

         // check the result again, result will be null on first page load
         if (result == null)
         {
            // get entity of T AsQueryable
            result = this.iDbContext.Set<T>.AsQueryable();

            // insert into the cache
            HttpRuntime.Cache.Insert(cacheKey, result, null, DateTime.Now.AddSeconds(20), TimeSpan.Zero);
         }
      }
   }

   return result;
}

We have assigned the result to be cached for 20 seconds. This means that for the next 20 seconds, any call that comes into the cached repository with the call to AsQueryable(), the ASP.NET runtime will return the cached response. In his article, Steve Smith points out that this approach does violate the DRY principle. If you want to look into that, check this link:

Cached Repository with Strategy Pattern