Cache Access Pattern Revised

Jul 7 2008 9:03 AM

Karl Seguin has an interesting post about using System.Func to fight repetitive code blocks, which actually addresses a pain point I've had for quite some time but had never acted on to fix. Whenever one access the Cache or a similar statebag that might or might not contain the value sought after, it is important to check if the value exists first, and if it doesn't, go and retrieve it from wherever its authoritative repository is (typically the database). This can be done poorly, so I show the correct way to do it frequently in my talks about caching or performance. The unfortunate thing about this pattern, however, is that you're always stuck writing the same code, which gets old after a while (and is prone to error).

Karl shows how one can refactor the object fetching method to accept a callback which is responsible for fetching the object if it is not in the cache/statebag, using Func<T>. The resulting code looks like this:

public T Get<T> (string key, Func<T> ifNullRetrievalMethod)

{

T item = (T) HttpRuntime.Cache.Get(key);

if (item == null)

{

item = ifNullRetrievalMethod();

Insert(key, item);

}

return item;

}

Assuming that we want a method that retrieves a user from its ID, and that fetching it from the data store requires a call to _dataStore.GetUserFromId(userId), we can write our data access method like so:

public User GetUserFromId(int userId)

{

return CacheFactory.GetInstance.Get(

string.Format("User.by_id.{0}", userId),

() => _dataStore.GetUserFromId(userId));

}

In this example, () => is a parameterless delegate. Essentially we're wrapping up the little bit of code required to fetch the user (or whatever object) and passing it into the method that is responsible for checking the cache. If the object is not in the cache, the function is executed (otherwise it is ignored).

Looking around a bit more, Alan Northam has a somewhat similar, but more involved caching pattern here, which does a little more error checking and uses a standard delegate for the retrieveMethod. Be sure to have a look at that as well - it would be easy to modify it to use Func<T>.