Make Your Website Scream By Adding A Simple Cache Layer to your Repositories
If you want your website to perform at optimum levels, caching will give you spectacular results. In this post, we'll add a data caching layer to your repositories making your website perform at top speeds.
When I discussed how to make your website perform with multiple result sets in the last post, I mentioned at the bottom that you could easily add a caching layer on top of your existing repositories.
The caching layer is inherited from your repositories. When you add that layer, you'll have an option to pass in a reload parameter to bypass the caching and load directly from the database.
Let's dig into the code.
New Cache Repository = CacheRepository
The best way to handle the caching is based on your needs. Since we won't be calling our FAQs all the time, we can cache them and make them available through the caching layer.
Let's create a new repository called CacheFaqRepository
repository\CachedFaqRepository.cs
namespace ThinController.Repository { public class CachedFaqRepository: FaqRepository { } }
We inherited from FaqRepository because we need to leverage what we already wrote, specifically the GetAll method in the FaqRepository class.
First, we need our locking object to make our code thread-safe and that we can perform any database call to retrieve our data from either the cache or from the database.
namespace ThinController.Repository
{ public class CachedFaqRepository: FaqRepository { private static readonly object CacheLockObject = new object(); } }
Next, we need our cache key. I usually concatenate the key made up of the method name, and optionally, the parameters. It just has to be unique.
using System.Collections.Generic; using ThinController.Models; namespace ThinController.Repository { public class CachedFaqRepository: FaqRepository { private static readonly object CacheLockObject = new object(); public IEnumerable<Faq> GetAllFaqs(int count, int hours, bool reload = false) { const string cacheKey = "CachedFaqRepository:GetAll"; } } }
As you can see, I'm just making a call to pull all of the FAQs since I don't have that many records in the table. So our cacheKey is the name of the class and the name of the method, just in case I need to view the cache at a later time.
Next, we need to perform the old double-check locking to make sure we don't receive any race conditions and we can actually retrieve the value. Once we have the value, we can cache it and return it safely.
using System; using System.Collections.Generic; using System.Linq; using System.Web; using ThinController.Models; namespace ThinController.Repository { public class CachedFaqRepository: FaqRepository { private static readonly object CacheLockObject = new object(); public IEnumerable<Faq> GetAllFaqs(int count, int hours, bool reload = false) { const string cacheKey = "CachedFaqRepository:GetAll"; // Retrieve the value from the cache, see if it's there. var result = HttpRuntime.Cache[cacheKey] as List<Faq>; // If it's not in the cache OR we force a reload... if (result == null || reload) { // ...then perform a lock. lock (CacheLockObject) { // Check to see if we can get the value again. // Reason: we may have been blocked from someone else // retrieving the value from cache. result = HttpRuntime.Cache[cacheKey] as List<Faq>; if (result == null || reload) { // If we still don't have a value or we are // forcing a reload, then perform the retrieval // of the data and cache it. result = base.GetAll().ToList(); HttpRuntime.Cache.Insert(cacheKey, result, null, DateTime.Now.AddHours(hours), TimeSpan.Zero); } } } return result; } } }
I usually replace my regular repository classes in my Unit Of Work pattern with my cached repository classes. That way, I can control whether I need to perform a full reload of my objects (by passing in a true to my reload parameter) or just calling the cache method without the reload parameter to use the data caching.
This has been extremely helpful in my CMS I built. In my administration section, I always pass in a true for reloading, so I know I'm always loading the latest data when I'm editing it.
Then on the public side, I leave the reload parameter off so it performs data caching, making the site faster since it doesn't have to hit the database.
Conclusion
This approach can definitely accelerate your website using data caching. When the implemention above is added to your repositories, your site will start to scream because it won't access the database as frequently.
Did this make sense? How could I have done this better? Post your comments below.