caching decorator

Dec 19, 2013 at 6:07 PM
Edited Dec 19, 2013 at 6:55 PM
Hi Steven,

I have a couple of questions here.

First, I've been reading some posts about caching (particularly this SO question and I think something is wrong with it in terms of what is being cached. In the question you all are talking about caching the results based on the name of the query.
        var key = query.GetType().ToString();
        var result = (TResult) _cache[key];
        if (result == null)
            _log.Debug(m => m("No cache entry for {0}", key));
            result = (TResult)_handler.Handle(query);
            if (!_cache.Contains(key))
                _cache.Add(key, result, _policy);
        return result;
It seems to me that the key should be the query object. If you only key from the name of the query, then queries from the UI within the policy time would return the same results no matter the actual query parameters. Make sense? It may be that the parties involved all knew this and the question is about the policy not the actual storage mechanism but I'd appreciate some feedback anyway.

Second, it seems that in order to get around the first problem, all of my queries need to override Equals so that I can do the following in my cache decorator.
            var key = query.GetType().Name;

            if (store[key] != null)
                var stuff = (Tuple<TQuery,TResult>)store[key];
                if (Equals(stuff.Item1, query))
                    return stuff.Item2;

            var result = _decorated.Handle(query);
            store[key] = new Tuple<TQuery, TResult>(query, result);
Alternatively, I can make the key unique based on parameters. Something like:
var key = query.Key
Either would work, but I have a TON of queries. Can you suggest some magic? I had considered a base class, but that doesn't give me much advantage over just adding a key property to my base IQuery interface (or adding another interface, i.e. - ICacheableQuery) interface. My other thought was to have my cache decorator use some refection magic.

Dec 19, 2013 at 8:42 PM
I'm not sure why the type name is suggested in that SO question, but what I can tell you is what I did in my last project. We cached our queries and as you said, they need to be cached by the actual values. What we did is the following:
  • Serialize the query to a JSON string.
  • Make an MD5 hash of that string.
  • Check whether a file by that name (the hash) exists on disk in a directory called Cache[QueryName].
  • If the file exists read the contents and deserialize it (json) back to the TResults.
  • If it doesn't exist, call the decoratee, serialize the result and write it to disk.
This way we didn't need to implement Equals on each query, because that would be cumbersome and error prone.
Marked as answer by dot_NET_Junkie on 2/26/2014 at 1:15 PM
Dec 20, 2013 at 12:06 AM
ok, this is close to what I've done, I use the ObjectCache as the questioner does in SO, but I just serialize the Query object to a Json string to use as my key. Then I store the results into the cache. The next time that querytype comes in I check the cache for the key. I'm curious why you serialize to disk, that seems like it'd be much slower than a memory cache of some kind.
Dec 20, 2013 at 4:57 AM
I use disk because the cache must survive an application restart. it is used to let thw client application work in offline mode.