Data Caching
Because of the dynamic nature of our sites, caching data is often more practical than an entire page. We already gave you a fairly extensive data caching example in Chapter 5, "Object-Oriented Programming Applied: A Custom Data Class," with our custom data class. As we said then, the key to using the Cache object is to make sure that changing data in the data store invalidates the cached data. Encapsulating your data access in something like our example class makes the validity of the cached data easy.There are trade-offs to consider when you're caching data. Reading data from memory instead of the database is certainly faster, but while your disk space is relatively "endless," your server only has a finite amount of memory. Your server situation has a lot to do with the amount of caching you can do. If the server has a gigabyte of memory with no other applications running on it, and the data you cache totals 100 megabytes, you obviously have no problem. If your application is running on a server farm, then you can't cache in memory because different servers would have different values for the data.Inserting data into the cache takes several parameters:
The first parameter is a name used to look up the cached item. Our example in Chapter 5 used a prefix and the primary key relative to the database record we were caching in object form. The second parameter is the object to actually place into the cache. The third parameter is a CacheDependency object, which is used to monitor file changes or other cache entries. It's not very useful for most data stores, and a null (Nothing) value is often passed in. The fourth parameter indicates an absolute DateTime, after which the item expires and is removed from the cache. When this value is used, the last parameter must be TimeSpan.Zero. The last value is a sliding expiration, where the item continues to be cached as long as the specified TimeSpan hasn't passed without the item being accessed. If this parameter is specified, the absolute expiration must be set to DataTime.MaxValue.A more useful cache dependency object is one based on the SqlCacheDependency class (starting in ASP.NET v2.0). It monitors changes to a SQL Server table to invalidate cached items. It's a lot more work to set up. It works differently for SQL Server 7.0 or 2000 and SQL Server 2005 (formerly known as "Yukon").For 7.0 and 2000, the class depends on a polling mechanism and a special series of tables and triggers added to your database. The cache is invalidated any time the table is changed. To make this work, you must run the aspnet_regsql.exe tool from the command line.
Cache.Insert(nameString, objectToCache, cacheDependency,
absoluteExpirationTime, slidingExpirationTimeSpan);
aspnet_regsql.exe does a lot of things, as this is the same tool you used to set up the membership features in a SQL database. This particular function, however, requires the use of the command line, not the program's GUI. |
This says "Enable cache dependency on the database named aspnetdb and use a trusted connection." Other options can be found by simply using aspnet_regsql -?. This creates a new table and some stored procedures. To make the rest work, triggers have to be placed on the tables you want to monitor for dependencies. This is done with the following on the command line:
aspnet_regsql -ed -d aspnetdb E
This particular command means "Enable cache dependency on a table, the table called tableName in database aspnetdb using a trusted connection." Finally, you'll need the web.config settings shown in Listing 15.1.
aspnet_regsql -et -t tableName -d aspnetdb E
Listing 15.1. Setting SqlCacheDependency in web.config
[View full width]
You're already familiar with the connection string and the general format of <add> elements in the file. The pollTime attribute determines how often ASP.NET should check to see if the tables have had an insert, update, or delete. This introduces a bit of overhead itself, but finding a good polling time will help you with the trade-off. The downside in this case is that with SQL Server 7.0 and 2000, you can only monitor entire tables, not individual records.In the Cache.Insert() syntax we showed you earlier, you can pass in an instance of SqlCacheDependency as your final step to invalidate a cache entry. This is as easy as replacing "cacheDependency" with:
<configuration>
<connectionStrings>
<add name="MyDatabase" connectionString="Data Source=localhost;IntegratedSecurity=SSPI;Initial Catalog=MyDataBase;" />
</connectionStrings>
<system.web>
<cache>
<sqlCacheDependency enabled="true" pollTime="60">
<add name="MyDep" connectionName="MyDatabase" pollTime="60" />
</sqlCacheDependency>
</cache>
</system.web>
</configuration>
"MyDep" comes from the dependency we named in Listing 15.1, in the web.config file, and the table name is whatever table you want to monitor for changes, making sure of course that you set it up as described earlier using the aspnet_regsql.exe tool.This caching affair is even easier to use with SQL Server 2005, as it has a built-in notification system that will create a callback to ASP.NET, letting it know that a record has been changed. To use this feature, forget the previous discussion with regards to setup and simply use this overload of the SqlCacheDependency constructor:
new SqlCacheDependency("MyDep", "tableName")
…where command is the SqlCommand object that originally fetched the data. Recall Listing 5.9 from Chapter 5, where we revised our constructor with our cache lookup and insert. We could change the cache insertion to look like this:
new SqlCacheDependency(command)
Instead of passing null into the third parameter, we've added a SqlCacheDependency object that will automatically listen for SQL Server 2005's notification that our record has changed. This means that the Update() and Delete() methods of our sample class in Chapter 5 don't need to fire the DeleteCache() method we wrote either because SQL Server 2005 and the .NET Framework will take care of this cache invalidation for us.
context.Cache.Insert("UberCustomer" +
_CustomerID.ToString(), this, new SqlCacheDependency(command),
DateTime.Now.AddSeconds(60), new TimeSpan.Zero);