As I said before in my post about the repository pattern, entity framework doesn’t come with any caching. I’m pretty sure this will be added at some point, and I look forward to not worrying about it myself… After all, caching static data is quite a common scenario. One thing to remember is that the ObjectContext does some caching for you, but it’s no good for you if you want to cache data for a long time or across multiple contexts. This post shows my approach to data caching. It extends the caching aspect, which you can read more about here. If you’re not doing AOP and are still interested in caching then this is still relevant (at least I hope it is…)
Before I start talking about the implementation, let’s think about the requirements… We want to cache the result of a query (i.e. give me the currency USD). When someone re-runs the same query (give me the currency USD) they are given the same result (we don’t go to the DB again for it). When someone runs a different query (give me the currency GBP) they are not given the wrong result (i.e. USD). It must be thread safe. It would also be good to be able to say that I only want to cache the result of the query for the duration of the session / unit of work. It would also be cool if you could say, cache this for 2 minutes or so (this was already done with caching aspect, so I just need to make sure it works OK with entities)…
There’s also some restrictions which Entity Framework (and other ORM’s to be fair) bring to the table. The most noticeable are around attaching / detaching. The container only knows about an object if it’s attached (if you do payment.Currency = cachedCurrency; and it’s not attached the container will think it’s new, and insert it…). Also, an object instance can only be attached to a single context at a time. This means that we need to be able to clone instances and automatically attach them to the container (assuming they aren’t already attached – remember, not all objects fetched are cached and not all cached objects are from a different container)…
I’ve kept the cache as simple as possible to meet my requirements… There are some things which I’ve left out, like purging items when they change (I’ll add it when I find out I need it)… I also only cache the results of a query which returns a single result (T Find<T>(…)), you can’t cache a IQuerable<T> FindAll<T>(…). Again, I haven’t needed that yet, and it could means that we cache a lot more data than we may need (for example, the most common currencies you get are USD and GBP, so why cache the 100 odd other ones until you actually need them?)
Let’s start off with configuring caching:
The thing to notice is that when we ask for caching to be enabled we tell it the specification and how to generate a cache key from that specification (I could rely on Equals() of the spec, or get it to implement a ICacheKey or something, but I think this approach means it can be configured externally).
As I said before, the caching is applied as an aspect (extending the existing caching aspect):
If we pull the cached item out of the session cache then we don’t need to worry about attach / detach as it’s already on the correct context. If not, we need to resolve an instance of the repository which manages the object and attach a clone of it to that (first we check if it’s not already been attached). Now I’ve read this code again I’m not happy with the resolve, I think I need some kind of thread static AspectContext (need to be a stack I guess) which let’s me know what type /method I’m intercepting (then I could say AspectContext.CurrentCall.Service.Attach(…)). That or somehow pass that down to the cache. Next time I change this class I’ll refactor that I think (really appreciate suggestions if anyone has some!).
The base class is (this is the the standard cache used by the caching aspect):
When you say you want caching you can give it an implementation of ICache and or ICacheStore to use (as per this tests below).
The RepositoryComponent.Register makes sure that the right ICache is specified if you want caching enabled.
That’s pretty much it…