So you have a central method that returns some common data, say for example US States.
public IList<State> GetUsStates()
{
List<State> states = new List<State>();
using(IDataReader dr = DataAccess.ExecuteReader("SELECT * FROM US_STATES"))
{
State theState = State.Fetch(dr);
states.Add(theState);
}
return states;
}
Pretty simple stuff, loop over a datareader, building “State” objects based on a row in the database.
Well since this data hardly ever changes and is the same for every user, it is a prime target for caching. (Psst, if you didn’t know, caching can be one of the most effective performance tuning techniques you can perform — if done correctly).
Now that we want to enable caching, we can create a little helper class that makes it a bit easier to work with…
This is a watered down version of the cache API, but it solves 90% of the cases.
public class CacheHelper
{
public static bool ItemExists(string key)
{
return HttpContext.Current.Cache[key] != null;
}
public static void Insert(string key, object obj)
{
TimeSpan oneHour = new TimeSpan(1,0,0);
Insert(key, obj, oneHour);
}
public static void Insert(string key, object obj, TimeSpan span)
{
HttpContext.Current.Cache.Add(
key,
obj,
null, //no dependencies
DateTime.Now.Add(span),
Cache.NoSlidingExpiration,
CacheItemPriority.Normal,
null //no remove callback
);
}
public static T Retrieve<T>(string key)
{
return (T)HttpContext.Current.Cache[key];
}
}
Now that we have the utility methods in place, we can alter our US States function to enable caching:
public IList<State> GetUsStates()
{
string statesKey = "US_STATES";
if (!CacheHelper.ItemExists(statesKey))
{
//add it
List<State> states = new List<State>();
using (IDataReader dr = DataAccess.ExecuteReader("SELECT * FROM US_STATES"))
{
State theState = State.Fetch(dr);
states.Add(theState);
}
CacheHelper.Insert(statesKey, states);
}
//now it definitely exists in cache
return CacheHelper.Retrieve<IList<State>>(statesKey);
}
This code quickly gets repetetive and ugly, but it gets the job done. How can we do better?
We need to be able to provide the means of getting the data, but without actually executing it every time. This sounds like a good use for a delegate!
In our CacheHelper class we define the delegate:
public delegate object RetrieveDelegate();
It’s just a method signature that returns an object.
Then we define another method that will first check the cache, and execute the delegate if the data is not there:
public static T GetAndCache(string key, RetrieveDelegate retrieveObject, TimeSpan span)
{
if (!ItemExists(key))
{
object value = retrieveObject(); //this makes the call to the database
Insert(key, value, span);
}
return Retrieve<T>(key);
}
Now we have a method that can be smart about caching, but it is completely agnostic of what data to cache. Now we can refactor our GetStates() method to this:
public IList<State> GetUsStates()
{
return CacheHelper.GetAndCache<IList<State>>(
"US_STATES",
delegate {
List<State> states = new List<State>();
using (IDataReader dr = DataAccess.ExecuteReader("SELECT * FROM US_STATES"))
{
State theState = State.Fetch(dr);
states.Add(theState);
}
return states;
},
TimeSpan.FromDays(1));
}
We only have to interact with the CacheHelper once, thus we only use the cache key once as well. There are no if statements here either. Just a single function call that excepts an anonymous method.
This is pretty clean and it makes caching in your application easier. What do you think? Do you have any ideas for improvement? Let’s hear in the comments!