Accessing and Updating Data in ASP.NET: Declaratively Caching Data

ASP.NET 2.0 introduced a number of new Web controls designed for accessing and modifying data.
These controls allow page developers to declaratively access and modify data without writing any
code to perform the data access. This article is one in a series of articles on ASP.NET's data source controls.

Data Source Control Basics - explores the concepts
and advantages of data source controls, and compares their usage in ASP.NET 2.0 to data access techniques in ASP.NET 1.x.

Accessing Database Data - shows how to use the
SqlDataSource and AccessDataSource controls to query data from a relational database.

Filtering Database Data with Parameters - learn how to
retrieve just a subset of database data based on hard-coded values and values from the querystring, other Web controls on the page,
session variables, and so on.

Introduction

The ASP.NET 2.0 data source controls provide a declarative means for accessing and working with data. Simply set a few properties
of the data source control, bind it to a data Web control, and, voila, data is being retrieved and displayed without
having written a single line of code!

In addition to working with data declaratively, the data source controls can also cache data declaratively.
Caching is a common technique used in data-driven applications to boost performance, and works by storing database data
in memory where it can be more efficiently accessed. ASP.NET provides the data cache, which is a programmatically-accessible
cache that is commonly used to store database results. See Caching
in ASP.NET for more details on using the data cache and other caching options in ASP.NET.

What's worth noting about the data source controls is that they offer declarative access to the data cache. By simply
setting a few properties, the data source controls will happily store their retrieved data in the data cache. Then, the
next time the data source is asked to retrieve data, they'll pull that data from the cache instead of returning to the
database. In this article we'll explore how to setup a data source control so that it stores it caches its results. Read on
to learn more!

A Quick Primer on Caching

Caching is a technique for improving performance by moving data from some expensive data store to one that is less so. By
"expensive" I mean that there is a performance cost associated with working with the data and by "data store" I mean some
location where data is stored: a database, a file, memory, and so forth. The canonical caching example is taking information
from a database and storing it in an application's memory, since an application can read from its memory many orders of magnitude
faster than it can read the same data from a remote database server.

Caching carries with it some limitations. Almost always, the more efficent data store has less storage space than the slower
store. That is, chances are the database holds a lot more information than can fit in the application's memory. Therefore,
not all data can be cached. Moreover, when adding data to the cache, other cached information might need to be evicted
from the cache in order to make room.

Also, caching imposes a disconnect between data the application sees and the source data
itself. If the source data is updated or deleted, the application working with a cached copy won't know that it's data
has become stale until it returns to the original data store and determines that this, indeed, is the case. For this
reason, data is typically only cached for a pre-determined duration, after which the data is automatically evicted. This
places an upperbound on how long the cached data may be stale. (ASP.NET 2.0
offers SQL cache dependencies, which is a more proactive technique for determining if the cached data has become stale.
This article does not explore SQL cache dependencies, but see the "Further Readings" section at the end for more information
on this topic.)

The data cache is the cache store commonly used by ASP.NET applications. It is an in-memory cache that is accessible
through the Cache object. The data cache provides dictionary-like access to its contents. To add an item to the
cache, specify a key for that cache item, where the key is a string. Likewise, to retrieve an item from the cache, refer to it via its key. The following
code snippet shows reading and writing to the data cache:

Caching Data Retrieved from the Data Source Controls

The ObjectDataSource, SqlDataSource, AccessDataSource, and XmlDataSource controls all contain properties that instruct the
data source control to cache its data in the data cache. This caching occurs automatically and requires no code from us,
the page developer. The key properties are:

EnableCaching - a Boolean value that indicates whether or not caching should be applied. Defaults
to False. You will need to set this to True to instruct the data source control to employ caching.

CacheDuration - an Integer value that indicates the duration the data should be cached (in seconds).

CacheExpirationPolicy - specifies the cache expiration behavior; can be set to Absolute or Sliding. Absolute
means that the cached item will be evicted CacheDuration seconds from when the item is initially inserted into the
cache. Sliding means that the item will be evicted CacheDuration seconds after the last time the data is accessed
from the cache. The default is Absolute.

CacheKeyDependency - a String value that specifies an additional item in the cache that serves as a dependency.
If this value is specified, then whenever the cache item CacheKeyDependency is modified, the data source will automatically
evict its data from the cache as well.

The SqlDataSource and ObjectDataSource controls also have a SqlCacheDependency property that should be used if you
are using SQL cache dependencies. Also note that while the AccessDataSource has this property, attempting to use it in
will result in a NotSupportedException exception to be thrown. Also note that
the data source controls will only cache data if EnableCaching is set to True and either a time-based expiry
is specified via the CacheDuration property or a SQL cache dependency is specified.

The following declarative syntax shows an AccessDataSource that is configured to support caching for an absolute duration
of 30 seconds (also be sure to check out the more in-depth demos available for download at the end of this article):

When this AccessDataSource's Select method was invoked by a data Web control (such as a GridView), the AccessDataSource
would first see if its data was in the data cache. If not, it would connect to the Microsoft Access database (Northwind.mdb)
and issue the SelectCommand query. It would then cache the results in the data cache and return them to the
requesting data Web control. Now, imagine that five seconds later another user visits the page. The data Web control will
again request the AccessDataSource for its data, but this time the data will be in the cache. Therefore, the AccessDataSource
will return its data to the data Web control without having to connect to the database. After 30 seconds, though, the
data will be evicted from the cache and the subsequent request will go back to the database.

A Detailed Look at How the Data Source Caches its Data

When the Select method is invoked for a data source control that has been configured for caching, the
data source control starts by checking to see if its data resides in the data cache. As mentioned earlier, the data cache's
items are accessed by a string key. The key used by the data source control is a combination of its essential properties.
For the SqlDataSource, this includes the value of the CacheDuration, CacheExpirationPolicy,
ConnectionString, SelectCommand, and SelectParameters properties. This ensures
that the cache is unique per the connection, per the SELECT query, and per the SELECT query's
parameter names and values (if any).

If the item is found in the data cache, the data source control returns the cached data and does not raise its
Selecting event. As discussed in the Examining
the Data Source Control's Events article, the Selecting event fires each time the data source control's
Select method is invoked and data is retrieved from the underlying store.

If the item is not found in the cache, then the data source control proceeds through its normal selecting workflow: the
Selecting event fires, the underlying data store is accessed and data retrieved, and the Selected
event fires. Before the data is returned, however, the data source control first adds it to the data cache, using the same
key construction process discussed above.

The following sequence diagram depicts the caching workflow for the ObjectDataSource, although the workflow is identical for
the AccessDataSource, SqlDataSource, and XmlDataSource controls as well. Note how the Selecting event does not
first if the item is served from the cache.

We can use the fact that the Selecting event only fires when the data is retrieved from the underlying data
store to display information on a page of when the data was last cached. The examples available for download at the end of this
article use a Label Web control that's updated with the current date and time each time the Selecting event fires,
thereby showing the most recent time the cache was updated. While an end user might not care or need to see this information,
it's helpful for page developers as it shows when cached data is being stored and what actions can cause the cached data to
be prematurely evicted.

Evicting Items from the Cache

One downside of caching is stale data. While a short time-based expiry can help limit the total duration of potentially stale
data, there are other actions that can be taken to forcibly evict an item from the data cache. The data source controls will
automatically remove their data from the cache if the data source control's Insert, Update, or
Delete methods are invoked. We've yet to explore these data modification methods available in the data source controls,
but we most certainly will in a future installment. In short, these methods modify the underlying data store, as their names imply,
and can be configured declaratively. When any of these methods are executed, the data source control is modifying the underlying
data store, so it makes sense for the data source control to evict the cached data since it now knows that that data is stale.
You can see this behavior in the examples available for download at the end of this article.

You can also programmatically remove the data source control's cached data by using the CacheKeyDependency property.
Simply set the CacheKeyDependency property to the key value of some data in the cache. Then, to remove the
data source control's cached data, simply update the cache item CacheKeyDependency.

Start by setting the data source control's CacheKeyDependency property to the name of the cache item key you
want to serve as a dependency. You can choose any old name for this property:

Next, it's imperative that this item exists in the cache, otherwise the data cached by the data source control will be immediately
evicted from the data cache each time its added. In the Page_Load event handler, test to see if the
item exists in the cache and, if not, add it:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
'Add the Cache item, if needed
If Cache(ProductsDataSource.CacheKeyDependency) Is Nothing Then
Cache(ProductsDataSource.CacheKeyDependency) = DateTime.Now
End If
End Sub

Here I assign DateTime.Now to the cache item, but you can use any value. DateTime.Now is a good
choice, though, because it indicates the last time the cache item was last updated.

To programmatically evict the data source's cached data, simply update the value of the Cache(ProductsDataSource.CacheKeyDependency)
cache item, such as to the current date and time (Cache(ProductsDataSource.CacheKeyDependency) = DateTime.Now). In the download at the end of this article there's an example that includes
a Button Web control that, when clicked, evicts the data from the cache by updating Cache(ProductsDataSource.CacheKeyDependency)
to the current date and time. That's all there is to it!

Conclusion

The data source controls make accessing and working with data as easy as setting a few properties. Oftentimes, no code is
required. As we saw in this article, this declarative model is also extended to caching. A data source control's data can
be cached in the data cache using a time-based expiry simply by setting the EnableCaching property to True
and the CacheDuration property to the duration the data should be cached (in seconds). The SqlDataSource and
ObjectDataSource controls can also use SQL cache dependencies, although this topic will have to wait for a future article.

Attachments:

ASP.NET 2.0 introduced a number of new Web controls designed for accessing and modifying data.
These controls allow page developers to declaratively access and modify data without writing any
code to perform the data access. This article is one in a series of articles on ASP.NET's data source controls.

Data Source Control Basics - explores the concepts
and advantages of data source controls, and compares their usage in ASP.NET 2.0 to data access techniques in ASP.NET 1.x.

Accessing Database Data - shows how to use the
SqlDataSource and AccessDataSource controls to query data from a relational database.

Filtering Database Data with Parameters - learn how to
retrieve just a subset of database data based on hard-coded values and values from the querystring, other Web controls on the page,
session variables, and so on.