ny application you write may require some sort of caching to meet the performance requirements of the business. Until the release of Enterprise Library, developers were forced to roll out their own caching implementations; however, developers now finally have a standard out-of-the-box caching solution that is fully tested?and it’s free! The Caching Application Block provides a flexible and extensible caching mechanism that can be used at any or all layers of an application. It supports in-memory, database, or isolated storage stores for persisting the cached data. It also includes a set of very easy-to-use APIs that let you incorporate standard caching operations into your applications without needing to learn the complexities of the caching implementation. This article introduces the Caching Application Block and shows examples of how to use it to write robust caching implementations.
The Caching Application Block
By default, the .NET Framework includes support for caching in web applications and web services through the classes contained in the System.Web.Cache namespace. However, not everyone’s writing web applications; other application types can also sometimes benefit from caching to increase performance and availability. For example, if you are creating a smart client application that uses locally cached references to create requests and support offline operations, the built-in ASP.NET-based caching solution might not work. The same applies to a Windows service or console-based application in which you need to implement a local cache to improve performance.
What You Need |
Visual Studio 2005 Professional RTM and Enterprise Library 3.1 May 2007 |
![]() | ? |
Figure 1. Caching Application Block Dependencies: In addition to the core assemblies, the Caching Application Block references data access block and cryptography assemblies when required. |
The Caching Application Block solves such problems. After you install Enterprise Library 3.1 May 2007 edition, you can find the Caching Application Block assemblies in the
As Figure 1 shows, if you have the need to encrypt/decrypt cached data you must also reference the cryptography application block.
A Simple Example
Here’s a simple example in which you can follow along with the steps involved in inserting, retrieving, and removing a simple scalar item from the cache. To start, create a new Visual C# Windows Application named CachingBlockExample, and add references to the Microsoft.Practices.EnterpriseLibrary.Caching.dll and Microsoft.Practices.EnterpriseLibrary.Common.dll assemblies by navigating to the
In the main form code-behind, import the following caching related namespaces:
using Microsoft.Practices.EnterpriseLibrary.Caching; using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
After that, declare a private variable named _manager:
private CacheManager _manager;
Next add an application configuration file (app.config) to the project and modify it to look as follows:
These configuration file settings instruct the Caching Application Block to use the in-memory backing store (also known as null backing store) for persisting cached items. Note that the configuration also sets the inMemory cache provider as the default cache manager:
As part of the
In the form’s constructor, populate the CacheManager variable by invoking the CacheFactory.GetCacheManager() method:
public Form1() { InitializeComponent(); _manager = CacheFactory.GetCacheManager(); }
The GetCacheManager() method returns a reference to the default CacheManager object. As the in-memory cache manager is the default for this example, that’s sufficient. If you define multiple cache managers, you can get a reference to a specific cache manager by supplying the name of the cache manager (as specified in the configuration file) as an argument to the overloaded GetCacheManager() method.
Now you are ready to add code to store, retrieve, and remove an item from the cache. The code below begins by showing the operations required for scalar items:
private void btnAddScalarItemToCache_Click( object sender, EventArgs e) { lstResults.Items.Clear(); _manager.Add("12345", "David"); lstResults.Items.Add("Scalar item added"); } private void btnRetrieveScalarItem_Click( object sender, EventArgs e) { lstResults.Items.Clear(); object value = _manager.GetData("12345"); if (value != null) lstResults.Items.Add((string)value); else lstResults.Items.Add("No item found"); } private void btnRemoveScalarItem_Click( object sender, EventArgs e) { lstResults.Items.Clear(); _manager.Remove("12345"); lstResults.Items.Add ("Scalar item identified by 12345 removed from the cache"); }
![]() | ? |
Figure 2. Retrieving a Scalar Item from the Cache: After adding an item to the cache, you can easily retrieve and remove that item from the cache using the cache key. |
The CacheManager.Add() method has two overloads; the preceding code uses the one that accepts a cache key and the actual data to be cached as arguments:
_manager.Add("12345", "David");
After adding the item, you can retrieve it using the GetData() method that accepts the cache key as an argument:
object value = _manager.GetData("12345");
Figure 2 shows the output produced when you retrieve the cached item after persisting it through the “Add a scalar item to the cache” button.
Persisting Complex Data
You’ve seen how to cache, retrieve, and delete simple scalar values, but caching complex data is a little different. To illustrate, create a custom class named Employee that acts as a placeholder for storing the employee details:
using System; namespace CachingBlockExample { [Serializable] public class Employee { private string _employeeID; public string EmployeeID { get { return _employeeID; } set { _employeeID = value; } } private string _name; public string Name { get { return _name; } set { _name = value; } } private int _age; public int Age { get { return _age; } set { _age = value; } } } }
Now that you have created the Employee class, add three buttons to the form that store, retrieve, and remove Employee objects from the cache (see Figure 3 for an example). Here’s the Click event code for each button:
private void btnAddObjectToCache_Click(object sender, EventArgs e) { lstResults.Items.Clear(); Employee emp = new Employee(); emp.EmployeeID = "1234"; emp.Name = "David"; emp.Age = 20; _manager.Add(emp.EmployeeID, emp, CacheItemPriority.High, new EmployeeCacheRefreshAction(), new AbsoluteTime(DateTime.Now.AddMinutes(60))); lstResults.Items.Add("Object added"); } private void btnRetrieveObject_Click(object sender, EventArgs e) { lstResults.Items.Clear(); object emp = _manager.GetData("1234"); if (emp != null) { Employee empTemp = (Employee)emp; lstResults.Items.Add(empTemp.EmployeeID); lstResults.Items.Add(empTemp.Name); lstResults.Items.Add(empTemp.Age.ToString()); } else lstResults.Items.Add("No item found"); } private void btnRemoveObject_Click(object sender, EventArgs e) { lstResults.Items.Clear(); _manager.Remove("1234"); lstResults.Items.Add ("Object identified by 1234 removed from the cache"); }
Note the additional arguments passed to the CacheManager.Add() method:
![]() | ? |
Figure 3. Retrieving Complex Data: The figure shows output produced by first storing and then retrieving an Employee object from the cache. |
_manager.Add(emp.EmployeeID, emp, CacheItemPriority.High, new EmployeeCacheRefreshAction(), new AbsoluteTime( DateTime.Now.AddMinutes(60)));
In addition to the cache key (the EmployeeID) and the data (the emp variable), the overloaded CacheManager.Add() method also accepts a CacheItemPriority enumeration value, a custom object used to refresh the cached item when the item is removed from the cache, and the absolute time at which the cached item needs to expire.
With the buttons and code in place, if you now run the application, you’ll see output somewhat similar to Figure 3:
As the name suggests, the CacheItemPriority enumeration allows you to specify priority levels for the cached items. The CacheItemPriority enumeration exposes the values shown in Table 1:
Value | Description |
High | High priority for scavenging, meaning that the items with this priority level are the least likely to be deleted from the cache as the server frees the system memory. |
Low | Low priority for scavenging, meaning that the items with this priority are the most likely to be deleted from the cache as the server frees the system memory. |
None | No priority associated with scavenging. |
Normal | Normal priority for scavenging, meaning that the items with this priority are likely to be deleted from the cache as the server frees the system memory only after those items with the “Low” priority. |
NotRemovable | Non-removable priority for scavenging, meaning that these items are not automatically deleted from the cache as the server frees the system memory. However, even items with this priority are removed according to the item’s absolute or sliding expiration time. |
Implementing Custom Refreshing Behavior
You have full control over how or whether you want to refresh of cached items when they’re removed from the cache. For example, you might want to invoke a custom method containing logic to replace a removed item by inserting a new item onto the cache. To accomplish this, follow these steps:
- Create a custom class that implements the ICacheItemRefreshAction interface:
- Implement the Refresh method, writing code to replenish the cache with the new item:
- Supply the custom class as an argument to the CacheManager.Add() method when adding the item to the cache:
public class EmployeeCacheRefreshAction : ICacheItemRefreshAction
public void Refresh(string key, object expiredValue, CacheItemRemovedReason removalReason) { // When the object has been removed from cache, add the new item }
_manager.Add(emp.EmployeeID, emp, CacheItemPriority.High, new EmployeeCacheRefreshAction(), new AbsoluteTime(DateTime.Now.AddMinutes(60)));
In the preceding code, note that the fourth argument to the Add() method is the custom class you created in step 1. That’s all there is to implementing a custom refresh action for a cached item.
Using SQL Server as the Backing Store
As mentioned earlier, in addition to the default null backing store (in-memory backing store), you also have the option of using SQL Server as the caching store. As you would expect, this approach is very scalable, but incurs additional overhead to store and retrieve cached items from the SQL Server database. To be able to use the SQL Server as the backing store, you need to go through the steps below:
- Execute the SQL script file CreateCachingDatabase.sql that you’ll find in the
EntLib3SrcApp BlocksSrcCachingDatabaseScripts folder. The script creates a database named Caching containing a table named CacheData. The CacheData table stores all the cached data. In addition, the Caching database contains several stored procedures that the cache provider uses to perform actions against the database. - Reference the Microsoft.Practices.EnterpriseLibrary.Caching.Database.dll assembly from within your project, in addition to the Microsoft.Practices.EnterpriseLibrary.Caching.dll and Microsoft.Practices.EnterpriseLibrary.Common.dll assemblies.
- Modify your app.config file to instruct the Caching Application Block to use the SQL Server as the backing store. As you can see, this configuration-driven approach provides a flexible caching implementation wherein you can seamlessly switch the backing store without having to touch a single line of code. Listing 1 shows the configuration file entries required to use a local SQL Server as the caching backing store.
- After modifying app.config with the code in Listing 1 and executing the solution, you should get?exactly the same results as running it with the in-memory store.
In addition to the in-memory and SQL Server caching stores, you can also implement custom backing stores such as an XML file or an Oracle database by creating a custom caching class that derives from the Microsoft.Practices.EnterpriseLibrary.Caching.IBackingStore interface. This custom class must implement the methods responsible for interacting with the underlying persistence mechanisms to store and retrieve cached items.
This article discussed the use of the Caching Application Block in .NET applications for creating high performance and fault-tolerant .NET applications. You have seen examples of how to use the Caching Application Block for caching both simple scalar data (such as strings or integers), and complex data (objects). In addition, you saw the flexibility of the Caching Application Block by discussing the steps involved in switching the backing store from the in-memory backing store to a SQL Server-based backing store.