Caching Items which are not user dependent is pretty straight forward. You create a key which represents the object in the cache collection and when it is time to remove the item you simply remove it using the key.
Things gets a little messy when you are dealing with User based cached items. This means a single user can have multiple items residing in the cache based on his credentials. The main problem comes in creating a UNIQUE key for each user. There are several approaches that can be used. Let's check out each one of them
Caching Items which are not user dependent is pretty straight forward. You create a key which represents the object in the cache collection and when it is time to remove the item you simply remove it using the key.
Things gets a little messy when you are dealing with User based cached items. This means a single user can have multiple items residing in the cache based on his credentials. The main problem comes in creating a UNIQUE key for each user. There are several approaches that can be used. Let's check out each one of them.
1) Using Session ID:
Session ID is unique for each user in a particular session. So, this means that when the user closes the browser window and comes back again his Session ID had been changed. This is the biggest advantage for using SessionID for creating cache keys. We sure want them unique but not for every user's session.
2) GUID:
Another technique is GUID. The same problem lies when using GUID. They can be changed when the user comes again. Also, GUID is pretty long field and the contast comparision in the cache dictionary will be a performance hit.
No solution is perfect! So, I came up with a very simple solution to generate the keys and also to remove the items when they are altered.
My key is based on [UserName]_[OBJECT TYPE]_[FUNCTION TYPE]. So, I have keys like the following:
JohnDoe_CUSTOMERS_GETALL
JohnDoe_CUSTOMERS_GETBYREGION
JohnDoe_CUSTOMERS -> basically the parent of GET_ALL and GETBYREGION
pretty simple!
Now, the GetCustomersByX methods would look like this:
public List<Customer> GetAllCustomers()
{
List<Customer> customers = CacheRepository.Get<Customer>(CacheRepository.CUSTOMERS_GET_ALL);
if (customers == null || customers.Count == 0)
{
customers = DataAccessGetAllCustomers();
CacheRepository.Insert(CacheRepository.CUSTOMERS_GET_ALL,customers);
}
return customers;
}
public List<Customer> GetCustomersByRegion(string regionName)
{
List<Customer> customers = CacheRepository.Get<Customer>(CacheRepository.CUSTOMERS_BY_REGION);
if (customers == null || customers.Count == 0)
{
customers = DataAccessGetCustomersByRegion(regionName);
CacheRepository.Insert(CacheRepository.CUSTOMERS_BY_REGION,customers);
}
return customers;
}
And the cache repository looks like this:
public const string CUSTOMERS_BY_REGION = "CUSTOMERS_GetByRegion";
public const string CUSTOMERS_GET_ALL = "CUSTOMERS_GetAll";
public const string CUSTOMERS = "CUSTOMERS";
public static List<T> Get<T>(string key)
{
return (List<T>) HttpContext.Current.Cache.Get(GenerateKey(key));
}
public static void Insert(string key,object item)
{
HttpContext.Current.Cache.Insert(CreateKey(key), item);
}
public static string GenerateKey(string key)
{
return "JohnDoe" + "_" +key;
}
// different overloads of the Insert method
private static string CreateKey(string key)
{
string userName = "JohnDoe";
return userName + "_" + key;
}
The Remove method removes all the entries from the cache for the particular user based on the [UserName]_[OBJECT TYPE] -> Parent.
So, whenever I add a new Customer to the persistent storage I will call the CacheRepository.Remove method like this:
CacheRepository.Remove(CacheRepository.CUSTOMERS);
And the Remove method:
public static void Remove(string key)
{
string userName = "JohnDoe";
key = userName + "_" + key;
// remove all the keys from the cache which have the corresponding key [UserName_Prefix]
IDictionaryEnumerator cacheEnum = HttpContext.Current.Cache.GetEnumerator();
while (cacheEnum.MoveNext())
{
if (cacheEnum.Key.ToString().StartsWith(key))
{
HttpContext.Current.Cache.Remove(key);
}
}
}
The biggest disadvantage is sending the JohnDoe_CUSTOMERS to the remove method. You need to create an extra public const to remove all the customers. Also, the developer can easily send any other key into the remove method. If you try to remove the cached keys only based on the username then several unwanted keys will be removed from the cache.
Another technique that I was looking at was using Cache attribute (custom attribute) on the methods. This way we can decorate the methods which require caching with the cache attribute. The downside is extra work done by reflection and also invoking the method with the correct parameters to get the result. Invoking data access method voilates the SRP (Single Responsibility Principle).
UPDATE:
I just realized that since the Cache can be application depedent or user dependent we can use the enums to associate the keys.
public static void Insert(Keys key, object value, Scope scope)
{
if (scope == Scope.APPLICATION)
{
// insert into the cache
HttpContext.Current.Cache.Insert(key.ToString(), value);
}
else if (scope == Scope.USER)
{
HttpContext.Current.Cache.Insert(PersonalizeKey(key.ToString()), value);
}
}
// create user dependent key
private static string PersonalizeKey(string key)
{
return HttpContext.Current.User.Identity.Name + "_" + key;
}