In addition to placing items in the cache using the indexer, the Cache object implements a parameterized method named Insert that you can use to control many aspects of the cached item. The ways in which you can control cache entries include the following:
Setting up an absolute expiration time Setting up a sliding expiration time Setting
up dependencies between cached items and their backing sources (for
example, database, file, or directory dependencies, or even
dependencies on other cache entries) Managing a relative invalidation priority of cached items Setting up callback functions to be called when items are removed
The Cache's insert method includes four overloads. Table 1 enumerates them.
Table 1. Overloads for the Cache.Insert MethodInsert Overload | Description | Insert (String, Object) | Directly corresponds to the indexer version. Blindly places the object in the Cache using the string key in the first parameter. | Insert (String, Object, CacheDependency) | Inserts an object into the Cache and associates it with a dependency. | Insert (String, Object, CacheDependency, DateTime, TimeSpan) | Inserts an object into the Cache, associating it with a dependency and an expiration policy. | Insert (String, Object, CacheDependency, DateTime, TimeSpan, CacheItemPriority, CacheItemRemovedCallback) | Inserts an object into the Cache. Associates a dependency and expiration and priority policies. Also associates the Cache entry with a delegate for a callback to notify the application when the item is removed from the cache. |
The following example
illustrates some of these settings and how they work. In addition, the
forthcoming examples illustrate another way to get DataTables and DataSets. You can actually create them programmatically. The next few examples use a DataTable
that is created in memory rather than being fetched from a database.
Although the impact of caching isn't quite as dramatic when using the in-memory DataTable, it is still appreciable—and you can see this other approach to managing data. The following section also shows how the DataTable serializes as XML as well (which is useful for examining cached items with file dependencies).
1. DataSets in Memory
In addition to fetching them from databases, you can synthesize a DataTable programmatically. Doing so involves constructing a DataTable and adding DataRows to describe the schema. After constructing a DataTable,
you can use it to create columns with the correct "shape," populate
them, and then add them to the table's columns collection. Example 1 shows an example of creating a DataTable in memory. The
table is a collection of famous quotes and their originators that will
be useful in the next examples.
Example 1. The QuotesCollection object
public class QuotesCollection : DataTable { public QuotesCollection() {
// // TODO: Add constructor logic here // }
public void Synthesize() { // Be sure to give a name so that it will serialize as XML this.TableName = "Quotations"; DataRow dr;
Columns.Add(new DataColumn("Quote", typeof(string))); Columns.Add(new DataColumn("OriginatorLastName", typeof(string))); Columns.Add(new DataColumn("OriginatorFirstName", typeof(string)));
dr = this.NewRow(); dr[0] = "Imagination is more important than knowledge."; dr[1] = "Einstein"; dr[2] = "Albert"; Rows.Add(dr);
dr = this.NewRow(); dr[0] = "Assume a virtue, if you have it not"; dr[1] = "Shakespeare"; dr[2] = "William"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = @"A banker is a fellow who lends you his umbrella when the sun is shining, but wants it back the minute it begins to rain."; dr[1] = "Twain"; dr[2] = "Mark"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = @"A man cannot be comfortable without his own approval."; dr[1] = "Twain"; dr[2] = "Mark"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = "Beware the young doctor and the old barber"; dr[1] = "Franklin"; dr[2] = "Benjamin"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = @"Reality is merely an illusion, albeit a very persistent one."; dr[1] = "Einstein"; dr[2] = "Albert"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = "Beer has food value, but food has no beer value"; dr[1] = "Sticker"; dr[2] = "Bumper"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = @"Research is what I'm doing when I don't know what I'm doing"; dr[1] = "Von Braun"; dr[2] = "Wernher"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = "Whatever is begun in anger ends in shame"; dr[1] = "Franklin"; dr[2] = "Benjamin"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = "We think in generalities, but we live in details"; dr[1] = "Whitehead"; dr[2] = "Alfred North"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = "Every really new idea looks crazy at first."; dr[1] = "Whitehead"; dr[2] = "Alfred North"; this.Rows.Add(dr);
dr = this.NewRow(); dr[0] = @"The illiterate of the 21st century will not be those who cannot read and write, but those who cannot learn, unlearn, and relearn."; dr[1] = "Whitehead"; dr[2] = "Alfred North"; this.Rows.Add(dr);
} }
|
Building a DataTable
in memory is straightforward—it's mostly a matter of defining the
column schema and adding rows to the table. This class is available on the CD accompanying this book, so you don't need to type the whole thing. You can just import it into the next examples.
The next section looks at managing items in the cache.
2. Cache Expirations
The first way to manage
cached items is to give them expiration thresholds. In some cases, you
might be aware of certain aspects of your cached data that allow you to
place expiration times on it. The Cache supports both absolute expirations and sliding expirations.
Placing absolute expirations
To try out absolute expirations, add a new page to the UseDataCaching site named CacheExpirations.aspx. Use
Website, Add Existing Item to bring the QuoteCollection.cs file from
the CD accompanying this book and make it part of this project. Drag a GridView onto the CacheExpirations page, as shown in the following graphic. Don't bind it to a data source yet. You handle that in the Page_Load method.
In the Page_Load method of the CacheExpirations page, check the cache to see whether there's already an instance of the QuotesCollection object (just as in the previous example). If the data set is not available from the cache, create an instance of the QuotesCollection class and call the Synthesize method to populate the table. Finally, add it to the cache using the overloaded Insert method. You can use the DateTime class to generate an absolute expiration. Bind the QuotesCollection object to the GridView. The caching policy should be Cache.NoSlidingExpiration. Set up some trace statements so that you can see how the expiration times affect the lifetime of the cached object. protected void Page_Load(object sender, EventArgs e) { QuotesCollection quotesCollection;
DateTime dtCurrent = DateTime.Now; Trace.Warn("Page_Load", "Testing cache at: " + dtCurrent.ToString()); quotesCollection = (QuotesCollection)Cache["QuotesCollection"];
if (quotesCollection == null) { quotesCollection = new QuotesCollection(); quotesCollection.Synthesize();
DateTime dtExpires = new DateTime(2008, 5, 31, 23, 59, 59); dtCurrent = DateTime.Now;
Trace.Warn("Page_Load", "Caching at: " + dtCurrent.ToString()); Trace.Warn("Page_Load", "This entry will expire at: " + dtExpires); Cache.Insert("QuotesCollection", quotesCollection, null, dtExpires, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null); }
this.GridView1.DataSource = quotesCollection; this.DataBind();
}
Experiment with changing the dates and times to see how setting the expiration time forces a reload of the cache.
An absolute
expiration time applied to the cached item tells ASP.NET to flush the
item from the cache at a certain time. Now try using a different kind
of expiration technique—the sliding expiration.
Using a sliding expiration tells ASP.NET to keep the data in the cache
as long as it has been accessed within a certain period of time. Items
that have not been accessed within that time frame are subject to
expiration.
Placing sliding expirations
To set a sliding expiration for the cached data, modify the Page_Load method in the CacheExpirations page. Getting a sliding expiration to work is simply a matter of changing the parameters of the Insert method. Make up a time span after which you want the cached items to expire. Pass DateTime.MaxValue as the absolute expiration date and the TimeSpan as the final parameter like so: protected void Page_Load(object sender, EventArgs e) { QuotesCollection quotesCollection;
DateTime dtCurrent = DateTime.Now; Trace.Warn("Page_Load", "Testing cache: " + dtCurrent.ToString());
quotesCollection = (QuotesCollection)Cache["QuotesCollection"];
if (quotesCollection == null) { quotesCollection = new QuotesCollection(); quotesCollection.Synthesize();
TimeSpan tsExpires = new TimeSpan(0, 0, 15); dtCurrent = DateTime.Now;
Trace.Warn("Page_Load", "Caching at: " + dtCurrent.ToString()); Trace.Warn("Page_Load", "This entry will expire in: " + tsExpires.ToString()); Cache.Insert("QuotesCollection", quotesCollection, null, DateTime.MaxValue, tsExpires); }
this.GridView1.DataSource = quotesCollection; this.DataBind(); }
Surf to the page. You should see the cache reloading if you haven't accessed the cached item within the designated time frame.
Cache dependencies represent another way to manage cached items. Look at how they work next.
3. Cache Dependencies
In addition to allowing objects in the cache to expire by duration, you can set up dependencies for the cached items. For example, imagine your program loads some data from a file and places it into the cache. The backing
file (that is, the source of the cached information) might change,
making the data in the cache invalid. ASP.NET supports setting up a
dependency between the cached item and the file so that changing the
file invalidates the cached item. The conditions under which the cached
items may be flushed include when a file changes, a directory changes,
another cache entry is removed, or data in a table in Microsoft SQL
Server changes (this is an often requested feature available since
ASP.NET 2.0).
Here's an example that illustrates setting up cache dependencies.
Setting up cache dependencies
Add a new page to the UseDataCache site. Name it CacheDependencies.aspx. Place a button on the page that you can use to post a request to the page to generate an XML file from the QuotesCollection. Use ButtonSaveAsXML as its ID. Also, drag a GridView onto the page like so:
Double-click the button to generate a handler for the button that will save the XML Schema and the XML from the DataTable to XML and XSD files in the App_Data directory. In the handler, instantiate a QuotesCollection object and call Synthesize to generate the data. In the page, you have a reference to the Server object. Call the MapPath method in the Server object to get the physical path for saving the file. Then, use that path to create an XML file and a schema file. The DataTableWriteXmlSchema and WriteXml methods, respectively. will do this for you automatically by calling the protected void ButtonSaveAsXML_Click(object sender, EventArgs e) { QuotesCollection quotesCollection = new QuotesCollection(); quotesCollection.Synthesize(); String strFilePathXml =
Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xml"); String strFilePathSchema = Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xsd"); quotesCollection.WriteXmlSchema(strFilePathSchema); quotesCollection.WriteXml(strFilePathXml); }
Now write a method to load the XML into the QuotesCollection object and cache
the data. You can use the file path to the XML file to create a
dependency on the file. When it changes, ASP.NET will empty the cache.
Turn off the absolute expiration and the sliding expiration by passing
in Cache.NoAbsoluteExpiration and Cache.NoSlidingExpiration.
If you put trace statements in, you can see the effect of updating the
file after it's been loaded in the cache. Finally, make sure to bind
the GridView to the QuotesCollection. protected void CacheWithFileDependency() { QuotesCollection quotesCollection;
Trace.Warn("Page_Load", "Testing cache "); quotesCollection = (QuotesCollection)Cache["QuotesCollection"];
if (quotesCollection == null) { Trace.Warn("Page_Load", "Not found in cache"); quotesCollection = new QuotesCollection();
String strFilePathXml = Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xml"); String strFilePathSchema = Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xsd");
quotesCollection.ReadXmlSchema(strFilePathSchema); quotesCollection.ReadXml(strFilePathXml); System.Web.Caching.CacheDependency cacheDependency = new System.Web.Caching.CacheDependency(strFilePathXml);
Cache.Insert("QuotesCollection", quotesCollection, new System.Web.Caching.CacheDependency(strFilePathXml), System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, null); }
this.GridView1.DataSource = quotesCollection; this.DataBind(); }
Call the CacheWithFileDependency() in the Page_Load method. protected void Page_Load(object sender, EventArgs e) { if (!IsPostBack) { ButtonSaveAsXML_Click(null, null); } CacheWithFileDependency(); }
Now run the page. It should load the XML and schema into the QuotesCollection, save the QuotesCollection
in the cache, and then show the data in the grid. Clicking the Save
Table As XML button refreshes the XML file (on which a cache dependency
was made). Because the file on the disk changes, ASP.NET will flush the
cache. Next time you load the page, the cache will need to be reloaded.
Next, look at the final cache dependency: the SQL Server dependency.
4. The SQL Server Dependency
ASP.NET 1.0 had a
huge gap in its cache dependency functionality. The most useful type of
dependency was completely missing—that is, a dependency between a
cached item coming from SQL Server and the physical database. Because
so many sites use data provided by SQL Server to back their DataGrids and other controls, establishing this dependency is definitely a most useful way to manage cached data.
For the SQL Server dependency to work, you first configure SQL Server using the program aspnet_regsql.exe. The dependency is described in the configuration file, whose name is passed into the SqlCacheDependency constructor. The SqlCacheDependency class monitors the table. When something causes the table to change, ASP.NET will remove the item from the Cache.
Example 2 shows a configuration file with a dependency on SQL Server.
Example 2. Configuration settings for SQL Server cache dependency
<caching> <sqlCacheDependency enabled="true" > <databases > <add name="DBName" pollTime="500" connectionStringName="connectionString"/> </databases> </sqlCacheDependency> </caching>
|
Example 3
shows an ASP.NET page that loads the data from the SQL Server database
and establishes a dependency between the database and the cached item.
Example 3. Page using SqlCacheDependency
<%@ Page Language="C#" %> <%@ Import namespace="System.Data" %> <%@ Import namespace="System.Data.SqlClient" %> <script runat="server"> protected void Page_Load(Object sender, EventArgs e) { DataSet ds = null; ds = (DataSet)Cache["SomeData"]; if (ds == null) { string connectionString = ConfigurationManager.ConnectionStrings["connectionString"]. ConnectionString; SqlDataAdapter da = new SqlDataAdapter("select * from DBName.tableName", connectionString); ds = new DataSet(); da.Fill(ds); SqlCacheDependency sqlCacheDependency = new SqlCacheDependency("DBName", "tableName"); Cache.Insert("SomeData", ds, sqlCacheDependency); } GridView1.DataSource = ds; DataBind(); } </script> <html><body> <form id="form1" runat="server"> <asp:GridView ID="GridView1" runat="server"> </asp:GridView> </form> </body></html>
|
Once items are in the cache
and their lifetimes are established through expirations and cached item
dependencies, one other cache administrative task remains—reacting when
items are removed.
5. Clearing the Cache
As you can see from the previous examples, ASP.NET clears the cache on several occasions, as follows:
Removing items explicitly by calling Cache.Remove Removing low-priority items because of memory consumption Removing items that have expired
One of the parameters to one of the Insert
overloaded methods is a callback delegate so that ASP.NET can tell you
that something's been removed from the cache. To receive callbacks, you
simply need to implement a method that matches the signature, wrap it
in a delegate, and then pass it when calling the Insert method. When the object is removed, ASP.NET will call the method you supply.
The next example illustrates setting up a removal callback function.
Setting up a removal callback
One
of the main tricks to getting the removal callback to work is finding
an appropriate place to put the callback. What happens if you make the
callback a normal instance member of your Page
class? It won't work. The callback will become disconnected after the
first page has come and gone. The callback has to live in a place that
sticks around. (You could make the callback a static method, however.)
The perfect class for establishing the callback is in the global application class. Add a global application class to your application. Select
Website, Add New Item. Select the Global
Application Class template, and click Add to insert it into the
project. Microsoft Visual Studio adds a new file named Global.asax to
your application. Global.asax.cs
includes application-wide code. Write a method to handle the callback
in the Global.asax.cs file. In this case, the response will be to set a
flag indicating the cache is dirty. Then, the code will simply place
the data back into the cache during the Application_BeginRequest handler. The code for doing so looks very much like the code in the CacheWithFileDependency method shown earlier. You can get a reference to the cache through the current HttpContext. using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Security; using System.Web.SessionState; using System.Web.Caching;
namespace UseDataCaching { public class Global : System.Web.HttpApplication { bool _bReloadQuotations = false; public void OnRemoveQuotesCollection(string key, object val, CacheItemRemovedReason r) { // Do something about the dependency Change if (r == CacheItemRemovedReason.DependencyChanged) { _bReloadQuotations = true; } }
protected void ReloadQuotations() { QuotesCollection quotesCollection = new QuotesCollection(); String strFilePathXml = Server.MapPath(HttpContext.Current.Request.ApplicationPath + "\\App_Data\\QuotesCollection.xml"); String strFilePathSchema = Server.MapPath(HttpContext.Current.Request.ApplicationPath + "\\App_Data\\QuotesCollection.xsd"); quotesCollection.ReadXmlSchema(strFilePathSchema); quotesCollection.ReadXml(strFilePathXml);
System.Web.Caching.CacheDependency cacheDependency = new System.Web.Caching.CacheDependency(strFilePathXml);
HttpContext.Current.Cache.Insert("QuotesCollection", quotesCollection, cacheDependency, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, this.OnRemoveQuotesCollection); }
protected void Application_BeginRequest(object sender, EventArgs e) { if (_bReloadQuotations == true) { ReloadQuotations(); _bReloadQuotations = false; } } // VS-provided code } }
Update the CacheWithFileDependency method to use the callback method when establishing the QuotesServer in the cache. You can access the callback method through the page's Application member. protected void CacheWithFileDependency() { QuotesCollection quotesCollection; Trace.Warn("Page_Load", "Testing cache "); quotesCollection = (QuotesCollection)Cache["QuotesCollection"];
if (quotesCollection == null) { Trace.Warn("Page_Load", "Not found in cache"); quotesCollection = new QuotesCollection();
string strFilePathXml = Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xml"); string strFilePathSchema = Server.MapPath(Request.ApplicationPath + "\\App_Data\\QuotesCollection.xsd"); quotesCollection.ReadXmlSchema(strFilePathSchema); quotesCollection.ReadXml(strFilePathXml);
System.Web.Caching.CacheDependency cacheDependency = new System.Web.Caching.CacheDependency(strFilePathXml);
Global global = HttpContext.Current.ApplicationInstance as Global; Cache.Insert("QuotesCollection", quotesCollection, cacheDependency, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration, System.Web.Caching.CacheItemPriority.Default, global.OnRemoveQuotesCollection); } this.GridView1.DataSource = quotesCollection; this.DataBind(); }
When you surf to the page, you should never see the Page_Load method refreshing the cache. That's because when the XML file is overwritten, ASP.NET immediately calls the ReloadQuotations method—which loads the cache again.
|