7. Refreshing the Cache
So far, when we used the Add
method to add items to the cache, we passed a null value for the
refreshAction parameter. You can use
this parameter to detect when an item is removed from the cache, and
discover the value of that item and the reason it was
removed.
You must create a class that implements the ICacheItemRefreshAction interface, and contains
a method named Refresh that accepts as parameters the key of the item
being removed, the value as an Object
type, and a value from the CacheItemRemovedReason enumeration. The values
from this enumeration are Expired,
Removed (typically by your code or a dependency), Scavenged (typically in response to shortage of
available memory), and Unknown (a
reserved value you should avoid using).
Therefore, inside your Refresh
method, you can query the parameter values passed to it to obtain the
key and the final cached value of the item, and see why it was removed
from the cache. At this point, you can make a decision on what to do
about it. In some cases, it may make sense to insert the item into the
cache again (such as when a file on which the item depends has
changed, or if the data is vital to your application). Of course, you should generally only do
this if it expired or was removed. If items are being scavenged
because your machine is short of memory, you should think carefully
about what you want to put back into the cache!
The example, Detect and refresh expired or removed
cache items, illustrates how you can capture items being removed from the cache,
and re-cache them when appropriate. The example uses the following
implementation of the ICacheItemRefreshAction interface to handle the
case when the cache contains instances of the Product type. For a general situation where you
cache different types, you would probably want to check the type
before attempting to cast it to the required target type. Also notice
that the class carries the Serializable
attribute. All classes that implement the ICacheItemRefreshAction interface must be
marked as serializable.
[Serializable]
public class MyCacheRefreshAction : ICacheItemRefreshAction
{
public void Refresh(string key, object expiredValue,
CacheItemRemovedReason removalReason)
{
// Item has been removed from cache. Perform desired actions here, based on
// the removal reason (for example, refresh the cache with the item).
Product expiredItem = (Product)expiredValue;
Console.WriteLine("Cached item {0} was expired in the cache with "
+ "the reason '{1}'", key, removalReason);
Console.WriteLine("Item values were: ID = {0}, Name = '{1}', "
+ "Description = {2}", expiredItem.ID,
expiredItem.Name, expiredItem.Description);
// Refresh the cache if it expired, but not if it was explicitly removed
if (removalReason == CacheItemRemovedReason.Expired)
{
CacheManager defaultCache = EnterpriseLibraryContainer.Current.GetInstance
<CacheManager>("InMemoryCacheManager");
defaultCache.Add(key, new Product(10, "Exciting Thing",
"Useful for everything"), CacheItemPriority.Low,
new MyCacheRefreshAction(),
new SlidingTime(new TimeSpan(0, 0, 10)));
Console.WriteLine("Refreshed the item by adding it to the cache again.");
}
}
}
To use the implementation of the ICacheItemRefreshAction interface, you simply
specify it as the refreshAction
parameter of the Add method when you
add an item to the cache. The example uses the following code to cache
an instance of the Product class that
will expire after three seconds.
defaultCache.Add(DemoCacheKeys[0], new Product(10, "Exciting Thing",
"Useful for everything"),
CacheItemPriority.Low, new MyCacheRefreshAction(),
new SlidingTime(new TimeSpan(0, 0, 3)));
The code then does the same as the earlier examples: it displays
the contents of the cache, waits five seconds for the item to expire,
displays the contents again, waits five more seconds until the item is
scavenged, and then displays the contents for the third time.
However, this time the Caching block executes the Refresh method of our ICacheItemRefreshAction callback as soon as the
item is removed from the cache. This callback displays a message
indicating that the cached item was removed because it had expired,
and that it has been added back into the cache. You can see it in the
final listing of the cache contents shown here.
The cache contains the following 1 item(s):
Item key 'ItemOne' (CachingExample.Product) = CachingExample.Product
Waiting... Waiting... Waiting... Waiting... Waiting...
The cache contains the following 1 item(s):
Item with key 'ItemOne' has been invalidated.
Cached item ItemOne was expired in the cache with the reason 'Expired'
Item values were: ID = 10, Name = 'Exciting Thing', Description = Useful for
everything
Refreshed the item by adding it to the cache again.
Waiting... Waiting... Waiting...
The cache contains the following 1 item(s):
Item key 'ItemOne' (CachingExample.Product) = CachingExample.Product
If you have configured a persistent backing store for a cache
manager, the Caching block will automatically load the in-memory
cache from the backing store when you instantiate that cache manager.
Usually, this will occur when the application starts up. This is an
example of proactive cache
loading. Proactive cache loading is useful if you know that the
data will be required, and it is unlikely to change much. Another
approach is to create a class with a method that reads data you
require from some data source, such as a database or an XML file, and
loads this into the cache by calling the Add method for each item. If you execute this
on a background or worker thread, you can load the cache without
affecting the interactivity of the application or blocking the user
interface.
Alternatively, you may prefer to use reactive cache
loading. This approach is useful for data that may or may not
be used, or data that is relatively volatile. In this case (if you are
using a persistent backing store), you may choose to instantiate the
cache manager only when you need to load the data. Alternatively, you
can flush the cache (probably when your application ends) and then
load specific items into it as required and when required. For
example, you might find that you need to retrieve the details of a
specific product from your corporate data store for display in your
application. At this point, you could choose to cache it if it may be
used again within a reasonable period and is unlikely to change during
that period.
The example, Load the cache proactively on
application startup, provides a simple demonstration of
proactive cache loading. In the startup code of your application you
add code to load the cache with the items your application will
require. The example creates a list of Product items, and then iterates through the
list calling the Add method of the
cache manager for each one. You would, of course, fetch the items to
cache from the location (such as a database) appropriate for your
own application. It may be that the items are available as a list,
or—for example—by iterating through the rows in a DataSet or a DataReader.
// Create a list of products - may come from a database or other repository
List<Product> products = new List<Product>();
products.Add(new Product(42, "Exciting Thing",
"Something that will change your view of life."));
products.Add(new Product(79, "Useful Thing",
"Something that is useful for everything."));
products.Add(new Product(412, "Fun Thing",
"Something that will keep the grandchildren quiet."));
// Iterate the list loading each one into the cache
for (int i = 0; i < products.Count; i++)
{
theCache.Add(DemoCacheKeys[i], products[i]);
}
Reactive cache loading simply means that you check if an item
is in the cache when you actually need it, and—if not—fetch it and
then cache it for future use. You may decide at this point to fetch
several items if the one you want is not in the cache. For example,
you may decide to load the complete product list the first time that
a price lookup determines that the products are not in the
cache.
The example, Load the cache reactively on
demand, demonstrates the general pattern for reactive
cache loading. After displaying the contents of the cache (to
show that it is, in fact, empty) the code attempts to
retrieve a cached instance of the Product class. Notice that this is a two-step
process in that you must check that the returned value is not
null.
If the item is in the cache, the code displays the values of
its properties. If it is not in the cache, the code executes a
routine to load the cache with all of the products. This routine
is the same as you saw in the previous example of loading the cache proactively.
Console.WriteLine("Getting an item from the cache...");
Product theItem = (Product)defaultCache.GetData(DemoCacheKeys[1]);
// You could test for the item in the cache using CacheManager.Contains(key)
// method, but you still must check if the retrieved item is null even
// if the Contains method indicates that the item is in the cache:
if (null != theItem)
{
Console.WriteLine("Cached item values are: ID = {0}, Name = '{1}', "
+ "Description = {2}", theItem.ID, theItem.Name,
theItem.Description);
}
else
{
Console.WriteLine("The item could not be obtained from the cache.");
// Item not found, so reactively load the cache
LoadCacheWithProductList(defaultCache);
Console.WriteLine("Loaded the cache with the list of products.");
ShowCacheContents(defaultCache);
}
After displaying the contents of the cache after loading the
list of products, the example code then continues by attempting once
again to retrieve the value and display its properties. You can see
the entire output from this example here.
The cache is empty.
Getting an item from the cache...
The item could not be obtained from the cache.
Loaded the cache with the list of products.
The cache contains the following 3 item(s):
Item key 'ItemOne' (CachingExample.Product) = CachingExample.Product
Item key 'ItemTwo' (CachingExample.Product) = CachingExample.Product
Item key 'ItemThree' (CachingExample.Product) = CachingExample.Product
Getting an item from the cache...
Cached item values are: ID = 79, Name = 'Useful Thing', Description = Something
that is useful for everything.
In general, the pattern for a function that performs reactive
cache loading is:
-
Check if the item is in the cache and the value returned
is not null.
-
If it is found in the cache, return it to the calling
code.
-
If it is not found in the cache, create or obtain the
object or value and cache it.
-
Return this new value or object to the calling
code.