C# – MemoryCache for Object, File and SQL Server – Part 1/2

Caching is always a must-to-have feature in all applications, especially in database application. For example, I have to load a nested set of about 10.000 items from database, build up their path then load them to control (combo box, list box, etc…). The query to get 10.000 entries from SQL Server takes not so long to finish but let’s think about the case that we have many users working at the same time, server will be almost overloaded for processing this query parallel. The CPU and RAM usage go quickly up and SQL Server will react slower than normal. Of course, if you own a good server, there are maybe small problem but for a small server it’ll be a big problem and there is no benefit that the program make a same query for many times without any changes from database. In addition to server performance, the program performance should be also discussed. The result of this query will be mapped into objects. This mapping process takes even longer than executing query to finish. At the end, we have such long time action which we can avoid to call frequently through cache.

In this blog post, I would like to make some basic examples for introducing new kind of cache which is available from .Net Framework 4 : MemoryCache. The MemoryCache implements the abstract ObjectCache class and represents the type that implements an in-memory cache. For more details you can read at MSDN http://msdn.microsoft.com/en-us/library/system.runtime.caching.memorycache.aspx . I wouldn’t discuss deeply how this class works but rather make concrete examples with it.

1. Object cache
The typical use of cache is storing an object in “clipboard” and gives the object back when required with its all information. In this example, I’ll create an object with a long time running method. This method takes time at the first call, the object will be then stored in cache. I call this method again in second time and now it gives the result back immediately. The object looks like this

internal class LongTimeRunningObject
{
	public string Name { get; set; }

	public int Seconds { get; set; }

	public void LongTimeRunningMethod()
	{
		Seconds = new Random().Next(1, 2);
		Thread.Sleep(new TimeSpan(0, 0, Seconds));
	}
}

The long time running method will takes randomly one or two seconds to finish (yeah, the duration is not really long but it’s ok for an example). Now I create an object of this class and store it in MemoryCache.

private static long CacheSimpleObject()
{
	string keyName = "LongTimeRunningObject";
	LongTimeRunningObject longTimeRunningObject = new LongTimeRunningObject();
	Stopwatch stopWatch = Stopwatch.StartNew();

	MemoryCache cache = MemoryCache.Default;

	if (cache.Contains(keyName))
	{
		longTimeRunningObject = (LongTimeRunningObject)cache.Get(keyName);
	}
	else
	{
		longTimeRunningObject = new LongTimeRunningObject();
		longTimeRunningObject.Name = "CacheSimpleObject";
		longTimeRunningObject.LongTimeRunningMethod();
		CacheItemPolicy cacheItemPolicy = new CacheItemPolicy();
		cacheItemPolicy.AbsoluteExpiration = new DateTimeOffset(DateTime.Now.AddSeconds(2));
		cache.Add(keyName, longTimeRunningObject, cacheItemPolicy);
	}

	Console.WriteLine(string.Format("Example: {0} - Long time running methods took {1} to finish", longTimeRunningObject.Name, longTimeRunningObject.Seconds));
	stopWatch.Stop();
	return stopWatch.ElapsedMilliseconds;
}

With the CacheItemPolicy I can define how long the object should be still located in MemoryCache. In this case, MemoryCache will delete the object after 2 seconds. If the object was deleted from MemoryCache or not loaded into MemoryCache, I need to initialize it again. If it was there, then hold it back. Let’s test to see how the cache works.

Console.WriteLine("Cache simple object");
Console.WriteLine(string.Format("First time running took {0} miliseconds to finish ", CacheSimpleObject()));
Console.WriteLine(string.Format("Second time running took {0} miliseconds to finish ", CacheSimpleObject()));
Thread.Sleep(new TimeSpan(0, 0, new Random().Next(2, 4)));
Console.WriteLine(string.Format("Third time running took {0} miliseconds to finish ", CacheSimpleObject()));

At the first and third time, the object was first created or just deleted from memory cache. Therefore it takes time for the method to finish his job. At the second time, object was “cached” and therefore the method doesn’t have to run again. The cache gives his result simply back.

2. List of object cache
The object cache works perfectly. However, in real case, we often need to cache a list of object and create a missing one when it’s not in the list anymore. That mean we provide a cache of a list, when this cache is required with an available item, he returns this item back. If not, he should call the Callback function to create this item. This feature can be accomplished with help of a MemoryCacheWrapper

internal class MemoryCacheWrapper<T> where T : class
{
	private readonly Func<string, T> callBack;
	private readonly MemoryCache memoryCache;

	public MemoryCacheWrapper(Func<string, T> factory, TimeSpan timeout, string name)
	{
		memoryCache = MemoryCache.Default;
		callBack = factory;
		this.TimeOut = timeout;
		this.Name = name;
	}

	public virtual T this[string key]
	{
		get
		{
			T value = default(T);

			if (memoryCache.Contains(key))
				value = (T)memoryCache[key];
			else
			{
				value = callBack(key);
				CacheItemPolicy policy = new CacheItemPolicy();
				policy.AbsoluteExpiration = new DateTimeOffset(DateTime.Now.Add(this.TimeOut));
				memoryCache.Set(key, value, policy);
			}
			return value;
		}
	}

	public TimeSpan TimeOut { get; set; }

	public string Name { get; set; }

	public void Add(object objectToCache, string key)
	{
		memoryCache.Add(key, objectToCache, DateTime.Now.AddMilliseconds(TimeOut.Ticks));
	}

	public T Get(string key)
	{
		return this[key];
	}

	public void Clear(string key)
	{
		memoryCache.Remove(key);
	}

	public bool Exists(string key)
	{
		return memoryCache.Get(key) != null;
	}

	public List<string> GetAll()
	{
		return memoryCache.Select(keyValuePair => keyValuePair.Key).ToList();
	}
}

This wrapper contains as his member variable one callback function, one MemoryCache object and for example TimeOut property to clean cache after an interval. Moreover, he provides some basic functions to access MemoryCache as List-way to make the access easier. The code below demonstrates how this wrapper works

private static void RunCustomCacheWrapper()
{
	Func<string, LongTimeRunningObject> cacheMissCreateCallback = CacheMissCreateCallback;
	MemoryCacheWrapper<LongTimeRunningObject> wrapper = new MemoryCacheWrapper<LongTimeRunningObject>(cacheMissCreateCallback, new TimeSpan(0, 0, 10), "CacheListOfLongTimeRunningObject");
	wrapper.Add(new LongTimeRunningObject() { Name = "One", Seconds = rnd.Next(2, 4) }, "One");
	wrapper.Add(new LongTimeRunningObject() { Name = "Two", Seconds = rnd.Next(2, 4) }, "Two");
	Console.WriteLine(string.Format("Name: {0}, Seconds: {1}", wrapper["Two"].Name, wrapper["Two"].Seconds));
	Console.WriteLine(string.Format("Name: {0}, Seconds: {1}", wrapper["Three"].Name, wrapper["Three"].Seconds));
	Console.WriteLine(string.Format("Name: {0}, Seconds: {1}", wrapper["Four"].Name, wrapper["Four"].Seconds));
}

private static LongTimeRunningObject CacheMissCreateCallback(string key)
{
	return new LongTimeRunningObject() { Name = "Name" + DateTime.Now.Ticks.ToString(), Seconds = rnd.Next(5, 10) };
}

The object with key “One” and “Two” were intentionally added to the list. It’s clearly accessible, but the object with key “Three” and “Four” was not in the cache. I have never added them before I access them from the cache. In this missing case, the Callback function will be called to add these missing one. If you access the key “Three” again, you’ll see that you got the same object created before

MemoryCacheWrapper

So now we reach the end of this part 1. The complete source code will be given at the end of Part 2. In part 2 we’ll discuss how we handle specific cases such as file cache and SQL Server cache.

2 thoughts on “C# – MemoryCache for Object, File and SQL Server – Part 1/2”

  1. Very clear article ! But I’m not seeing Part 2 anywhere 🙁
    I would be very interested in seeing more about it

Leave a Reply

Your email address will not be published. Required fields are marked *