Karl Seguin

Sponsors

The Lounge

Wicked Cool Jobs

Advertisement

Images in this post missing? We recently lost them in a site migration. We're working to restore these as you read this. Should you need an image in an emergency, please contact us at imagehelp@codebetter.com
Tweaking .NET's Cache API

Much like I typically wrap my DataReaders in the CSLA.NET's SafeDataReader to better handle DB Null's, I've taken to wrapping .NET's built-in Cache object in order to improve the API, decreasing my code's coupling and improve testability.

Three things always bug me about the Cache's API:
   1 - Inserting null data throws an exception
   2 - Never seems to have the right overload
   3 - Repetitiveness of creating composite keys


First thing to do is create an ICacheManager interface, we’ll keep it simple:


namespace CodeBetter
{
  public interface ICacheManager
  {
    void Insert(string key, object data, TimeSpan slidingExpiration, params object[] args);
    void Insert(string key, object data, DateTime absoluteExpiration, params object[] args);
    void Insert(string key, object data, CacheDependency dependency, DateTime absoluteExpiration, TimeSpan slidingExpiration, Priority priority, params object[] args);
    T Get<T>(string key, params object[] args);
  }
}


In my experience, I almost always reuse the same Insert overloads - I rarely ever set a priority (let .NET manage its own memory usage I say) or specify both an Absolute and Sliding expiry, and not too frequently specify a CacheDependency. So I create a couple Insert that'll get used 95% of the time without having to specify a bunch of null/defaults AND a one that lets me specify everything.

Internally I handle null data by simply returning out of the insert:

if (data == null)
{
  return;
}


Finally, you probably noticed that in each case above I have a params object[] args. That's because I often find myself using composite keys, something like:

string key = string.Format("UserById:{0}", userId);

By letting my insert handle that, my API is a bit cleaner. (A nice thing about params is that you don't have to specify a property at all)

Here are a couple sample calls:

Insert("UserById:{0}", user, TimeSpan.FromMinutes(10), user.Id);
Insert("SystemConfiguration", configData, DateTime.Now.AddHours(2));


And here's the relevant code:

public class InMemoryCacheManager : ICacheManager
{
   public void Insert(string key, object data, TimeSpan slidingExpiration, params object[] args)
   {
      //call overload...
   }
   public void Insert(string key, object data, DateTime absoluteExpiration, params object[] args)
   {
      //call overload...
   }
   public void Insert(string key, object data, CacheDependency dependency, DateTime absoluteExpiration, TimeSpan slidingExpiration, Priority priority, params object[] args)
   {
      if (key == null)
      {
          throw new ArgumentNullException("key");
      }
      if (data == null)
      {
          return;
      }
      if (args.Length > 0)
      {
          key = string.Format(key, args);
      }
      HttpRuntime.Cache.Insert(key, data, dependency, absoluteExpiration, slidingExpiration, priority);
   }

   public T Get<T>(string key, params object[] args)
   {
      if (args.Length > 0)
      {
          key = string.Format(key, args);
      }
      return (T)HttpRuntime.Cache.Get(key);           
   }   
}



Although I find the syntactical improvements pleasant, the real benefit is with the introduction of the ICacheManager interface I've reduced my system's coupling and can easily create an IMemCachedMemoryManager. It's also easy to Mock up, and plays nice with tools like StructureMap:

MockRepository mock = new MockRepository();
ICacheManager cacheManager = _mock.CreateMock<ICacheManager>();
IDataStore dataStore = _mock.CreateMock<IDataStore>();
ObjectFactory.InjectStub(typeof(IDataStore), dataStore);
ObjectFactory.InjectStub(typeof(ICacheManager), cacheManager);

User user = new User { UserId = 34 };
using (_mock.Ordered())
{              
    Expect.Call(dataStore.LoadUserFromCredentials("username", "password")).Return(user);
    _cacheManager.Insert("UseById:{0}", user, TimeSpan.FromMinutes(10), user.UserId);
}           
mock.ReplayAll();
Assert.AreSame(user, new Repository().CreateUserFromCredentials("username", "password"));
mock.VerifyAll();

 


Posted Sun, Jun 3 2007 4:36 PM by karl

[Advertisement]

Comments

Jeremy D. Miller wrote re: Tweaking .NET's Cache API
on Sun, Jun 3 2007 6:52 PM

Karl,

Forgot to tell you, I've got the Inject<T>(blah) method coming in 2.1

karl wrote re: Tweaking .NET's Cache API
on Mon, Jun 4 2007 2:10 PM

sweet...typeof() is sooo last year

Jim Bonnie wrote re: Tweaking .NET's Cache API
on Mon, Jun 4 2007 2:17 PM

Hi Karl,

Nice wrapper functions.

I am just looking at caching for the first time, and was wondering about considerations around the cache key size. Is there a limit on the size of the key ? Is cache performance impacted by the key size.

I have some rather large strings, 100 plus chars that give me uniqueness. Could this present an issue ?

karl wrote re: Tweaking .NET's Cache API
on Mon, Jun 4 2007 7:58 PM

Jim,

The cache internally uses a Hashtable collection which uses a very efficient hashing algorithm (though I don't know the details, just going by what I've heard).  Unless we are talking about truly large string (like say an 2000 character XML document), I doubt you'll notice much of a hit.

Using a Hashtable, it'd be pretty easy to test though. Load up a hashtable, and try inserting  and retrieving 100 000 entries. Try with different key lengths and measure the performance.

Chris O. wrote re: Tweaking .NET's Cache API
on Wed, Jun 6 2007 11:06 PM

Has anyone else found themselves wishing there was a caching layer for ADO.NET? After becoming involved with ASP.NET and using the HttpRuntime.Cache extensively, I've decided creating a similar class for ADO.NET is a must!

Chris O. wrote re: Tweaking .NET's Cache API
on Wed, Jun 6 2007 11:08 PM

Jim, I was under the impression that after .NET 2.0, most Hashtable objects were converted to generic dictionary instead. No? It would certainly make sense.

Karl Seguin wrote Scale Cheaply - Memcached
on Mon, Jul 7 2008 7:45 PM

I generally subscribe to the attitude that premature optimizations are evil, but I strongly believe that

Community Blogs wrote Scale Cheaply - Memcached
on Mon, Jul 7 2008 8:18 PM

I generally subscribe to the attitude that premature optimizations are evil, but I strongly believe that

Devlicio.us