This is a followup to the question:
C# cache controller
Suggestions taken onboard from previous feedback, and refactoring to shorten the code in the calls to the cache controller by allowing queries to be passed as parameters.
Problem
Remove repeating cache code throughout code by creating a new cache controller class.
Cache Controller Class
public class LocalCacheController
{
private static readonly ConcurrentDictionary<string, object> Locks = new ConcurrentDictionary<string, object>();
/// <summary>
/// Return cache object
/// </summary>
private static Cache Cache => HttpContext.Current?.Cache ?? HttpRuntime.Cache;
/// <summary>
/// Return key of an object
/// </summary>
private static string GetCacheKey<T>(int recordID)
{
return EntityObj.GetCacheKey<T>(recordID);
}
/// <summary>
/// Given a set of record ID's, return the uncached
/// </summary>
public static List<int> GetUncachedRecordIDs<T>(List<int> recordIDsToQuery)
{
return recordIDsToQuery
.Where(c => !IsCached(GetCacheKey<T>(c)))
.ToList();
}
/// <summary>
/// Bulk save objects in cache if they don't exist.
/// </summary>
public static void AddRange<T1, T2>(IQueryable<T1> uncached)
{
foreach (dynamic rec in uncached)
{
int recordID = rec.ID;
LocalCacheController.Add<T1, T2>(recordID, rec);
}
}
/// <summary>
/// Set value in cache
/// </summary>
public static T2 Add<T1, T2>(int recordID, T1 getCachableObject)
{
Common.TestTypeDerivedFromDatabase<T1>();
var key = GetCacheKey<T2>(recordID);
var obj = Cache[key];
if (obj != null) return (T2)obj;
try
{
Locks.TryAdd(key, new object());
lock (Locks[key])
{
obj = Cache[key];
if (obj == null)
{
obj = Activator.CreateInstance(typeof(T2), getCachableObject);
SetLocalCacheValue(obj, false, key);
}
}
}
finally
{
object tR;
Locks.TryRemove(key, out tR);
}
return (T2)obj;
}
/// <summary>
/// Actually sets the value in the cache
/// </summary>
private static void SetLocalCacheValue(object objectToCache, bool overwrite, string key)
{
const CacheItemPriority priority = CacheItemPriority.Normal;
var slidingExpiry = Cache.NoSlidingExpiration;
var absoluteExpiry = Cache.NoAbsoluteExpiration;
if (overwrite)
{
Cache.Insert(key, objectToCache, null, absoluteExpiry, slidingExpiry, priority, null);
}
else
{
Cache.Add(key, objectToCache, null, absoluteExpiry, slidingExpiry, priority, null);
}
}
/// <summary>
/// Is there an object at key
/// </summary>
private static bool IsCached(string key)
{
return Cache[key] != null;
}
/// <summary>
/// Get object from cache
/// </summary>
public static T GetFromCache<T>(int recordID)
{
var key = GetCacheKey<T>(recordID);
var obj = Cache[key];
if (obj == null) return default(T);
return (T)obj;
}
}
Uncached query
/// <summary>
/// Return this object when querying which record ID's are currently uncached
/// for a specific type.
/// </summary>
public struct UncachedQueryResult<T>
{
/// <summary>
/// The returned uncached record IDs
/// </summary>
public List<int> UncachedRecordIDs { get; set; }
/// <summary>
/// Is every record ID passed in cached?
/// </summary>
public bool AllCached
{
get { return UncachedRecordIDs.Count == 0; }
}
private UncachedQueryResult(List<int> queryRecordIDs)
{
UncachedRecordIDs = LocalCacheController.GetUncachedRecordIDs<T>(queryRecordIDs);
}
/// <summary>
/// Perform a new query on a set of record IDs
/// </summary>
public static UncachedQueryResult<T> Get(List<int> queryRecordIDs)
{
return new UncachedQueryResult<T>(queryRecordIDs);
}
}
Example usage
Retrieve a single private message by ID
/// <summary>
/// Return a private message by it's ID
/// </summary>
public static PrivateMessage GetPrivateMessage(int messageID)
{
var pm = LocalCacheController.GetFromCache<PrivateMessage>(messageID);
if (pm != null) return pm;
using (var db = new DBContext())
{
return LocalCacheController
.Add<DB.PrivateMessage, PrivateMessage>(
messageID,
CompiledQueries.GetPMByID(db, messageID)
);
}
}
Cache any uncached private messages by ID
/// <summary>
/// Given list of message ID's, cache any uncached
/// </summary>
public static void CacheUncachedMessageIDs(List<int> messageIDs)
{
var q = UncachedQueryResult<PrivateMessage>.Get(messageIDs);
if (q.AllCached) return;
using (var db = new DBContext())
{
LocalCacheController.AddRange<DB.PrivateMessage, PrivateMessage>(
db.PrivateMessages.Where(c => q.UncachedRecordIDs.Contains(c.ID))
);
}
}