Smarter Time-limited Caching

[4/10/2015 3:50:03 PM UPDATE: I updated the definition of the Caches class to support injecting dependencies into ICaches<T> implementations.]

[7/1/2017 2:44:53 PM UPDATE: Here’s a dinky little solution that illustrates the caching solution at work:   ConsoleApp1

Also, I hate reading my old posts. :) ]


In many applications, there is a need to globally cache some information for a certain period of time, and then periodically refresh that data the next time it is called upon.

And more often than not, there are several different types of information that we want to cache using this paradigm.  And wouldn’t it be cool if we could call a single method to reset all of the caches we’re holding on to?

Here’s how I like to make that kind of magic happen… 

First, an example..

Suppose I have a list of records in a database that I want to cache in memory for 2 minutes before refreshing the cache…

The goal, for me, is to be able to retrieve those records at any time with code that looks like this:

[TestMethod]
public void CanGetCachedRecordsFromDatabase()
{
	var records = Caches.Get<SomeDatabaseRecords>().Records;
	Assert.IsTrue(records.Any());
}

Manually resetting the record cache should be as simple as this:

[TestMethod]
public void CanResetRecordCache()
{
	Caches.Reset<SomeDatabaseRecords>();
}

And, supposing I wanted to reset all of the caches in the system, I’d do something like this:

[TestMethod]
public void CanResetAllCaches()
{
	Caches.ResetAll();
}

4/10/2015 3:53:29 PM UPDATE: It makes sense that a cache might require dependencies, like a database repository, for example..  With my code here, you can do something like this now:

[TestMethod]
public void CanGetCachedRecordsFromDatabaseWithDependency()
{
	var records = Caches.Get(() => new SomeDatabaseRecords(new SomeDatabaseRepository))).Records;
	Assert.IsTrue(records.Any());
}

 

Making it work

At the root of it all, are the three classes below which take advantage of System.Runtime.Caching.MemoryCache  and System.Lazy<T> . Don’t get too bogged down in the details if it looks confusing. Continue reading below to see how to implement my Caches class in your application..

Warning: The code below might seem complicated for some basic caching, but its complexity serves a couple of benefits:

  1. System.Runtime.Caching.MemoryCache requires the use of magic strings. The code above eliminates the chance of that getting in the way.
  2. Using System.Runtime.Caching.MemoryCache  properly can be kind of confusing.. This is a once and done system that prevents code duplication errors down the road when you need multiple caches in your system.
  3. Being able to reset all of your in-memory caches at once is a pretty handy feature!

So….

Caches.cs

public class Caches
{
	public static T Get<T>() where T : ICaches<T>, new()
	{
		var cache = new T();
		return Get(cache);
	}

	private static T Get<T>(T cache) where T : ICaches<T>, new()
	{
		var init = new Lazy<T>(cache.Init);
		var result =
			(Lazy<T>)
				MemoryCache.Default.AddOrGetExisting(cache.Name(), init, DateTimeOffset.Now.AddMinutes(cache.Minutes()));

		return (result ?? init).Value;
	}

	public static T Get<T>(Func<T> create) where T : ICaches<T>, new()
	{
		var cache = create();
		return Get(cache);

	}

	public static void Reset<T>() where T : ICaches<T>, new()
	{
		var cache = new T();
		var name = cache.Name();
		Reset(name);
	}

	public static void Reset(string name)
	{
		MemoryCache.Default.Remove(name);
	}

	public static IEnumerable<ICaches> Find()
	{
		var caches = typeof (ICaches).Assembly.GetTypes().Where(p => typeof(ICaches).IsAssignableFrom(p) && !p.IsInterface).Select(
			cache => Activator.CreateInstance(cache) as ICaches).Where(cache => cache != null);
		return caches;
	}

	public static void ResetAll()
	{
		var caches = Find();
		foreach (var cache in caches)
		{
			Reset(cache.Name());
		}
	}
}

ICaches.cs

public interface ICaches<T> : ICaches
{
	T Init();

}

public interface ICaches
{
	string Name();
	int Minutes();
}

Now, Implementing a cache like the original example above is as simple as creating a class that inherits from ICaches<T> . So..

public class SomeDatabaseRecords : ICaches<SomeDatabaseRecords>
{
	public SomeDatabaseRecords()
	{
		Records = new List<string>();
	}
	public List<string> Records { get; set; }
	public SomeDatabaseRecords Init()
	{
		Records = SomeDatabaseRepository.GetRecords();
		return this;
	}

	public string Name()
	{
		return "Records";
	}

	public int Minutes()
	{
		return 2;
	}
}

Hopefully it’s easy to see what’s happening there… Essentially, the Init method is called any time the cache needs to be updated from the database… Name is the magic string that MemoryCache requires.. and Minutes is the number of minutes that the cache will remain in memory before it gets refreshed from the database.

Simple, but effective..

Don’t forget to read my other other post if your’e new to MemoryCache.