When working with mapped objects, a number of places and situations call for a fast but not necessarily reliable caching mechanism. Although .NET is built to be a highly scalable stateless model, it doesn't hurt to use a little caching at the Web server as long as it doesn't consume unnecessary resources.
A good example of where this is helpful is loading something such as an object for the currently logged-in user. For each page view you get a cookie from the user that's their user ID, query the database, load it into the mapped object, and then display their first and last name in the header of the page. Using a simple caching mechanism eliminates many of these round trips and thus boosts scalability and reducing latency.
The problem with using a traditional cache that keeps items cached for a set amount of time is the amount of memory consumed by the server increases for each user. The entire point of a stateless model is to avoid situations where memory usage increases for all of the incoming connections to the Web server.
In most situations you can use what Microsoft gives you by putting the Cache object to work. In situations where you have a layer of abstraction between what you're caching and ASP.NET, this simply isn't possible without explicitly passing a reference to the Cache object to your data access components. Using good OR mapping practices such as abstracting your objects from the presentation layer make the Cache object unusable in a mapped application. This unfortunately means that if you need a caching mechanism available in any situation where your code isn't executing on a Web server, you'll need to buy or write your own caching object. Because you can't use the Cache object in your data access components, the solution to implementing a caching system in the stateless model is to use weak references to your objects. A weak reference is a way of referring to an object that tells the .NET runtime that if it needs the memory for something else to go ahead and overwrite the space where your object is in memory. If you attempt to access your weak referenced object after its memory has been consumed by another process, it will return the Visual Basic null constant Nothing. Listing 19-15 shows the WeakCache object, a cache that won't consume resources needed by other parts of the application.
Listing 19-15: The WeakCache Object
Imports System Imports System.Collections Namespace EnterpriseVB.VideoStore.Data Public Class WeakCache Private hash As Hashtable Private lastCleared As DateTime Public Sub New() hash = New Hashtable() lastCleared = DateTime.Now End Sub Public Property Items(ByVal key As String) As Object Get Dim ref As WeakReference If (hash(key) Is Nothing) Then Return Nothing End If If (lastCleared.Ticks + 1000000 < DateTime.Now.Ticks) Then Dim temp As Object lastCleared = DateTime.Now temp = CType(hash(key), WeakReference).Target hash.Clear() lastCleared = DateTime.Now Return temp End If Return CType(hash(key), WeakReference).Target End Get Set(ByVal Value As Object) hash(key) = New WeakReference(Value) End Set End Property End Class End Namespace
The WeakCache object creates an instance of a Hashtable and sets the lastCleared property to the current time. When the calling program sets a value in the Items indexed property, the WeakCache object stores it as a WeakReference.
When the calling program tries to get a value from the indexed property, a few things happen. As the property is accessed, the WeakCache object checks to make sure the value being requested is not Nothing. If it's Nothing, it returns immediately to avoid a NullReferenceException.
Next, it checks to see if 1,000,000 ticks have passed since the lastAccessed time. If they have, the WeakCache object clears the entire cache after updating the lastCleared time to the currentTime and returning the value for which the calling program has asked. You may be asking yourself the question, "If weak references don't hog memory, why ever clear the cache?"
There are two primary reasons why an occasional clearing of the cache is necessary. First, there has to be a check to make sure that cached objects have some kind of expiration date. It would be bad if a user whose account you locked out didn't expire until you rebooted the server. The second reason is that although the object is weak referenced, the key isn't. If you don't clear the Hashtable from time to time, these tiny little keys will pile up and consume a noticeable amount of memory toward a collision course with the greatest fear of all Web applications— paging. (Paging is when a machine runs out of available RAM memory and has to start writing to disk, which is substantially slower than RAM.)
The final thing the getter method of the Items property indexer does is actually return the value. All of the checks prior to this step are necessary to guard against NullReferenceExceptions, keeping the data fresh and avoiding a memory leak.
Optimal areas to put a weak cache in your code are areas where large amounts of data, such as a detail table, are loaded. Generally, in situations such as these, you want to cache the entire collection as a single entry in the WeakCache object so if one of the objects in the detail collection gets overwritten, the whole thing returns null. If you just cache the individual objects and scan through the collection to load the details, some records may have disappeared. You want an all-or-nothing situation.