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.