WEBSITE

Microsoft ASP.NET 3.5 : Caching Application Data (part 5) - SQL Server Cache Dependency

1/12/2013 3:24:27 AM

6. SQL Server Cache Dependency

Many ASP.NET applications query some data out of a database, cache it, and then manage to serve a report to the user. Binding the report to the data in the cache will both reduce the time required to load each report and minimize traffic to and from the database. What’s the problem, then? With a report built from the cache, if the data displayed is modified in the database, users will get an out-of-date report. If updates occur at a known or predictable rate, you can set an appropriate duration for the cached data so that the report gets automatically refreshed at regular intervals. However, this contrivance just doesn’t work if serving live data is critical for the application or if changes occur rarely and, worse yet, randomly. In the latter case, whatever duration you set might hit the application in one way or the other. A too-long duration creates the risk of serving outdated reports to end users which, in some cases, could undermine the business; a too-short duration burdens the application with unnecessary queries.

A database dependency is a special case of custom dependency that consists of the automatic invalidation of some cached data when the contents of the source database table changes. Not directly supported by the framework in ASP.NET 1.x, database dependencies are a native feature starting with ASP.NET 2.0. In ASP.NET 2.0 and beyond, you find an ad hoc class—SqlCacheDependency—that inherits CacheDependency and supports dependencies on SQL Server tables. More precisely, the class is compatible with MSDE, SQL Server 7.0, and subsequent SQL Server versions (including SQL Server 2005).

Under the Hood of Database Dependencies

With the notable exception of SQL Server 2005, no database today offers listening features to detect when relevant changes occur. This means that on SQL Server 7.0, SQL Server 2000, and non-SQL Server databases you must create a database-level infrastructure that makes table changes emerge, allows them to be captured, and notifies the ASP.NET cache of the changes.

In the past several years, a few techniques have been investigated by the ASP.NET team and developers in the community. None of the techniques is perfect, but all are worth a look if you plan to implement database cache invalidation in ASP.NET 1.x applications.

A database cache invalidation system is based on two key elements—a mechanism to detect changes on the database, and a mechanism to notify ASP.NET of the changes. To detect changes, triggers are the most commonly used technique. You need to have a trigger active on each table in a database that you want to monitor for changes. The trigger captures insertions, deletions, and updates on a table and does something with them. What exactly it does depends on the second mechanism you implement. Various approaches have been tested over time:

  • An extended stored procedure invokes an application-specific HTTP handler (which is simpler than a plain page). The handler receives the key of the cache item that has been invalidated and removes it from the cache.

  • An extended stored procedure modifies the timestamp of a disk file that the cached data is dependent upon.

Although some people don’t particularly like the use of triggers, as I see things the real issue here is the use of extended stored procedures. They have to be written in C++ and deployed manually to SQL Server. Furthermore, they require administrative permissions because they run external programs and introduce potentially serious blocking issues. The extended stored procedure can’t return until the HTTP call or the file modification is complete. What if the Web server takes a long time to respond? What if the file is locked? In the end, the database will be affected and the flow of information from it or to it might be slowed down. These solutions might work great for small applications with no scalability concerns, but they are not ideal for large, real-world sites.

Tip

If you don’t like triggers, you might want to try T-SQL checksum functions. The following query returns a value that varies when changes are made to a table record:

SELECT CHECKSUM_AGG(BINARY_CHECKSUM(*)) FROM Customers

Checksum functions are reasonably fast but don’t work with reference columns such as text and image. The advantage of checksum functions is that all you need to deploy on the database is a stored procedure to wrap the query just shown.


Extended stored procedures implement a push model, where the database backend pushes changes to the ASP.NET application. The reverse approach is also possible—a pull model based on polling—which is the foundation of the ASP.NET implementation of database cache invalidation.

The database to be monitored is equipped with triggers and a helper table with one record for each monitored table. Triggers update the helper table whenever the corresponding table is modified. A custom component placed in the ASP.NET cache polls this helper table looking for changes—and because it’s a very small table, results are likely to be paged in SQL Server’s memory for the fastest performance. When the polling component detects a change in the table, it will invalidate the linked cache item with the data used by the application.

Note

To implement database dependencies in ASP.NET 1.x, you start by creating a custom CacheDependency class along the lines of the CacheDependency class available in ASP.NET 2.0 and beyond. This abstract class will start a timer in the constructor and call an overridable method—say, HasChanged—to check for changes. The user-defined DatabaseCacheDependency class inherits from CacheDependency and overrides HasChanged to query against the helper table of change notifications (or checksums). To insert data in the cache bound to this dependency object, you resort to a helper method that extends the Insert method of the native Cache object. Basically, your helper insert method will create a pair of cache entries—one for the real data, and one for the dependency object polling for data. The two entries are linked so that changes to the entry with the dependency invalidate the one with real data. Details and sample code are available at http://msdn.microsoft.com/msdnmag/issues/04/07/CuttingEdge.


Enabling Database Dependencies

In ASP.NET 2.0 and beyond, database dependencies are implemented through the SqlCacheDependency class. The class works with SQL Server 7.0, SQL Server 2000, and the newer SQL Server 2005. To make it work with SQL Server 2005, much less setup work is required. Let’s tackle SQL Server 7.0 and SQL Server 2000 first.

For the SqlCacheDependency class to work correctly, any tables that you want to monitor for changes must have notifications enabled. Enabling notifications entails administrative changes to the database that must be accomplished before the application is published. Changes include creating ad hoc triggers and stored procedures to handle any incoming UPDATE, INSERT, or DELETE statements.

You use the command-line tool aspnet_regsql to do any required offline work. You first enable notifications on the database, and next do the same on one or more of the database tables. Run the following command to enable notifications on the Northwind database for the local installation of SQL Server:

aspnet_regsql.exe -S (local) -U YourUserName -P YourPassword
                  -d Northwind -ed

Run the following command to enable notification on the Customers table:

aspnet_regsql.exe -S (local) -U YourUserName -P YourPassword
                  -d Northwind –et –t Customers

The first command adds a new table to the database whose name is AspNet_SqlCacheTablesForChangeNotification. In addition, a bunch of stored procedures and triggers are added. Note that you need to specify a login with enough permissions to perform all the operations.

The second command adds a trigger to the specified table and a new record to AspNet_SqlCacheTablesForChangeNotification that refers to the specified table. Here’s the trigger:

CREATE TRIGGER dbo.[Customers_AspNet_SqlCacheNotification_Trigger]
ON [Customers]
FOR INSERT, UPDATE, DELETE AS BEGIN
  SET NOCOUNT ON
  EXEC dbo.AspNet_SqlCacheUpdateChangeIdStoredProcedure N'Customers'
END

Figure 5 provides a view of the structure of the change notification table. This table contains one record for each monitored table.

Figure 5. The structure of the AspNet_SqlCacheTablesForChangeNotification table.


The trigger executes the following stored procedure whenever a change occurs on the monitored table. As a result, the changeId column for the table is modified.

BEGIN
  UPDATE dbo.AspNet_SqlCacheTablesForChangeNotification WITH (ROWLOCK)
    SET changeId = changeId + 1
    WHERE tableName = @tableName
END

To finalize the setup of SqlCacheDependency, you need to add the following script to the application’s web.config file:

<system.web>
  <caching>
    <sqlCacheDependency enabled="true" pollTime="1000" >
      <databases>
        <add name="Northwind" connectionStringName="NWind" />
      </databases>
    </sqlCacheDependency>
  </caching>
</system.web>

The pollTime attribute indicates (in milliseconds) the interval of the polling. In the preceding sample, any monitored table will be checked every second. Under the <databases> node, you find a reference to monitored databases. The name attribute is used only to name the dependency. The connectionStringName attribute points to an entry in the <connectionStrings> section of the web.config file and denotes the connection string to access the database.

Note

In addition to using the aspnet_regsql command-line tool, you can use a programming interface to create the runtime environment that allows database cache dependencies for SQL Server 7 and SQL Server 2000. The following code enables the Northwind database for notifications:

SqlCacheDependencyAdmin.EnableNotifications("Northwind");

You add a table to the list of monitored tables with the following code:

SqlCacheDependencyAdmin.EnableTableForNotifications(
    "Northwind", "Employees");

The SqlCacheDependencyAdmin class also has methods to disable previously enabled dependencies.


Let’s see now how to create and use a SqlCacheDependency object.

Taking Advantage of SQL Server Dependencies

The SqlCacheDependency class has two constructors. The first takes a SqlCommand object, and the second accepts two strings—the database name and the table name. The constructor that accepts a SqlCommand is intended for use only with SQL Server 2005; the other is designed for dependencies that involve older versions of SQL Server. Functionally speaking, the two are equivalent.

The following code creates a SQL Server dependency and binds it to a cache key:

protected void AddToCache(object data)
{
    string database = "Northwind";
    string table = "Customers";
    SqlCacheDependency dep = new SqlCacheDependency(database, table);
    Cache.Insert("MyData", data, dep);
}
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // Get some data to cache
        CustomerCollection data = Customers.LoadByCountry("USA");

        // Cache with a dependency on Customers
        AddToCache(data);
    }
}

The data in the cache can be linked to any data-bound control, as follows:

CustomerCollection data = null;
object o = Cache["MyData"];
if (o != null)
    data = (CustomerCollection)o;
else
    Trace.Warn("Null data");

CustomerList.DataTextField = "CompanyName";
CustomerList.DataSource = data;
CustomerList.DataBind();

When the database is updated, the MyData entry is invalidated and, as for the sample implementation provided here, the listbox displays empty.

Important

You get notification based on changes in the table as a whole. In the preceding code, we’re displaying a data set that results from the following:

SELECT * FROM customers WHERE country='USA'

If, say, a new record is added to the Customers table, you get a notification no matter what the value in the country column is. The same happens if a record is modified or deleted where the country column is not USA.

SQL Server 2005 offers a finer level of control and can notify applications only of changes to the database that modify the output of a specific command.


Once you are set up for table notifications, pages that use a SqlDataSource control can implement a smarter form of caching that monitors the bound table for changes and reloads data in case of changes:

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
     ConnectionString="<%$ ConnectionStrings:NWind %>"
     SelectCommand="SELECT * FROM Customers"
     EnableCaching="true"
     SqlCacheDependency="Northwind:Customers">
</asp:SqlDataSource>

You set the SqlCacheDependency property to a string of the form Database:Table. The first token is the name of the database dependency as set in the <databases> section. The second token is the name of the table to monitor. You can also define multiple dependencies, separating each pair with a semi-colon:

<asp:SqlDataSource ID="SqlDataSource1" runat="server"
     EnableCaching="true"
     SqlCacheDependency="Northwind:Customers;Pubs:Authors"
     ...
/>

Note that caching must be enabled for the feature to work.

Note

Although I’ve mentioned only SqlDataSource, the SqlCacheDependency property also works with ObjectDataSource as long as ObjectDataSource returns data through ADO.NET objects.


Cache Dependencies in SQL Server 2005

As mentioned, the SqlCacheDependency class has two constructors, one of which takes a SqlCommand object as its sole argument. This constructor is used to create SqlCacheDependency objects for SQL Server 2005 databases. Here’s how to use it:

protected void AddToCache()
{
   SqlConnection conn = new SqlConnection(
      ConfigurationManager.ConnectionStrings["NWind05"].ConnectionString);
   SqlCommand cmd = new SqlCommand(
       "SELECT * FROM Customers WHERE country='USA'",
       conn);

   SqlDataAdapter adapter = new SqlDataAdapter(cmd);
   DataTable data = new DataTable();
   adapter.Fill(data);

   SqlCacheDependency dep = new SqlCacheDependency(cmd);
   Cache.Insert("MyData", data, dep);
}

Note that with SQL Server 2005 no setup work is needed and no external objects must be added to the database. No triggers, stored procedures, and notification tables are needed.

SQL Server 2005 incorporates a made-to-measure component that monitors changes at a finer level than was possible in earlier versions. This component takes a command object and tracks all the ongoing changes to detect whether something happened to modify the result set returned by the command. When this happens, the component pushes new information to the listening object. 

So when you instantiate a SqlCacheDependency object and have it guard a given command, a SqlDependency object wraps the command and fires an event when a change is detected. In turn, the SqlCacheDependency catches the event and invalidates the cached item. Figure 6 illustrates the data flow.

Figure 6. The internal implementation of SqlCacheDependency when used with SQL Server 2005.


Note

The SQL Server 2005 implementation of database cache invalidation is clearly the best possible because it leverages a new infrastructure built in the database. ASP.NET applications receive an asynchronous notification of changes, which is good for performance and poses no blocking issues. Also, no setup is necessary for the feature to work. The SQL Server 7.0 and SQL Server 2000 implementation of the same feature relies on polling and requires some setup work. The ASP.NET team made the SQL dependency setup as smooth as possible, but it still requires an administrative login to get into the database to create triggers, tables, and stored procedures. This might be a problem if you’re not allowed full access to the database or if you’re working in an ISP scenario. Is polling the best possible option for detecting changes? All things considered, polling is a necessity when there’s no built-in notification mechanism in the database. And polling a small table is more efficient than repeatedly running a query. Finally, polling doesn’t suffer from blocking issues as do approaches built on extended stored procedures, and it also works great in Web farms and Web gardens. In the end, the ASP.NET team determined that polling was the best option for the broadest number of ASP.NET applications willing to support database cache invalidation.

Other  
 
Video
Top 10
SG50 Ferrari F12berlinetta : Prancing Horse for Lion City's 50th
The latest Audi TT : New angles for TT
Era of million-dollar luxury cars
Game Review : Hearthstone - Blackrock Mountain
Game Review : Battlefield Hardline
Google Chromecast
Keyboards for Apple iPad Air 2 (part 3) - Logitech Ultrathin Keyboard Cover for iPad Air 2
Keyboards for Apple iPad Air 2 (part 2) - Zagg Slim Book for iPad Air 2
Keyboards for Apple iPad Air 2 (part 1) - Belkin Qode Ultimate Pro Keyboard Case for iPad Air 2
Michael Kors Designs Stylish Tech Products for Women
REVIEW
- First look: Apple Watch

- 3 Tips for Maintaining Your Cell Phone Battery (part 1)

- 3 Tips for Maintaining Your Cell Phone Battery (part 2)
Popular Tags
Video Tutorail Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Exchange Server Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe Photoshop CorelDRAW X5 CorelDraw 10 windows Phone 7 windows Phone 8 Iphone