Caching Best Practices

Shortly after my ASP.NET 3.5 book was released in March 2008, I received feedback from Steven Smith who had reviewed the book. He pointed out that the code I am using to retrieve cached objects has a flaw. Fortunately, the problem is only likely to occur in high traffic sites, and is pretty easy to fix.

Here's the offending code from the book, presented on page 504 / 505:

VB.NET
If Cache("MyReview" + reviewId.ToString()) Is Nothing Then
  Using db As New PlanetWroxDataContext()
    myReview = (From r In db.Reviews _
                Where r.Id = reviewId _
                Select r).Single()
  End Using
  Cache("MyReview" + reviewId.ToString()) = myReview
End If

myReview = CType(Cache("MyReview" + reviewId.ToString()), Review)

C#
if (Cache["MyReview" + reviewId.ToString()] == null)
{
  using (PlanetWroxDataContext db = new PlanetWroxDataContext())
  {
    myReview = (from r in db.Reviews
                    where r.Id == reviewId
                    select r).Single();
  }
  Cache["MyReview" + reviewId.ToString()] = myReview;
}

myReview = (Review) Cache["MyReview" + reviewId.ToString()];

At first, this code may look fine to you. It checks for the existence of an object in the cache. If it's not there, it's created using a LINQ query and inserted in the cache. So, on the last line that retrieves the item from the cache and casts it to a Review, the object must always be there. Or so you think.....

What could happen is that in between the If call at the top of each code block, and the last line of code that retrieves the item, the cached item could actually be removed from the cache. So, while the first If check may tell you the item is there, the cast at the end may actually fail if the item has been removed in the mean time.

The fix is easy though; store the item in a variable and use that instead to do your checking and casting.

VB.NET
Dim cacheItem As Object = TryCast(Cache("MyReview" + reviewId.ToString()), Review)

If cacheItem Is Nothing Then

  Using db As New PlanetWroxDataContext()
    cacheItem = (From r In db.Reviews _
                Where r.Id = reviewId _
                Select r).Single()
    Cache("MyReview" + reviewId.ToString()) = cacheItem
  End Using

End If

myReview = CType(cacheItem, Review)

C#
object cacheItem = Cache["MyReview" + reviewId.ToString()] as Review;

if (cacheItem == null)
{
  using (PlanetWroxDataContext db = new PlanetWroxDataContext())
  {
    cacheItem = (from r in db.Reviews
                where r.Id == reviewId
                select r).Single();
    Cache["MyReview" + reviewId.ToString()] = cacheItem;
  }
}

myReview = (Review) cacheItem;

This code tries to get the cached item in a local variable called cacheItem. The as keyword in C# and the TryCast method in VB try to cast the requested item to a Review. If it succeeds, a valid instance of a Review is stored in cacheItem. However, if the code doesn't succeed, because the item in the cache doesn't exist or is not a review, then both as in C# and TryCast in VB.NET result in cacheItem getting a value of null / Nothing.

The remainder of the code is similar; LINQ is used to get the requested Review which is then assigned to the cacheItem variable and stored in the cache for later use. At the end, the myReview variable gets a value by casting the cacheItem instance to a Review.

It's an issue I was aware of before writing the book, so it never should have made it in the book in the first place. I'll be sure to correct it in case there's a reprint.

References


Where to Next?

Wonder where to go next? You can read existing comments below or you can post a comment yourself on this article.

Doc ID 463
Full URL https://imar.spaanjaars.com/463/caching-best-practices
Short cut https://imar.spaanjaars.com/463/
Written by Imar Spaanjaars
Date Posted 07/14/2008 15:40

Talk Back! Comment on Imar.Spaanjaars.Com

I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.

When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.

If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the Doc ID of the document.

(Plain text only; no HTML or code that looks like HTML or XML. In other words, don't use < and >. Also no links allowed.