2. Adding Items to and Retrieving Items from the Cache
To add an item to the cache, you can use the simple approach of
specifying just the key for the item and the value to cache as
parameters to the Add method. The item
is cached with a never expired lifetime, and normal priority. If you
want more control over the way an item is cached, you can use the
other overload of the Add method, which
additionally accepts a value for the priority, a reference to a
callback that will execute when the cached item expires, and an array
of expirations that specify when the item should expire.
Possible values for the priority, as defined in the CacheItemPriority enumeration, are None, Low, Normal, High, and NotRemovable. In addition to the NeverExpired value for the expirations, you can
use AbsoluteTime, SlidingTime,
FileDependency, and ExtendedFormatTime expirations. If you create
an array containing more than one expiration instance, the block will
expire the item when any one of these indicates that it has
expired.
The example starts by obtaining a reference to an instance of a
CacheManager—in this case one that has
no backing store defined in its configuration (or, to be more precise,
it has the NullBackingStore class
defined) and so uses only the in-memory cache. It stores this
reference as the interface type ICacheManager.
Next, it calls a separate routine that adds items to the cache
and then displays the contents of the cache.
// Resolve the default CacheManager object from the container.
// The actual concrete type is determined by the configuration settings.
// In this example, the default is the InMemoryCacheManager instance.
ICacheManager defaultCache
= EnterpriseLibraryContainer.Current.GetInstance<ICacheManager>();
// Store some items in the cache and show the contents using a separate routine.
CacheItemsAndShowCacheContents(defaultCache);
The CacheItemsAndShowCacheContents routine uses the
cache manager passed to it; in this first example, this is the
in-memory only cache manager. However, the code
to add items to the cache and manipulate the cache is (as you would
expect) identical for all configurations of cache managers. Notice
that the code defines a set of string values that it uses as the cache
keys. This makes it easier for the code later on to examine the
contents of the cache. This is the declaration of the cache keys array
and the first part of the code in the CacheItemsAndShowCacheContents
routine.
// Declare an array of string values to use as the keys of the cached items.
string[] DemoCacheKeys
= {"ItemOne", "ItemTwo", "ItemThree", "ItemFour", "ItemFive"};
void CacheItemsAndShowCacheContents(ICacheManager theCache)
{
// Add some items to the cache using the key names in the DemoCacheKeys array.
theCache.Add(DemoCacheKeys[0], "Some Text");
theCache.Add(DemoCacheKeys[1],
new StringBuilder("Some text in a StringBuilder"));
theCache.Add(DemoCacheKeys[2], 42, CacheItemPriority.High, null,
new NeverExpired());
theCache.Add(DemoCacheKeys[3], new DataSet(), CacheItemPriority.Normal,
null, new AbsoluteTime(new DateTime(2099, 12, 31)));
// Note that the next item will expire after three seconds
theCache.Add(DemoCacheKeys[4],
new Product(10, "Exciting Thing", "Useful for everything"),
CacheItemPriority.Low, null,
new SlidingTime(new TimeSpan(0, 0, 3)));
// Display the contents of the cache.
ShowCacheContents(theCache);
...
In the code shown above, you can see that the CacheItemsAndShowCacheContents routine uses the
simplest overload to cache the first two items; a String value and an
instance of the StringBuilder class.
For the third item, the code specifies the item to cache as the
Integer value 42 and indicates that it should have high priority (it
will remain in the cache after lower priority items when the cache has to
be minimized due to memory or other constraints). There is no callback
required, and the item will never expire.
The fourth item cached by the code is a new instance of the
DataSet class, with normal priority and no callback. However, the expiry of the cached item is set to an
absolute date and time (which should be well after the time that you
run the example).
The final item added to the cache is a new instance of a custom
class defined within the application. The Product class is a simple class with just three
properties: ID, Name, and Description. The class has a constructor that
accepts these three values and sets the properties in the usual way.
It is cached with low priority, and a sliding time expiration set to
three seconds.
The final line of code above calls another routine named ShowCacheContents
that displays the contents of the cache. Not shown here
is code that forces execution of the main application to halt for five
seconds, redisplay the contents of the cache, and repeat this process
again. This is the output you see when you run this example.
The cache contains the following 5 item(s):
Item key 'ItemOne' (System.String) = Some Text
Item key 'ItemTwo' (System.Text.StringBuilder) = Some text in a StringBuilder
Item key 'ItemThree' (System.Int32) = 42
Item key 'ItemFour' (System.Data.DataSet) = System.Data.DataSet
Item key 'ItemFive' (CachingExample.Product) = CachingExample.Product
Waiting for last item to expire...
Waiting... Waiting... Waiting... Waiting... Waiting...
The cache contains the following 5 item(s):
Item key 'ItemOne' (System.String) = Some Text
Item key 'ItemTwo' (System.Text.StringBuilder) = Some text in a StringBuilder
Item key 'ItemThree' (System.Int32) = 42
Item key 'ItemFour' (System.Data.DataSet) = System.Data.DataSet
Item with key 'ItemFive' has been invalidated.
Waiting for the cache to be scavenged...
Waiting... Waiting... Waiting... Waiting... Waiting...
The cache contains the following 4 item(s):
Item key 'ItemOne' (System.String) = Some Text
Item key 'ItemTwo' (System.Text.StringBuilder) = Some text in a StringBuilder
Item key 'ItemThree' (System.Int32) = 42
Item key 'ItemFour' (System.Data.DataSet) = System.Data.DataSet
You can see in this output that the cache initially contains the
five items we added to it. However, after a few seconds, the last one
expires. When the code examines the contents of the cache again, the
last item (with key ItemFive) has
expired but is still in the cache. However, the code detects this and
shows it as invalidated. After a further five seconds, the code checks
the contents of the cache again, and you can see that the invalidated
item has been removed.
Note
Depending on the performance of your machine, you
may need to change the value configured for the expiration poll
frequency of the cache manager in order to see the invalidated item
in the cache and the contents after the scavenging cycle
completes.
The example you've just seen displays the contents of the cache, indicating which items are
still available in the cache, and which (if any) are in the cache
but not available because they are waiting to be scavenged. So
how can you tell what is actually in the cache and
available for use? In the time-honored way, you might like to answer
"Yes" or "No" to the following questions:
-
Can I use the Contains
method to check if an item with the key I specify is available
in the cache?
-
Can I query the Count
property and retrieve each item using its index?
-
Can I iterate over the collection of cached items, reading
each one in turn?
If you answered "Yes" to any of these, the bad news is that
you are wrong. All of these are false. Why? Because the cache is
managed by more than one process. The cache manager you are using is
responsible for adding items to the cache and retrieving them through
the public methods available to your code. However, a background
process also manages the cache, checking for any items that have
expired and removing (scavenging) those that are no longer valid.
Cached items may be removed when memory is scarce, or in response to
dependencies on other items, as well as when the expiry date and
time you specified when you added an item to the cache has
passed.
So, even if the Contains method
returns true for a specified cache
key, that item might have been invalidated and is only in the cache until the next scavenging operation.
You can see this in the output for the previous example, where the
two waits force the code to halt until the item has been flagged as
expired, and then halt again until it is scavenged. The actual delay
before scavenging takes place is determined by the expiration poll
frequency configuration setting of the cache manager. In the
previous example, this is 10 seconds.
The correct approach to extracting cached items is to simply
call the GetData method and check
that it did not return null. However,
you can use the Contains method to
see if an item was previously cached and will (in most cases) still
be available in the cache. This is efficient, but you must still
(and always) check that the returned item is not null after you
attempt to retrieve it from the cache.
The code used in the examples to read the cached items depends
on the fact that we use an array of cache keys throughout the
examples, and we can therefore check if any of these items are in
the cache. The code we use is shown here.
void ShowCacheContents(ICacheManager theCache)
{
if (theCache.Count > 0)
{
Console.WriteLine("Cache contains the following {0} item(s):",
theCache.Count);
// Cannot iterate the cache, so use the five known keys
foreach (string key in DemoCacheKeys)
{
if (theCache.Contains(key))
{
// Try and get the item from the cache
object theData = theCache.GetData(key);
// If item has expired but not yet been scavenged, it will still show
// in the count of the number of cached items, but the GetData method
// will return null.
if (null != theData)
Console.WriteLine("Item key '{0}' ({1}) = {2}", key,
theData.GetType().ToString(), theData.ToString());
else
Console.WriteLine("Item with key '{0}' has been invalidated.", key);
}
}
}
else
{
Console.WriteLine("The cache is empty.");
}
}