Web Formsthe Page classdefine a Cache property. Cache functions like a data dictionary of name and values. By associating a key with an object in the cache, you can store processor- intensive data the first time you request it.
The ease with which you can use the cache is part of what makes it so powerful. Assume that you run a query on the code database that returns a DataTable of source code. You can store that DataTable in the cache. You can manipulate the data in the cache instead of requerying the database. If you modify data in the cache, you can update the database from the cache.
This is especially useful for CPU-intensive processes like sorting database queries. Instead of a thousand users all hitting the database, when the first user requests the data, it is cached with the application on the server, resulting in lower traffic between the Web application and the database.
The Page class contains a Cache property. The Cache works like a data dictionary (or collection). Include a bit of code to see if an item needs to be added to or is available in the cache, and you have a Web application that caches data. Listing 19.5 adds caching to the code database DataGrid browser, caching sorts to avoid requesting a particular sort from the database more than once. (The whole application is listed here to facilitate your understanding of the necessary changes.)
Listing 19.5 Caching processor-intensive tasks , like requesting sorted data from a database
1: Imports System.Data.OleDb 2: 3: Public Class WebForm1 4: Inherits System.Web.UI.Page 5: 6: 7: [ Web Form Designer Generated Code ] 8: 9: Private Function ConnectionString() As String 10: Return "Provider=Microsoft.Jet.OLEDB.4.0;" & _ 11: "Data Source=c:\ inetpub\ wwwroot\ CodeDatabase\ data\ CodeDatabase.mdb" 12: End Function 13: 14: Private Function Query() As String 15: Return "SELECT * FROM SOURCE" 16: End Function 17: 18: Private Function OrderBy(ByVal SortColumn As String) As String 19: If (SortColumn = "") Then 20: Return "" 21: Else 22: Return " ORDER BY " & SortColumn 23: End If 24: End Function 25: 26: Private Function Query(ByVal SortColumn As String) As String 27: Return Query() & OrderBy(SortColumn) 28: End Function 29: 30: Private Sub Page_Load(ByVal sender As System.Object, _ 31: ByVal e As System.EventArgs) Handles MyBase.Load 32: 'Put user code to initialize the page here 33: 34: If (IsPostBack()) Then Exit Sub 35: BindData(Query()) 36: 37: End Sub 38: 39: Private Sub DataGrid1_PageIndexChanged(_ 40: ByVal source As System.Object, _ 41: ByVal e As System.Web.UI.WebControls. _ 42: DataGridPageChangedEventArgs) 43: 44: DataGrid1.CurrentPageIndex = e.NewPageIndex 45: BindData(Query(FSortColumn)) 46: 47: End Sub 48: 49: Protected WithEvents DataGrid1 As _ 50: System.Web.UI.WebControls.DataGrid 51: Protected WithEvents Label1 As _ 52: System.Web.UI.WebControls.Label 53: 54: Private FSortColumn As String = "" 55: Private Property SortColumn() As String 56: Get 57: Return FSortColumn 58: End Get 59: Set(ByVal Value As String) 60: FSortColumn = Value 61: Changed() 62: End Set 63: End Property 64: 65: Private Overloads Sub BindData(ByVal SQL As String) 66: Dim Adapter As New _ 67: OleDbDataAdapter(SQL, ConnectionString()) 68: Dim DataSet As New DataSet() 69: Adapter.Fill(DataSet, "Source") 70: BindData(DataSet.Tables("Source")) 71: AddToCache(FSortColumn, DataSet.Tables("Source")) 72: End Sub 73: 74: Private Overloads Sub BindData(ByVal Table As DataTable) 75: DataGrid1.DataSource = Table 76: DataGrid1.DataBind() 77: End Sub 78: 79: Private Sub AddToCache(ByVal Column As String, ByVal Table As DataTable) 80: If Not (CachedTable(Column) Is Nothing) Then Exit Sub 81: CachedTable(Column) = Table 82: End Sub 83: 84: Private Property CachedTable(ByVal Column As String) As DataTable 85: Get 86: Return CType(Cache(Column), DataTable) 87: End Get 88: Set(ByVal Value As DataTable) 89: If (Column = "") Or (Value Is Nothing) Then Exit Property 90: Cache.Insert(Column, Value) 91: End Set 92: End Property 93: 94: Private Sub Changed() 95: 96: If (CachedTable(FSortColumn) Is Nothing) Then 97: BindData(Query(FSortColumn)) 98: Label1.Text = "Ran Query" 99: Else 100: BindData(CachedTable(FSortColumn)) 101: Label1.Text = "From Cache" 102: End If 103: 104: End Sub 105: 106: Private Sub DataGrid1_SortCommand(_ 107: ByVal source As Object, ByVal e As _ 108: System.Web.UI.WebControls.DataGridSortCommandEventArgs) _ 109: Handles DataGrid1.SortCommand 110: 111: SortColumn = e.SortExpression 112: End Sub 113: End Class
The bulk of the revisions occur between lines 65 and 104. An overloaded version of BindData was implemented to separate binding from SQL and binding from a DataTable. When we bind from SQL, we add the DataTable to the Cache (see line 71). The biggest change occurs in the Change method. If the sorted column was previously requested , the binding occurs from the cache rather than creating the adapter, running the query, creating the dataset, and binding from the DataSet's Tables collection.
Listing 19.5 wraps the Cache access in a property method; you don't have to do that. As a matter of technique, though, adding a property wrapper around the cache access allows us to add any additional constraints transparently . For example, in the listing, we don't try to add anything to the cache if the key or object is null.
There are a few salient points about general technique and the specific mechanics of caching that you should be aware of. The CachedTable property on lines 84 to 92 demonstrates an indexed property. Line 86 demonstrates how to retrieve an object from the page's cache and cast it to the type of the object we stored in the cache, a DataTable. Line 96 demonstrates how to inspect the cache using the key-value. If there is no cached DataTable for a specific column, we call BindData; our BindData accesses the database. On the other hand, if the cache contains a DataTable for a particular column, we bind the data from the cache (see line 100).
Adding and Removing Cached Items
The System.Web.Caching.Cache class allows you to Add and Insert items into the cache. Insert supports four overloaded methods of varying degrees of difficulty. The Add method is not overloaded. The basic parameters of the two methods depend on which version of Insert or Add you call. IntelliSense and the help files will ably assist you on a case-by-case basis.
The following example shows the syntax of the Add method. Each argument (or return value) is described in Table 19.1.
Public Function Add(_ ByVal key As String, _ ByVal value As Object, _ ByVal dependencies As CacheDependency, _ ByVal absoluteExpiration As DateTime, _ ByVal slidingExpiration As TimeSpan, _ ByVal priority As CacheItemPriority, _ ByVal onRemoveCallback As CacheItemRemovedCallback _) As Object
Table 19.1. Arguments You Need to Supply to Cache.Add or Cache.Insert
The biggest difference between Add and Insert is that Insert replaces items with the same key and Add does not replace the item.
Setting an Expiration Time for Cached Items
The Cache class allows you to proactively manage the amount of data that ends up in the cache. As you might imagine, storing data tables for dozens or hundreds of users can become resource-intensive. By passing AbsoluteExpiration or SlidingExpiration arguments, you can control how long an object lives in the cache.
AbsoluteExpiration indicates how long an item can remain in the cache before it is removed. SlidingExpiration determines how long since the last access something can stay in the cache. For example, an AbsoluteExpiration of five minutes means that an item will be removed after five minutes has expired . A SlidingExpiration of five minutes means that the item will remain in the cache as long as someone accesses it within each five-minute interval or until the AbsoluteExpiration interval expires .