This project is read-only.

Transparently Caching Queries

The How and the What are often discussed when it comes to caching. As always, there is no Silver Bullet that solves all issues at once.

TecX offers one solution for the following scenario: Say you have a datasource that provides access to your data via a set of IQueryable<T> properties. The interface might look like this:

public interface ICustomerRepository
{
  IQueryable<Customer> Customers { get; }
}

Now you want to add caching but you don't want to have to change how the consumers work with that repository. So you need some transparent caching mechanism that isolates your consumers (and your developers) from the actual implementation of caching. You are then able to run your application without caching. You can use the new features from System.Runtime.Caching, the AppFabric Cache or something completely different.

Two classes from TecX.Caching (namely QueryInterceptor and QueryInterceptorProvider) allow for transparent interception of queries against an IQueryable<T>. They are wrappers for IQueryable and IQueryProvider that use the Expression property of the IQueryable to generate a unique cache key. A simple ToString() operation won't give you a key that is "uniqe enough" so a technique presented by Pete Montgomery is used to partially evaluate the expression tree of the query.

The sample CachingCustomerRepository demonstrates how this interception can be used to introduce a transparent caching layer into your application and swap it out for arbitrary implementations at any time.

Using a framework like Moq you can easily mock your actual dataaccess in order to run tests against your cache.

var mock = new Mock<ICustomerRepository>();
mock.SetupGet(r => r.Customers).Returns(
  new[]
  {
    new Customer { Id = 1, Name = "1" }, 
    new Customer { Id = 2, Name = "2" },
    new Customer { Id = 3, Name = "3" }
  }.AsQueryable());
var cache = new CachingCustomerRepository(mock.Object);
var r1 = cache.Customers.Where(c => c.Id < 3).ToList();
var r2 = cache.Customers.Where(c => c.Id < 3).ToList();
var r3 = cache.Customers.Where(c => c.Id < 3).ToList();
var r4 = cache.Customers.Where(c => c.Id < 3).ToList();
Assert.IsTrue(r1.SequenceEqual(r2));
Assert.IsTrue(r2.SequenceEqual(r3));
Assert.IsTrue(r3.SequenceEqual(r4));
mock.VerifyGet(r => r.Customers, Times.Once());

No matter how often you run the same query your database will only be hit once until your cache is invalidated.

Last edited Dec 15, 2011 at 7:34 AM by weberse, version 3

Comments

No comments yet.