A set of methods which helps you extend your functions so that they cache their result after execution.
LambdaCache helps you to make expensive function calls cacheable. Instead of calling
slowFunction(inputVar); you run LambdaCache.ToCachedFunction(() => slowFunction(inputVar), TimeSpan.FromHours(24), inputVar)(); to make the function cache it's result for 24 hours.
#region
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Web;
using System.Web.Caching;
using System.Web.UI;
#endregion
namespace Utilities
{
/// <summary>
/// LambdaCache helps you to make expensive function calls cacheable.
/// For instance, instead of calling <code>expensiveFunction(inputVar);</code>
/// you run <code>LambdaCache.ToCachedFunction(() => expensiveFunction(inputVar), TimeSpan.FromHours(24), inputVar)();</code>
/// to make the function cache it's result for 24 hours.
///
/// The methods lock on the input (input-vars + function-name) to make sure that concurrent calls to the same function with
/// the same input are not called twice.
/// </summary>
public static class LambdaCache
{
private static bool cacheIsEnabled
{
get { return HttpContext.Current != null; }
}
/// <summary>
/// Makes your function cache its return value in HttpContext.Cache.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="expensiveFunction">The function to cache.</param>
/// <param name="timeToCache">Time to cache it.</param>
/// <param name="cacheKeys">The input variables which the function's output depends on.</param>
/// <returns></returns>
public static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, TimeSpan timeToCache, params object[] cacheKeys)
{
return ToCachedFunction(expensiveFunction, timeToCache, CacheLocation.Cache, cacheKeys);
}
public static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, DateTime cacheDate, params object[] cacheKeys)
{
return ToCachedFunction(expensiveFunction, cacheDate, CacheLocation.Cache, cacheKeys);
}
/// <summary>
/// Works the same way as ToCachedFunction, except that it stores the result in HttpContext.Items (i.e. within the current http-request).
/// Therefore you don't have to say how long time to cache it.
/// </summary>
/// <param name="expensiveFunction">The function to cache.</param>
/// <param name="cacheKeys">The input variables which the function's output depends on.</param>
/// <returns></returns>
public static Func<T> ToRequestCachedFunction<T>(this Func<T> expensiveFunction, params object[] cacheKeys)
{
return ToCachedFunction(expensiveFunction, TimeSpan.Zero, CacheLocation.Request, cacheKeys);
}
/// <summary>
/// Works the same way as ToCachedFunction, except that it stores the result in HttpContext.Session (i.e. within the current http-session).
/// Therefore you don't have to say how long time to cache it.
/// </summary>
/// <param name="expensiveFunction">The function to cache.</param>
/// <param name="cacheKeys">The input variables which the function's output depends on.</param>
/// <returns></returns>
public static Func<T> ToSessionCachedFunction<T>(this Func<T> expensiveFunction, params object[] cacheKeys)
{
return ToCachedFunction(expensiveFunction, TimeSpan.Zero, CacheLocation.Session, cacheKeys);
}
private static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, DateTime endTime, CacheLocation cacheLocation,
params object[] cacheKeys)
{
var timeToCache = endTime.Subtract(DateTime.Now);
return ToCachedFunction(expensiveFunction, timeToCache, cacheLocation, cacheKeys);
}
private static string getCacheKey<T>(Func<T> expensiveFunction, IEnumerable<object> localCacheKey)
{
var cacheKeyBuilder = new StringBuilder(expensiveFunction.ToString());
var textWriter = new StringWriter(cacheKeyBuilder);
var serializer = new LosFormatter(false, "");
foreach (var cacheKey in localCacheKey)
{
cacheKeyBuilder.Append('|');
if (cacheKey != null)
{
//Diffrerent instances of DateTime which represents the same value
//sometimes serialize differently due to some internal variables which are different.
//We therefore serialize it using Ticks instead. instead.
var inputDateTime = cacheKey as DateTime?;
if (inputDateTime.HasValue)
{
cacheKeyBuilder.Append(inputDateTime.Value.Ticks);
}
else if (cacheKey is string)
{
cacheKeyBuilder.Append(cacheKey);
}
else
{
//Serialize the input and write it to the key StringBuilder.
serializer.Serialize(textWriter, cacheKey);
}
}
else
{
cacheKeyBuilder.Append("NullValue");
}
}
return String.Intern(cacheKeyBuilder.ToString());
}
public static void RemoveCacheData<T>(this Func<T> expensiveFunction, params object[] localCacheKey)
{
if (cacheIsEnabled)
{
string globalCacheKey = getCacheKey(expensiveFunction, localCacheKey);
lock (globalCacheKey)
{
HttpContext.Current.Cache.Remove(globalCacheKey);
}
}
}
private static Func<T> ToCachedFunction<T>(this Func<T> expensiveFunction, TimeSpan timeToCache, CacheLocation cacheLocation,
params object[] localCacheKey)
{
return () =>
{
//We lock the function to make sure that concurrent calls to it are not made.
// This will increase the cache-hit ratio since the chances of a function being called previously is greater when
// we wait for the function to finish its execution.
// We use intern to make sure we use the same
string globalCacheKey = getCacheKey(expensiveFunction, localCacheKey);
lock (globalCacheKey)
{
if (cacheIsEnabled)
{
Object tempCacheValue;
switch (cacheLocation)
{
case CacheLocation.Cache:
tempCacheValue = HttpContext.Current.Cache[globalCacheKey];
break;
case CacheLocation.Request:
tempCacheValue = HttpContext.Current.Items[globalCacheKey];
break;
case CacheLocation.Session:
tempCacheValue = HttpContext.Current.Session[globalCacheKey];
break;
default:
throw new ArgumentOutOfRangeException("cacheLocation");
}
if (tempCacheValue is DefaultValue)
{
return default(T);
}
if (tempCacheValue != null)
{
return (T) tempCacheValue;
}
}
#if DEBUG
Debug.WriteLine("Start:" + expensiveFunction + " key: " + globalCacheKey);
var stop = Stopwatch.StartNew();
#endif
Object methodResult = expensiveFunction();
#if DEBUG
stop.Stop();
Debug.WriteLine("End:" + expensiveFunction + " time: " + stop.ElapsedMilliseconds);
#endif
if (cacheIsEnabled)
{
//The ASP.NET cache cannot store null-values. We therefore use this
// class-instance as a place-holder.
var cacheValue = methodResult ?? new DefaultValue();
switch (cacheLocation)
{
case CacheLocation.Cache:
//TODO: Implement support for callbacks to reload the cache when it expires.
HttpContext.Current.Cache.Insert(
globalCacheKey,
cacheValue,
null,
Cache.NoAbsoluteExpiration,
timeToCache,
CacheItemPriority.Normal, null);
break;
case CacheLocation.Request:
HttpContext.Current.Items[globalCacheKey] = cacheValue;
break;
case CacheLocation.Session:
HttpContext.Current.Session[globalCacheKey] = cacheValue;
break;
default:
throw new ArgumentOutOfRangeException("cacheLocation");
}
}
return (T) methodResult;
}
};
}
#region Nested type: CacheLocation
private enum CacheLocation
{
Cache,
Request,
Session
}
#endregion
#region Nested type: DefaultValue
private class DefaultValue
{
}
#endregion
}
}
Fork
0 Feedback
You must log in before you can give any feedback
You must log in before you can post a comment


1.2k
0




Mark 'asp.net' tag as 'like'
Mark 'asp.net' tag as 'ignore'