Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

An Easy-To-Use CacheManager

0.00/5 (No votes)
13 Mar 2002 1  
A .NET class that manages ASP.NET data cache

Introduction

ASP.NET provides a System.Web.Caching.Cache class. Every ASP.NET application domain has an instance of this class, which is accessible through Page.Cache property. Cache is used as temporary in-memory storage. Like ASP Application variables, Cache is an application level memory storage, which stores data common to all user sessions in an application. Cache also performs its own thread management. You do not have to lock it before changing data stored in it and unlock it afterwards, as you must do with Application.

Another feature of cache is that you can set its expiration. A cached item can expire (be removed) at an absolute point of time, with a sliding time window, or invalidated when another cached item is removed, or a file is changed, or any file in a directory is changed.

Using Cache is easy. Basically, you only need to use Insert to store data and use Get to access data. But you have to decide if the data is already in the cache, which in turn requires that you have a scheme to build a key. So you still have to do some work. The CacheManager serves to shield you from the details of managing the cache key.

The CacheManager

In our scenario, we have to fetch data from a data source. It could be a SQL server or a webservice over the internet. The data to be fetched is large and the data at the source is not changed frequently. Therefore we do not want to fetch it too frequently. The data is deterministic, in the meaning that a given input always gives a given data set, except when data at data source has been changed, which should be the case in most real life applications.

We can use all the inputs to compose the key, and the fetched data as the value. In most cases, any part of the user identity is not part of the input for querying the data source. That means a cached item can be shared among all the user sessions that have the same inputs.

We want to also show the data in a DataGrid. Since many data rows may be returned, we want to use paging in DataGrid. With every page change, the DataGrid must be rebind. If data is not cached, it has to be fetched again from data source. In many cases with large dataset, combining DataGrid paging and server data caching is the only solution to the performance problems in ASP.NET. Of course you can design a custom paging solution that depends on a data source that returns just data for a specific page. But it may not perform better and requires a lot more effort to make.

The CacheManager class has only these public members:

public delegate object SourceDataDelegate(object[] parameters);

public event System.EventHandler PreGetFromSource;
public event System.EventHandler PostGetFromSource;

public object GetCachedData(string cacheCategory, int expireSeconds, 
    SourceDataDelegate getSourceData, object[] parameters)

Parameters for GetCachedData

cacheCategory: Distinguishes different data used in different part of the application.

expireSeconds: The expiration time for the cached item.

getSourceData: The delegate for CacheManager to callback when the data is not found in the cache.

parameters: The parameter used for the callback delegate. It is also used for compose the cache key together with the cacheCategory.

Here is a code snippet that shows how to use the CacheManager:

private void ShowData()
{
    DataTable table = GetDataFromCache();
    if (table != null) 
    {
        DataGrid1.DataSource = table.DefaultView ;
    }
    DataGrid1.DataBind(); 
}

private DataTable GetDataFromCache()
{
    string placeName = SearchText.Text;
    CacheManager cm = new CacheManager();
    cm.PostGetFromSource += new System.EventHandler(this.GotFromSource); 
    object[] ar = new object[] {placeName};
    CacheManager.SourceDataDelegate dlg = +
        new CacheManager.SourceDataDelegate(this.GetDataFromWebService);
    DataTable dt = (DataTable) cm.GetCachedData("Place", 60, dlg, ar);
    return dt;
}

private object GetDataFromWebService(object[] parameters)
{
    string placeName = parameters[0] as string;
    Geonet.Location[] places;
       
    try 
    {
        Geonet.PlaceFinder finder = new Geonet.PlaceFinder();
        Geonet.LocationInfo locinfo = finder.findPlace(placeName);
        places = locinfo.candidates;
        if (places == null) 
        {
            ErrorMessage.Text = "No place found with this name ";
            return null;
        }
    }
    
    catch (System.Net.WebException e)
    {
        ErrorMessage.Text = "Service not available at present "; 
        return null;
    }
    
    catch
    {
        ErrorMessage.Text = "An error occured "; 
        return null;
    }
    
    DataTable placeTable = new DataTable("places");
    placeTable.Columns.Add( new DataColumn("Name"));
    placeTable.Columns.Add( new DataColumn("Place"));
    placeTable.Columns.Add( new DataColumn("Type") );
    
    for (int i = 0; i<places.Length; i++) 
    {
        string[] place = {
            places[i].description2, 
            places[i].description1,
            places[i].type };
        placeTable.Rows.Add(place);
    }
    
    return placeTable;
}

We connect to geographynetwork.com webservice to get a list of place names starting with the text we provided, build a DataTable from the list and store it in the cache. Then we bind the DataTable to a DataGrid to show in a Webform. Very easy, isn�t it?

Considerations

Caching data consumes memory. By setting the expiration time carefully you can balance the quantity of data stored in the cache and the frequency it is fetched. The normal user behaviors should be taken into consideration. How much time they will stay in a DataGrid page before they click to the next page?

You should avoid as much as possible overlapping sets of data be stored in different cached items. If for example, the user queries all the customers in the Europe, then he or another user queries all customers in France. There will be overlapping data in cache. Our cache manager is not intelligent enough to figure out that. A short expiration time is the safeguard against high memory usage. We can also choose another strategy. When a user queries all customers in France, we can make a query on all customers in Europe, but only a subset of the data (French customers) is consumed by the Webform. With this strategy, it is advantageous that the expiration time be long enough. There are several ways to implement this strategy, which I leave to the readers.

You are welcome to contact me by email (zhong.yu@telia.com) if you want to discuss or comment anything in this article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here