Cache-aside Pattern

 

Comments

 

Database implementation: Checks  if cache item exists, if it does not, check database,  if exists, store item with expiration timer if in database into cache otherwise create entry in database.

 

Code

 

private DataCache cache;
// My edit – database representation
private IDataStore store;


public async Task GetMyEntityAsync(int id)
{  
  // Define a unique key for this method and its parameters.
  var key = string.Format("StoreWithCache_GetAsync_{0}", id);
  var expiration = TimeSpan.FromMinutes(3);
  bool cacheException = false;

  try
  {
    // Try to get the entity from the cache.
    var cacheItem = cache.GetCacheItem(key);
    if (cacheItem != null)
    {
      return cacheItem.Value as MyEntity;
    }
  }
  catch (DataCacheException)
  {
    // If there is a cache related issue, raise an exception 
    // and avoid using the cache for the rest of the call.
    cacheException = true;
  }

  // If there is a cache miss, get the entity from the original store and cache it.
  // Code has been omitted because it is data store dependent.  
  var entity = ...;

  if (!cacheException)
  {
    try
    {
      // Avoid caching a null value.
      if (entity != null)
      {
	// using the same key check database if exists store value in cache
	var storeItem = store.GetStoreItem(key);
	if (storeItem != null)
    	{
	  // Put the item in the cache with a custom expiration time that 
         // depends on how critical it might be to have stale data. 
	  cache.Put(storeItem.key, entity, timeout: expiration);
      	  return storeItem.Value as MyEntity;
    	}
	// if the key does not exist on the database either DEPENDING ON APPLICATION RETURN AN LOG ERROR OOR STORE IN BOTH DATABASE AND CACHE
	else 
    	{
	  store.Put(storeItem.key);

 	// Put the item in the cache with a custom expiration time that 
        // depends on how critical it might be to have stale data. 
	 cache.Put(storeItem.key, entity, timeout: expiration);

      	  return storeItem.Value as MyEntity;
    	} 
    }
    catch (DataCacheException)
    {
      // If there is a cache related issue, ignore it
      // and just return the entity.
    }
  }

  return entity;
}

GENERIC CACHING: INTERFACE IMPLEMENTATION

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Runtime.Caching;
using System.Collections.Concurrent;

namespace Ektron.Com
{
    /// 
    /// Uses System.Runtime.Caching to provide a thread-safe caching class.
    /// Recommended use is to employ the TryGetAndSet method as a wrapper to call
    /// your data-building function.
    /// 
    public static class CacheManager
    {

        /// 
        /// The cache store. A dictionary that stores different memory caches by the type being cached.
        /// 
        private static ConcurrentDictionary cacheStore;

        /// 
        /// The default minutes (15)
        /// 
        private const int DefaultMinutes = 15;

        #region constructors

        /// 
        /// Initializes the  class.
        /// 
        static CacheManager()
        {
            cacheStore = new ConcurrentDictionary();
        }
        #endregion

        #region Setters

        /// 
        /// Sets the specified cache using the default absolute timeout of 15 minutes.
        /// 
        /// 
        /// The cache key.
        /// The data to be cached.
        static internal void Set(string cacheKey, T cacheItem)
        {
            Set(cacheKey, cacheItem, DefaultMinutes);
        }

        /// 
        /// Sets the specified cache using the absolute timeout specified in minutes.
        /// 
        /// 
        /// The cache key.
        /// The data to be cached.
        /// The absolute expiration (in minutes).
        static internal void Set(string cacheKey, T cacheItem, int minutes)
        {
            if (Ektron.Com.Helpers.Constants.IsCachingEnabled)
            {
                Type t = typeof(T);
                if (!cacheStore.ContainsKey(t))
                {
                    RegisterCache(t);
                }
                var cache = cacheStore[t];
                cache.Set(cacheKey, cacheItem, GetCacheItemPolicy(minutes));
            }
        }

        /// 
        /// Sets the specified cache using the passed function to generate the data. 
        /// Uses default absolute timeout of 15 minutes.
        /// 
        /// 
        /// The cache key.
        /// The function to generate the data to be cached.
        static internal void Set(string cacheKey, Func getData)
        {
            Set(cacheKey, getData, DefaultMinutes);
        }

        /// 
        /// Sets the specified cache using the passed function to generate the data. 
        /// Uses the specified absolute timeout (in minutes).
        /// 
        /// 
        /// The cache key.
        /// The function to generate the data to be cached.
        /// The absolute expiration (in minutes).
        static internal void Set(string cacheKey, Func getData, int minutes)
        {
            if (Ektron.Com.Helpers.Constants.IsCachingEnabled)
            {
                Type t = typeof(T);
                if (!cacheStore.ContainsKey(t))
                {
                    RegisterCache(t);
                }
                var cache = cacheStore[t];
                T data = getData();
                cache.Set(cacheKey, data, GetCacheItemPolicy(minutes));
            }
        }
        #endregion

        #region Getters
        /// 
        /// Tries to retrieve data from cache first. If the data is not found in cache, the passed function
        /// will be used to generate and store the data in cache. Data is returned via the returnData parameter.
        /// Function returns true if successful.
        /// Uses the default absolute timeout of 15 minutes.
        /// 
        /// 
        /// The cache key.
        /// The function to generate the data to be cached.
        /// The return data.
        /// True if successful. False if data is null.
        public static bool TryGetAndSet(string cacheKey, Func getData, out T returnData)
        {
            if (!Ektron.Com.Helpers.Constants.IsCachingEnabled)
            {
                Remove(cacheKey);
            }
            Type t = typeof(T);
            bool retrievedFromCache = TryGet(cacheKey, out returnData);
            if (retrievedFromCache)
            {
                return true;
            }
            else
            {
                returnData = getData();
                Set(cacheKey, returnData);
                return returnData != null;
            }
        }

        /// 
        /// Tries to retrieve data from cache first. If the data is not found in cache, the passed function
        /// will be used to generate and store the data in cache. Data is returned via the returnData parameter.
        /// Function returns true if successful.
        /// Uses the specified absolute timeout (in minutes).
        /// 
        /// 
        /// The cache key.
        /// The function to generate the data to be cached.
        /// The absolute expiration (in minutes).
        /// The return data.
        /// True if successful. False if data is null.
        public static bool TryGetAndSet(string cacheKey, Func getData, int minutes, out T returnData)
        {
            Type t = typeof(T);
            bool retrievedFromCache = TryGet(cacheKey, out returnData);
            if (retrievedFromCache && Ektron.Com.Helpers.Constants.IsCachingEnabled)
            {
                return true;
            }
            else
            {
                returnData = getData();
                Set(cacheKey, returnData, minutes);
                return returnData != null;
            }
        }

        /// 
        /// Attempts to retrieve data from cache.
        /// 
        /// 
        /// The cache key.
        /// The data from cache.
        /// True if successful. False if data is null or not found.
        static internal bool TryGet(string cacheKey, out T returnItem)
        {
            Type t = typeof(T);
            if (cacheStore.ContainsKey(t))
            {
                var cache = cacheStore[t];
                object tmp = cache[cacheKey];
                if (tmp != null)
                {
                    returnItem = (T)tmp;
                    return true;
                }
            }
            returnItem = default(T);
            return false;
        }
        #endregion

        /// 
        /// Removes the specified item from cache.
        /// 
        /// 
        /// The cache key.
        static internal void Remove(string cacheKey)
        {
            Type t = typeof(T);
            if (cacheStore.ContainsKey(t))
            {
                var cache = cacheStore[t];
                cache.Remove(cacheKey);
            }
        }

        /// 
        /// Registers the cache in the dictionary.
        /// 
        /// The type used as the key for the MemoryCache that stores this type of data.
        private static void RegisterCache(Type t)
        {
            ObjectCache newCache = new MemoryCache(t.ToString());
            cacheStore.AddOrUpdate(t, newCache, UpdateItem);
        }

        /// 
        /// Updates the item. Required for use of the ConcurrentDictionary type to make this thread-safe.
        /// 
        /// The Type used as the key for the MemoryCache that stores this type of data.
        /// The cache to be updated.
        /// 
        private static ObjectCache UpdateItem(Type t, ObjectCache cache)
        {
            var newCache = new MemoryCache(cache.Name);
            foreach (var cachedItem in cache)
            {
                newCache.Add(cachedItem.Key, cachedItem.Value, GetCacheItemPolicy(DefaultMinutes));
            }
            return newCache;
        }

        /// 
        /// Gets the cache item policy.
        /// 
        /// The absolute expiration, in minutes.
        /// A standard CacheItemPolicy, varying only in expiration duration, for all items stored in MemoryCache.
        private static CacheItemPolicy GetCacheItemPolicy(int minutes = 15)
        {
            var policy = new CacheItemPolicy()
            {
                Priority = CacheItemPriority.Default,
                AbsoluteExpiration = DateTimeOffset.Now.AddMinutes(minutes)
            };
            return policy;
        }
    }
}