<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Blog of developer Mikkel Ovesen &#187; cache</title>
	<atom:link href="http://blog.ovesens.net/tag/cache/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.ovesens.net</link>
	<description>My thoughts, stuff I need to remember or things I just want to share with the world</description>
	<lastBuildDate>Thu, 19 Jan 2012 11:55:29 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>NHibernate Membase caching provider</title>
		<link>http://blog.ovesens.net/2011/02/nhibernate-membase-caching-provider/</link>
		<comments>http://blog.ovesens.net/2011/02/nhibernate-membase-caching-provider/#comments</comments>
		<pubDate>Tue, 22 Feb 2011 16:22:40 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[membase]]></category>
		<category><![CDATA[memcache]]></category>
		<category><![CDATA[nhibernate]]></category>

		<guid isPermaLink="false">http://blog.ovesens.net/?p=322</guid>
		<description><![CDATA[Enyim has made a nice client library for both Memcache and Membase. Membase can be used as a free distributed caching platform. With the new provider model introduced in ASP.NET 4, it is now possible to write providers for output &#8230; <a href="http://blog.ovesens.net/2011/02/nhibernate-membase-caching-provider/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://memcached.enyim.com/">Enyim</a> has made a nice client library for both Memcache and Membase. Membase can be used as a free distributed caching platform.</p>
<p>With the new provider model introduced in ASP.NET 4, it is now possible to write providers for output caching and session state. Enyim has done just that with their <a href="https://github.com/enyim/memcached-providers">memcache-provider</a>.</p>
<p>I use NHibernate for some of my projects and being able to use a Membase cache provider for NHibernates second level cache would be very nice.</p>
<p>With help from <a href="http://dalager.com" target="_blank">Christian Dalager</a> and my self, such a library is now available. Take a look here <a href="https://bitbucket.org/ovesen/membasecacheprovider">https://bitbucket.org/ovesen/membasecacheprovider</a></p>
<p>Zipped binaries are provided with mapping against NHibernate 2.0.1, 2.1.0, 2.1.2 and NH3 here <a href="https://bitbucket.org/ovesen/membasecacheprovider/downloads">https://bitbucket.org/ovesen/membasecacheprovider/downloads</a></p>
<p>An example configuration file is provided in the downloads, but else you can see the options here:</p>
<pre class="brush: xml;">&lt;?xml version="1.0" encoding="utf-8" ?&gt;
&lt;configuration&gt;
    &lt;!-- This is a sample configuration file for using MembaseCacheProvider --&gt;

    &lt;configSections&gt;

        &lt;!-- (Required) Default configuration section for Membase --&gt;
        &lt;section name="membase" type="Membase.Configuration.MembaseClientSection, Membase" /&gt;

        &lt;!-- (Optional) An additional configuration section for Membase, can be used to target another bucket or servers --&gt;
        &lt;section name="membaseNhibernate" type="Membase.Configuration.MembaseClientSection, Membase" /&gt;

        &lt;!-- (Optional) A section for the Enyim Membase client, allows loggint to e.g. log4net --&gt;
        &lt;sectionGroup name="enyim.com"&gt;
            &lt;section name="log" type="Enyim.Caching.Configuration.LoggerSection, Enyim.Caching" /&gt;
        &lt;/sectionGroup&gt;

        &lt;!-- (Optional, but recommended) A configuration section that allows specific settings to be set for different NHibernate caching regions --&gt;
        &lt;section name="membaseNhibernateCache" type="MembaseCacheProvider.MembaseNhConfiguration, MembaseCacheProvider" /&gt;

    &lt;/configSections&gt;

    &lt;!-- (Optional) Defines that log4net should be used for Membase client logging, remember to define log4net configuration --&gt;
    &lt;enyim.com&gt;
        &lt;log factory="Enyim.Caching.Log4NetFactory, Enyim.Caching.Log4NetAdapter" /&gt;
    &lt;/enyim.com&gt;

    &lt;!-- (Required) Maps to the configuration section named "membase", defines a single server url and the "default" bucket --&gt;
    &lt;!-- Other configuration settings are available, look in the Enyim documentation and examples --&gt;
    &lt;!-- here http://memcached.enyim.com/ or here https://github.com/enyim/memcached-providers --&gt;
    &lt;membase&gt;
        &lt;servers bucket="default"&gt;
            &lt;add uri="http://localhost:8091/pools/default" /&gt;
        &lt;/servers&gt;
    &lt;/membase&gt;

    &lt;!-- (Optional) Maps to the configuration section name "membaseNhibernate", defines a single server url and the bucket "default_nhibernate" --&gt;
    &lt;membaseNhibernate&gt;
        &lt;servers bucket="default_nhibernate"&gt;
            &lt;add uri="http://localhost:8091/pools/default" /&gt;
        &lt;/servers&gt;

        &lt;!-- (Optional) Sets specific settings for the socket pool --&gt;
        &lt;socketPool minPoolSize="10" maxPoolSize="100" connectionTimeout="00:00:10" /&gt;

        &lt;!-- (Optional) Sets a specific locator --&gt;
        &lt;locator type="Enyim.Caching.Memcached.DefaultNodeLocator, Enyim.Caching" /&gt;
    &lt;/membaseNhibernate&gt;

    &lt;!-- (Optional, but recommended) Configuration settings of NHibernate second level cache, Membase provider --&gt;
    &lt;!-- The section attribute defines that the Membase configuration names "membaseNhibernate" should be used --&gt;
    &lt;membaseNhibernateCache section="membaseNhibernate"&gt;

        &lt;!-- Here expiration details are specified for the Nhibernate cache regions LongTerm and ShortTerm --&gt;
        &lt;!-- The expiration are defined in minutes, meaning LongTerm = 60 minutes and ShortTerm = 2 minutes --&gt;
        &lt;cache region="LongTerm" expiration="60" /&gt;
        &lt;cache region="ShortTerm" expiration="2" /&gt;
    &lt;/membaseNhibernateCache&gt;

&lt;/configuration&gt;</pre>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2011/02/nhibernate-membase-caching-provider/feed/</wfw:commentRss>
		<slash:comments>18</slash:comments>
		</item>
		<item>
		<title>Membase local development machine ip problem &#8211; FIXED</title>
		<link>http://blog.ovesens.net/2011/02/membase-local-development-machine-ip-problem-fixed/</link>
		<comments>http://blog.ovesens.net/2011/02/membase-local-development-machine-ip-problem-fixed/#comments</comments>
		<pubDate>Tue, 22 Feb 2011 12:05:18 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[debug]]></category>
		<category><![CDATA[error]]></category>
		<category><![CDATA[fix]]></category>
		<category><![CDATA[membase]]></category>

		<guid isPermaLink="false">http://blog.ovesens.net/?p=323</guid>
		<description><![CDATA[Membase caching stops working I struggled with Membase last night. I added NHibernate 2. level caching, Asp.net Session caching, Output caching and custom caching to some new Membase providers. It worked at first, but then suddenly nothing was cached, and &#8230; <a href="http://blog.ovesens.net/2011/02/membase-local-development-machine-ip-problem-fixed/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<h2>Membase caching stops working</h2>
<p>I struggled with Membase last night. I added NHibernate 2. level caching, Asp.net Session caching, Output caching and custom caching to some new Membase providers.</p>
<p>It worked at first, but then suddenly nothing was cached, and nothing was retrieved from the cache. I was working on my development machine and it seems like the problem is due to the network switching.</p>
<p>Well here is how it was solved. First navigate to the Membase server directory (C:\Program Files\Membase\Server\bin). Then execute the following commands.</p>
<p><code>service_stop.bat<br />
service_unregister.bat<br />
service_register.bat ns_1@127.0.0.1<br />
service_start.bat</code></p>
<p>Read the original here: <a href="http://blog.danhulton.com/2011/02/05/membase-on-windows-7-ip-address-fix/">http://blog.danhulton.com/2011/02/05/membase-on-windows-7-ip-address-fix/</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2011/02/membase-local-development-machine-ip-problem-fixed/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Membase cache item expiration issue</title>
		<link>http://blog.ovesens.net/2011/02/membase-cache-item-expiration-issue/</link>
		<comments>http://blog.ovesens.net/2011/02/membase-cache-item-expiration-issue/#comments</comments>
		<pubDate>Mon, 21 Feb 2011 17:08:42 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[Software]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[membase]]></category>
		<category><![CDATA[memcache]]></category>

		<guid isPermaLink="false">http://blog.ovesens.net/?p=306</guid>
		<description><![CDATA[Membase can be used as a distributed caching platform, and best of all&#8230; it is free to use. This is from their website: Unlimited use in development and up to two nodes may be deployed for free in a production &#8230; <a href="http://blog.ovesens.net/2011/02/membase-cache-item-expiration-issue/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.membase.org/" target="_blank">Membase</a> can be used as a distributed caching platform, and best of all&#8230; it is free to use. This is from <a href="http://www.membase.com/products-and-services/overview" target="_self">their website</a>:</p>
<blockquote><p>Unlimited use in development and up to two nodes may be deployed for free in a production cluster, with modest additional prices for additional nodes.</p></blockquote>
<p>When I started to develope and test Membase&#8230; I first wondered. Why does the total number of caching items continue to increase. Why do the number never decrease?</p>
<p>I expected items to be removed when their expiration time passed, but they didn&#8217;t and I started to wonder whether I was doing something wrong.</p>
<p>It turns out that Membase does not have an active expiration cache clean-up functionality. If an item has expired, it is not removed until the next Get, or when the cache is full and items need to be evicted.</p>
<p>Here is a very good description of the logic (<a href="http://forums.membase.org/node/197">Original here</a>):</p>
<blockquote><p>Your understanding of evictions is correct. When a new item is added and there is no space for it, an older item must be thrown away. One key thing to understand is that expiration and eviction have nothing to do with one another.</p>
<p>When an item expires, nothing actually happens. It is only upon the next access of that item that the server will notice it has expired and remove it from memory. There is also no guarantee that already expired items will be evicted first, though it practicality they are usually old enough that they tend to be among the first to get evicted (but nothing in the server controls this).</p>
<p>Now, onto the nuances of evictions. Within memcached there is a slab allocator which handles memory allocation for items. Basically, the whole memory space is broken up into 1mb pages which are then broken up into slabs of varying sizes. There are configuration options to control this, but that&#8217;s the default mechanism. Each size is called a &#8220;slab class&#8221; and has its own eviction logic which means that if one slab class fills up, you can&#8217;t reclaim memory from other slab classes. Depending on the variation in size of the objects that you are putting in, this could become more or less of a problem for you. Can you telnet to your servers and run &#8216;stats&#8217; and &#8216;stats slabs&#8217; and paste the output? That will let me understand exactly where memory is being taken up in your server.</p>
<p>Another key point to understand is that evictions are not always a bad thing. With changing workloads you can end up with lots of stale data still stored in cache that just needs to take some time to be pushed out. If everything seems to be running fine (with the exception of the evictions) then you probably don&#8217;t really need to take any action. Are you seeing a large number of misses as well? High misses and high evictions can usually be correlated to mean that the application is expecting data to be there when it&#8217;s not, and that would be a problem. If the application is receiving the data that it&#8217;s asking for, your cache is working correctly.</p>
<p>Your understanding of evictions is correct. When a new item is added and there is no space for it, an older item must be thrown away. One key thing to understand is that expiration and eviction have nothing to do with one another.When an item expires, nothing actually happens. It is only upon the next access of that item that the server will notice it has expired and remove it from memory. There is also no guarantee that already expired items will be evicted first, though it practicality they are usually old enough that they tend to be among the first to get evicted (but nothing in the server controls this).Now, onto the nuances of evictions. Within memcached there is a slab allocator which handles memory allocation for items. Basically, the whole memory space is broken up into 1mb pages which are then broken up into slabs of varying sizes. There are configuration options to control this, but that&#8217;s the default mechanism. Each size is called a &#8220;slab class&#8221; and has its own eviction logic which means that if one slab class fills up, you can&#8217;t reclaim memory from other slab classes. Depending on the variation in size of the objects that you are putting in, this could become more or less of a problem for you. Can you telnet to your servers and run &#8216;stats&#8217; and &#8216;stats slabs&#8217; and paste the output? That will let me understand exactly where memory is being taken up in your server.</p>
<p>Another key point to understand is that evictions are not always a bad thing. With changing workloads you can end up with lots of stale data still stored in cache that just needs to take some time to be pushed out. If everything seems to be running fine (with the exception of the evictions) then you probably don&#8217;t really need to take any action. Are you seeing a large number of misses as well? High misses and high evictions can usually be correlated to mean that the application is expecting data to be there when it&#8217;s not, and that would be a problem. If the application is receiving the data that it&#8217;s asking for, your cache is working correctly.</p></blockquote>
<p>So if it seems like the total number of your Membase  cache items just increases and increases, don&#8217;t worry it is actually normal behavior. Just make sure you have configured the server instance with the recommended amount of  server memory and disk resources. Take a look in the <a href="http://wiki.membase.org/display/membase/Membase+Best+Practices" target="_self">best practises</a>.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2011/02/membase-cache-item-expiration-issue/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Using SQL Cache Dependencies</title>
		<link>http://blog.ovesens.net/2007/09/using-sql-cache-dependencies/</link>
		<comments>http://blog.ovesens.net/2007/09/using-sql-cache-dependencies/#comments</comments>
		<pubDate>Sat, 29 Sep 2007 14:20:27 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[.net]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[mssql]]></category>
		<category><![CDATA[sql]]></category>

		<guid isPermaLink="false">/post/2007/09/29/Using-SQL-Cache-Dependencies.aspx</guid>
		<description><![CDATA[Original post here: http://www.asp.net/learn/data-access/tutorial-61-cs.aspx &#8212; Introduction The caching techniques examined in the Caching Data with the ObjectDataSource and Caching Data in the Architecture tutorials used a time-based expiry to evict the data from the cache after a specified period. This &#8230; <a href="http://blog.ovesens.net/2007/09/using-sql-cache-dependencies/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Original post here: <a title="http://www.asp.net/learn/data-access/tutorial-61-cs.aspx" href="http://www.asp.net/learn/data-access/tutorial-61-cs.aspx">http://www.asp.net/learn/data-access/tutorial-61-cs.aspx</a></p>
<p>&#8212;</p>
<h1>Introduction</h1>
<p>The caching techniques examined in the <a href="http://www.asp.net/tutorial-58-cs.aspx">Caching Data with the ObjectDataSource</a> and <a href="http://www.asp.net/tutorial-59-cs.aspx">Caching Data in the Architecture</a> tutorials used a time-based expiry to evict the data from the cache after a specified period. This approach is the simplest way to balance the performance gains of caching against data staleness. By selecting a time expiry of <i>x</i> seconds, a page developer concedes to enjoy the performance benefits of caching for only <i>x</i> seconds, but can rest easy that her data will never be stale longer than a maximum of <i>x</i> seconds. Of course, for static data, <i>x</i> can be extended to the lifetime of the web application, as was examined in the <a href="http://www.asp.net/tutorial-60-cs.aspx">Caching Data at Application Startup</a> tutorial.</p>
<p>When caching database data, a time-based expiry is often chosen for its ease of use but is frequently an inadequate solution. Ideally, the database data would remain cached until the underlying data has been modified in the database; only then would the cache be evicted. This approach maximizes the performance benefits of caching and minimizes the duration of stale data. However, in order to enjoy these benefits there must be some system in place that knows when the underlying database data has been modified and evicts the corresponding items from the cache. Prior to ASP.NET 2.0, page developers were responsible for implementing this system. </p>
<p>ASP.NET 2.0 provides a <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.sqlcachedependency.aspx"><code>SqlCacheDependency</code> class</a> and the necessary infrastructure to determine when a change has occurred in the database so that the corresponding cached items can be evicted. There are two techniques for determining when the underlying data has changed: notification and polling. After discussing the differences between notification and polling, we&#x2019;ll create the infrastructure necessary to support polling and then explore how to use the <code>SqlCacheDependency</code> class in declarative and programmatically scenarios.</p>
<h1>Understanding Notification and Polling</h1>
<p>There are two techniques that can be used to determine when the data in a database has been modified: notification and polling. With notification, the database automatically alerts the ASP.NET runtime when the results of a particular query have been changed since the query was last executed, at which point the cached items associated with the query are evicted. With polling, the database server maintains information about when particular tables have last been updated. The ASP.NET runtime periodically polls the database to check what tables have changed since they were entered into the cache. Those tables whose data has been modified have their associated cache items evicted.</p>
<p>The notification option requires less setup than polling and is more granular since it tracks changes at the query level rather than at the table level. Unfortunately, notifications are only available in the full editions of Microsoft SQL Server 2005 (i.e., the non-Express editions). However, the polling option can be used for all versions of Microsoft SQL Server from 7.0 to 2005. Since these tutorials use the Express edition of SQL Server 2005, we will focus on setting up and using the polling option. Consult the Further Reading section at the end of this tutorial for further resources on SQL Server 2005&#x2019;s notification capabilities.</p>
<p>With polling, the database must be configured to include a table named <code>AspNet_SqlCacheTablesForChangeNotification</code> that has three columns &#8211; <code>tableName</code>, <code>notificationCreated</code>, and <code>changeId</code>. This table contains a row for each table that has data that might need to be used in a SQL cache dependency in the web application. The <code>tableName</code> column specifies the name of the table while <code>notificationCreated</code> indicates the date and time the row was added to the table. The <code>changeId</code> column is of type <code>int</code> and has an initial value of 0. Its value is incremented with each modification to the table.</p>
<p>In addition to the <code>AspNet_SqlCacheTablesForChangeNotification</code> table, the database also needs to include triggers on each of the tables that may appear in a SQL cache dependency. These triggers are executed whenever a row is inserted, updated, or deleted and increment the table&#x2019;s <code>changeId</code> value in <code>AspNet_SqlCacheTablesForChangeNotification</code>.</p>
<p>The ASP.NET runtime tracks the current <code>changeId</code> for a table when caching data using a <code>SqlCacheDependency</code> object. The database is periodically checked and any <code>SqlCacheDependency</code> objects whose <code>changeId</code> differs from the value in the database are evicted since a differing <code>changeId</code> value indicates that there has been a change to the table since the data was cached. </p>
<h1>Step 1: Exploring the <code>aspnet_regsql.exe</code> Command Line Program</h1>
<p>With the polling approach the database must be setup to contain the infrastructure described above: a predefined table (<code>AspNet_SqlCacheTablesForChangeNotification</code>), a handful of stored procedures, and triggers on each of the tables that may be used in SQL cache dependencies in the web application. These tables, stored procedures, and triggers can be created through the command line program <code>aspnet_regsql.exe</code>, which is found in the <code>$WINDOWS$\Microsoft.NET\Framework\<i>version</i></code> folder. To create the <code>AspNet_SqlCacheTablesForChangeNotification</code> table and associated stored procedures, run the following from the command line:</p>
<p><code>/* For SQL Server authentication... */ aspnet_regsql.exe -S server -U user -P password -d database -ed /* For Windows Authentication... */ aspnet_regsql.exe -S server -E -d database -ed</code></p>
<p><b>Note:</b> To execute these commands the specified database login must be in the <a href="http://msdn2.microsoft.com/en-us/library/ms188685.aspx"><code>db_securityadmin</code></a> and <a href="http://msdn2.microsoft.com/en-us/library/ms190667.aspx"><code>db_ddladmin</code></a> roles. To examine the T-SQL sent to the database by the <code>aspnet_regsql.exe</code> command line program, refer to <a href="http://scottonwriting.net/sowblog/posts/10709.aspx">this blog entry</a>.</p>
<p>For example, to add the infrastructure for polling to a Microsoft SQL Server database named <code>pubs</code> on a database server named <code>ScottsServer</code> using Windows Authentication, navigate to the appropriate directory and, from the command line, enter:</p>
<p><code>aspnet_regsql.exe -S ScottsServer -E -d pubs -ed</code></p>
<p>After the database-level infrastructure has been added, we need to add the triggers to those tables that will be used in SQL cache dependencies. Use the <code>aspnet_regsql.exe</code> command line program again, but specify the table name using the <code>-t</code> switch and instead of using the <code>-ed</code> switch use <code>-et</code>, like so:</p>
<p><code>/* For SQL Server authentication... */ aspnet_regsql.exe -S &lt;i&gt;server&lt;/i&gt; -U &lt;i&gt;user&lt;/i&gt; -P &lt;i&gt;password&lt;/i&gt; -d &lt;i&gt;database&lt;/i&gt; -t &lt;i&gt;tableName&lt;/i&gt; -et /* For Windows Authentication... */ aspnet_regsql.exe -S &lt;i&gt;server&lt;/i&gt; -E -d &lt;i&gt;database&lt;/i&gt; -t &lt;i&gt;tableName&lt;/i&gt; -et</code></p>
<p>To add the triggers to the <code>authors</code> and <code>titles</code> tables on the <code>pubs</code> database on <code>ScottsServer</code>, use:</p>
<p><code>aspnet_regsql.exe -S ScottsServer -E -d pubs -t authors -et aspnet_regsql.exe -S ScottsServer -E -d pubs -t titles -et</code></p>
<p>For this tutorial add the triggers to the <code>Products</code>, <code>Categories</code>, and <code>Suppliers</code> tables. We&#x2019;ll look at the particular command line syntax in Step 3.</p>
<h1>Step 2: Referencing a Microsoft SQL Server 2005 Express Edition Database in <code>App_Data</code></h1>
<p>The <code>aspnet_regsql.exe</code> command line program requires the database and server name in order to add the necessary polling infrastructure. But what is the database and server name for a Microsoft SQL Server 2005 Express database that resides in the <code>App_Data</code> folder? Rather than having to discover what the database and server names are, I&#x2019;ve found that the simplest approach is to attach the database to the <code>localhost\SQLExpress</code> database instance and rename the data using <a href="http://msdn2.microsoft.com/en-us/library/ms174173.aspx">SQL Server Management Studio</a>. If you have one of the full versions of SQL Server 2005 installed on your machine, then you likely already have SQL Server Management Studio installed on your computer. If you only have the Express edition, you can download the free <a href="http://www.microsoft.com/downloads/details.aspx?displaylang=en&amp;FamilyID=C243A5AE-4BD1-4E3D-94B8-5A0F62BF7796">Microsoft SQL Server Management Studio Express Edition</a>.</p>
<p>Start by closing Visual Studio. Next, open SQL Server Management Studio and choose to connect to the <code>localhost\SQLExpress</code> server using Windows Authentication.</p>
<p><img alt="Attach to the localhost\SQLExpress&#xD;&#xA;Server" src="http://static.asp.net/asp.net/images/dataaccess3/61fig01CS.gif" /></p>
<p><strong>Figure 1</strong>: Attach to the <code>localhost\SQLExpress</code> Server</p>
<p>After connecting to the server, Management Studio will show the server and have subfolders for the databases, security, and so forth. Right-click on the Databases folder and choose the Attach option. This will bring up the Attach Databases dialog box (see Figure 2). Click the Add button and select the <code>NORTHWND.MDF</code> database folder in your web application&#x2019;s <code>App_Data</code> folder.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig02CS.png"><img alt="Attach the NORTHWND.MDF Database from&#xD;&#xA;the App_Data Folder" src="http://static.asp.net/asp.net/images/dataaccess3/61fig02CSs.gif" /></a></p>
<p><strong>Figure 2</strong>: Attach the <code>NORTHWND.MDF</code> Database from the <code>App_Data</code> Folder (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig02CS.png">Click to view full-size image</a>)</p>
<p>This will add the database to the Databases folder. The database name might be the full path to the database file or the full path prepended with a <a href="http://en.wikipedia.org/wiki/Globally_Unique_Identifier">GUID</a>. To avoid having to type in this lengthy database name when using the aspnet_regsql.exe command line tool, rename the database to a more human-friendly name by right-clicking on database just attached and choosing Rename. I&#x2019;ve renamed my database to &#x201C;DataTutorials&#x201D;.</p>
<p><img alt="Rename the Attached Database to a More Human-Friendly Name" src="http://static.asp.net/asp.net/images/dataaccess3/61fig03CS.gif" /></p>
<p><strong>Figure 3</strong>: Rename the Attached Database to a More Human-Friendly Name</p>
<h1>Step 3: Adding the Polling Infrastructure to the Northwind Database</h1>
<p>Now that we have attached the <code>NORTHWND.MDF</code> database from the <code>App_Data</code> folder, we&#x2019;re ready to add the polling infrastructure. Assuming that you&#x2019;ve renamed the database to &#x201C;DataTutorials&#x201D;, run the following four commands:</p>
<p><code>aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -ed aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Products -et aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Categories -et aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Suppliers -et</code></p>
<p>After running these four commands, right-click on the database name in Management Studio, go to the Tasks submenu, and choose Detach. Then close Management Studio and reopen Visual Studio.</p>
<p>Once Visual Studio has reopened, drill into the database through the Server Explorer. Note the new table (<code>AspNet_SqlCacheTablesForChangeNotification</code>), the new stored procedures, and the triggers on the <code>Products</code>, <code>Categories</code>, and <code>Suppliers</code> tables.</p>
<p><img alt="The Database Now Includes the Necessary Polling Infrastructure" src="http://static.asp.net/asp.net/images/dataaccess3/61fig04CS.gif" /></p>
<p><strong>Figure 4</strong>: The Database Now Includes the Necessary Polling Infrastructure</p>
<h1>Step 4: Configuring the Polling Service</h1>
<p>After creating the needed tables, triggers, and stored procedures in the database, the final step is to configure the polling service, which is done through <code>Web.config</code> by specifying the databases to use and the polling frequency in milliseconds. The following markup polls the Northwind database once every second.</p>
<p><code>&lt;?xml version=&quot;1.0&quot;?&gt; &lt;configuration&gt; &lt;connectionStrings&gt; &lt;add name=&quot;NORTHWNDConnectionString&quot; connectionString= &quot;Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF; Integrated Security=True;User Instance=True&quot; providerName=&quot;System.Data.SqlClient&quot;/&gt; &lt;/connectionStrings&gt; &lt;system.web&gt; ... &lt;!-- Configure the polling service used for SQL cache dependencies --&gt; &lt;caching&gt; &lt;sqlCacheDependency enabled=&quot;true&quot; pollTime=&quot;1000&quot; &gt; &lt;databases&gt; &lt;add name=&quot;NorthwindDB&quot; connectionStringName=&quot;NORTHWNDConnectionString&quot; /&gt; &lt;/databases&gt; &lt;/sqlCacheDependency&gt; &lt;/caching&gt; &lt;/system.web&gt; &lt;/configuration&gt;</code></p>
<p>The <code>name</code> value in the <code>&lt;add&gt;</code> element (&#x201C;NorthwindDB&#x201D;) associates a human-readable name with a particular database. When working with SQL cache dependencies, we&#x2019;ll need to refer to the database name defined here as well as the table that the cached data is based on. We&#x2019;ll see how to use the <code>SqlCacheDependency</code> class to programmatically associate SQL cache dependencies with cached data in Step 6.</p>
<p>Once a SQL cache dependency has been established, the polling system will connect to the databases defined in the <code>&lt;databases&gt;</code> elements every <code>pollTime</code> milliseconds and execute the <code>AspNet_SqlCachePollingStoredProcedure</code> stored procedure. This stored procedure &#8211; which was added back in Step 3 using the <code>aspnet_regsql.exe</code> command line tool &#8211; returns the <code>tableName</code> and <code>changeId</code> values for each record in <code>AspNet_SqlCacheTablesForChangeNotification</code>. Outdated SQL cache dependencies are evicted from the cache.</p>
<p>The <code>pollTime</code> setting introduces a tradeoff between performance and data staleness. A small <code>pollTime</code> value increases the number of requests to the database, but more quickly evicts stale data from the cache. A larger <code>pollTime</code> value reduces the number of database requests, but increases the delay between when the backend data changes and when the related cache items are evicted. Fortunately, the database request is executing a simple stored procedure that&#x2019;s returning just a few rows from a simple, lightweight table. But do experiment with different <code>pollTime</code> values to find an ideal balance between database access and data staleness for your application. The smallest <code>pollTime</code> value allowed is 500.</p>
<p><b>Note: </b>The above example provides a single <code>pollTime</code> value in the <code>&lt;sqlCacheDependency&gt;</code> element, but you can optionally specify the <code>pollTime</code> value in the <code>&lt;add&gt;</code> element. This is useful if you have multiple databases specified and want to customize the polling frequency per database.</p>
<h1>Step 5: Declaratively Working with SQL Cache Dependencies</h1>
<p>In Steps 1 through 4 we looked at how to setup the necessary database infrastructure and configure the polling system. With this infrastructure in place, we can now add items to the data cache with an associated SQL cache dependency using either programmatic or declarative techniques. In this step we&#x2019;ll examine how to declaratively work with SQL cache dependencies. In Step 6 we&#x2019;ll look at the programmatic approach.</p>
<p>The <a href="http://www.asp.net/tutorial-58-cs.aspx">Caching Data with the ObjectDataSource</a> tutorial explored the declarative caching capabilities of the ObjectDataSource. By simply setting the <code>EnableCaching</code> property to <code>true</code> and the <code>CacheDuration</code> property to some time interval, the ObjectDataSource will automatically cache the data returned from its underlying object for the specified interval. The ObjectDataSource can also use one or more SQL cache dependencies.</p>
<p>To demonstrate using SQL cache dependencies declaratively, open the <code>SqlCacheDependencies.aspx</code> page in the <code>Caching</code> folder and drag a GridView from the Toolbox onto the Designer. Set the GridView&#x2019;s <code>ID</code> to <code>ProductsDeclarative</code> and, from its smart tag, choose to bind it to a new ObjectDataSource named <code>ProductsDataSourceDeclarative</code>.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig05CS.png"><img alt="Create a New ObjectDataSource Named ProductsDataSourceDeclarative" src="http://static.asp.net/asp.net/images/dataaccess3/61fig05CS.gif" /></a></p>
<p><strong>Figure 5</strong>: Create a New ObjectDataSource Named <code>ProductsDataSourceDeclarative</code> (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig05CS.png">Click to view full-size image</a>)</p>
<p>Configure the ObjectDataSource to use the <code>ProductsBLL</code> class and set the drop-down list in the SELECT tab to <code>GetProducts()</code>. In the UPDATE tab, choose the <code>UpdateProduct</code> overload with three input parameters &#8211; <code>productName</code>, <code>unitPrice</code>, and <code>productID</code>. Set the drop-down lists to &#x201C;(None)&#x201D; in the INSERT and DELETE tabs.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig06CS.png"><img alt="Use the UpdateProduct Overload with Three Input Parameters" src="http://static.asp.net/asp.net/images/dataaccess3/61fig06CS.gif" /></a></p>
<p><strong>Figure 6</strong>: Use the UpdateProduct Overload with Three Input Parameters (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig06CS.png">Click to view full-size image</a>)</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig07CS.png"><img alt="Set the Drop-Down List to &#x201C;(None)&#x201D; for the INSERT and DELETE Tabs" src="http://static.asp.net/asp.net/images/dataaccess3/61fig07CS.gif" /></a></p>
<p><strong>Figure 7</strong>: Set the Drop-Down List to &#x201C;(None)&#x201D; for the INSERT and DELETE Tabs (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig07CS.png">Click to view full-size image</a>)</p>
<p>After completing the Configure Data Source wizard, Visual Studio will create BoundFields and CheckBoxFields in the GridView for each of the data fields. Remove all fields but <code>ProductName</code>, <code>CategoryName</code>, and <code>UnitPrice</code>, and format these fields as you see fit. From the GridView&#x2019;s smart tag, check the Enable Paging, Enable Sorting, and Enable Editing checkboxes. Visual Studio will set the ObjectDataSource&#x2019;s <code>OldValuesParameterFormatString</code> property to <code>original_{0}</code>. In order for the GridView&#x2019;s edit feature to work properly, either remove this property entirely from the declarative syntax or set it back to its default value, <code>{0}</code>.</p>
<p>Finally, add a Label Web control above the GridView and set its <code>ID</code> property to <code>ODSEvents</code> and its <code>EnableViewState</code> property to <code>false</code>. After making these changes, your page&#x2019;s declarative markup should look similar to the following. Note that I&#x2019;ve made a number of aesthetic customizations to the GridView fields that are not necessary to demonstrate the SQL cache dependency functionality.</p>
<p><code>&lt;asp:Label ID=&quot;ODSEvents&quot; runat=&quot;server&quot; EnableViewState=&quot;False&quot; /&gt; &lt;asp:GridView ID=&quot;ProductsDeclarative&quot; runat=&quot;server&quot; AutoGenerateColumns=&quot;False&quot; DataKeyNames=&quot;ProductID&quot; DataSourceID=&quot;ProductsDataSourceDeclarative&quot; AllowPaging=&quot;True&quot; AllowSorting=&quot;True&quot;&gt; &lt;Columns&gt; &lt;asp:CommandField ShowEditButton=&quot;True&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Product&quot; SortExpression=&quot;ProductName&quot;&gt; &lt;EditItemTemplate&gt; &lt;asp:TextBox ID=&quot;ProductName&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;' /&gt; &lt;asp:RequiredFieldValidator ID=&quot;RequiredFieldValidator1&quot; ControlToValidate=&quot;ProductName&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must provide a name for the product.&quot; SetFocusOnError=&quot;True&quot; runat=&quot;server&quot;&gt;*&lt;/asp:RequiredFieldValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label2&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;asp:BoundField DataField=&quot;CategoryName&quot; HeaderText=&quot;Category&quot; ReadOnly=&quot;True&quot; SortExpression=&quot;CategoryName&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Price&quot; SortExpression=&quot;UnitPrice&quot;&gt; &lt;EditItemTemplate&gt; $&lt;asp:TextBox ID=&quot;UnitPrice&quot; runat=&quot;server&quot; Columns=&quot;8&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:N2}&quot;) %&gt;'&gt;&lt;/asp:TextBox&gt; &lt;asp:CompareValidator ID=&quot;CompareValidator1&quot; runat=&quot;server&quot; ControlToValidate=&quot;UnitPrice&quot; ErrorMessage=&quot;You must enter a valid currency value with no currency symbols. Also, the value must be greater than or equal to zero.&quot; Operator=&quot;GreaterThanEqual&quot; SetFocusOnError=&quot;True&quot; Type=&quot;Currency&quot; Display=&quot;Dynamic&quot; ValueToCompare=&quot;0&quot;&gt;*&lt;/asp:CompareValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemStyle HorizontalAlign=&quot;Right&quot; /&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label1&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:c}&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;/Columns&gt; &lt;/asp:GridView&gt; &lt;asp:ObjectDataSource ID=&quot;ProductsDataSourceDeclarative&quot; runat=&quot;server&quot; SelectMethod=&quot;GetProducts&quot; TypeName=&quot;ProductsBLL&quot; UpdateMethod=&quot;UpdateProduct&quot;&gt; &lt;UpdateParameters&gt; &lt;asp:Parameter Name=&quot;productName&quot; Type=&quot;String&quot; /&gt; &lt;asp:Parameter Name=&quot;unitPrice&quot; Type=&quot;Decimal&quot; /&gt; &lt;asp:Parameter Name=&quot;productID&quot; Type=&quot;Int32&quot; /&gt; &lt;/UpdateParameters&gt; &lt;/asp:ObjectDataSource&gt;</code></p>
<p>Next, create an event handler for the ObjectDataSource&#x2019;s <code>Selecting</code> event and in it add the following code:</p>
<p><code>protected void ProductsDataSourceDeclarative_Selecting (object sender, ObjectDataSourceSelectingEventArgs e) { ODSEvents.Text = &quot;-- Selecting event fired&quot;; }</code></p>
<p>Recall that the ObjectDataSource&#x2019;s <code>Selecting</code> event fires only when retrieving data from its underlying object. If the ObjectDataSource accesses the data from its own cache, this event is not fired.</p>
<p>Now, visit this page through a browser. Since we&#x2019;ve yet to implement any caching, each time you page, sort, or edit the grid the page should display the text, &#x201C;ï¿½Selecting event fired&#x201D;, as Figure 8 shows.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig08CS.png"><img alt="The ObjectDataSource&#x2019;s Selecting&#xD;&#xA;Event Fires Each Time the GridView is Paged, Edited, or Sorted" src="http://static.asp.net/asp.net/images/dataaccess3/61fig08CSs.gif" /></a></p>
<p><strong>Figure 8</strong>: The ObjectDataSource&#x2019;s <code>Selecting</code> Event Fires Each Time the GridView is Paged, Edited, or Sorted (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig08CS.png">Click to view full-size image</a>)</p>
<p>As we saw in the <a href="http://www.asp.net/tutorial-58-cs.aspx">Caching Data with the ObjectDataSource</a> tutorial, setting the <code>EnableCaching</code> property to <code>true</code> causes the ObjectDataSource to cache its data for the duration specified by its <code>CacheDuration</code> property. The ObjectDataSource also has a <a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.sqlcachedependency.aspx"><code>SqlCacheDependency</code> property</a>, which adds one or more SQL cache dependencies to the cached data using the pattern:</p>
<p><code>databaseName1:tableName1;databaseName2:tableName2;...</code></p>
<p>Where <i>databaseName</i> is the name of the database as specified in the <code>name</code> attribute of the <code>&lt;add&gt;</code> element in <code>Web.config</code>, and <i>tableName</i> is the name of the database table. For example, to create an ObjectDataSource that caches data indefinitely based on a SQL cache dependency against the Northwind&#x2019;s <code>Products</code> table, set the ObjectDataSource&#x2019;s <code>EnableCaching</code> property to <code>true</code> and its <code>SqlCacheDependency</code> property to &#x201C;NorthwindDB:Products&#x201D;.</p>
<p><b>Note:</b> You can use a SQL cache dependency <i>and</i> a time-based expiry by setting <code>EnableCaching</code> to <code>true</code>, <code>CacheDuration</code> to the time interval, and <code>SqlCacheDependency</code> to the database and table name(s). The ObjectDataSource will evict its data when the time-based expiry is reached or when the polling system notes that the underlying database data has changed, whichever happens first.</p>
<p>The GridView in <code>SqlCacheDependencies.aspx</code> displays data from two tables &#8211; <code>Products</code> and <code>Categories</code> (the product&#x2019;s <code>CategoryName</code> field is retrieved via a <code>JOIN</code> on <code>Categories</code>). Therefore, we want to specify two SQL cache dependencies: &#x201C;NorthwindDB:Products;NorthwindDB:Categories&#x201D;.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig09CS.png"><img alt="Configure the ObjectDataSource to Support Caching Using SQL Cache&#xD;&#xA;Dependencies on Products and Categories" src="http://static.asp.net/asp.net/images/dataaccess3/61fig09CSs.gif" /></a></p>
<p><strong>Figure 9</strong>: Configure the ObjectDataSource to Support Caching Using SQL Cache Dependencies on <code>Products</code> and <code>Categories</code> (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig09CS.png">Click to view full-size image</a>)</p>
<p>After configuring the ObjectDataSource to support caching, revisit the page through a browser. Again, the text &#x201C;ï¿½Selecting event fired&#x201D; should appear on the first page visit, but should go away when paging, sorting, or clicking the Edit or Cancel buttons. This is because after the data is loaded into the ObjectDataSource&#x2019;s cache, it remains there until the <code>Products</code> or <code>Categories</code> tables are modified or the data is updated through the GridView.</p>
<p>After paging through the grid and noting the lack of the &#x201C;ï¿½Selecting event fired&#x201D; text, open a new browser window and navigate to the Basics tutorial in the Editing, Inserting, and Deleting section (<code>~/EditInsertDelete/Basics.aspx</code>). Update the name or price of a product. Then, from to the first browser window, view a different page of data, sort the grid, or click a row&#x2019;s Edit button. This time, the &#x201C;ï¿½Selecting event fired&#x201D; should reappear, as the underlying database data has been modified (see Figure 10). If the text does not appear, wait a few moments and try again. Remember that the polling service is checking for changes to the <code>Products</code> table every <code>pollTime</code> milliseconds, so there is a delay between when the underlying data is updated and when the cached data is evicted.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig10CS.png"><img alt="Modifying the Products Table Evicts the Cached Product Data" src="http://static.asp.net/asp.net/images/dataaccess3/61fig10CSs.gif" /></a></p>
<p><strong>Figure 10</strong>: Modifying the Products Table Evicts the Cached Product Data (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig10CS.png">Click to view full-size image</a>)</p>
<h1>Step 6: Programmatically Working with the <code>SqlCacheDependency</code> Class</h1>
<p>The <a href="http://www.asp.net/tutorial-59-cs.aspx">Caching Data in the Architecture</a> tutorial looked at the benefits of using a separate Caching Layer in the architecture as opposed to tightly coupling the caching with the ObjectDataSource. In that tutorial we created a <code>ProductsCL</code> class to demonstrate programmatically working with the data cache. To utilize SQL cache dependencies in the Caching Layer, use the <code>SqlCacheDependency</code> class.</p>
<p>With the polling system, a <code>SqlCacheDependency</code> object must be associated with a particular database and table pair. The following code, for example, creates a <code>SqlCacheDependency</code> object based on the Northwind database&#x2019;s <code>Products</code> table:</p>
<p><code>Caching.SqlCacheDependency productsTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Products&quot;);</code></p>
<p>The two input parameters to the <code>SqlCacheDependency</code>&#x2019;s constructor are the database and table names, respectively. Like with the ObjectDataSource&#x2019;s <code>SqlCacheDependency</code> property, the database name used is the same as the value specified in the <code>name</code> attribute of the <code>&lt;add&gt;</code> element in <code>Web.config</code>. The table name is the actual name of the database table.</p>
<p>To associate a <code>SqlCacheDependency</code> with an item added to the data cache, use one of the <code>Insert</code> method overloads that accepts a dependency. The following code adds <i>value</i> to the data cache for an indefinite duration, but associates it with a <code>SqlCacheDependency</code> on the <code>Products</code> table. In short, <i>value</i> will remain in the cache until it is evicted due to memory constraints or because the polling system has detected that the <code>Products</code> table has changed since it was cached.</p>
<p><code>Caching.SqlCacheDependency productsTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Products&quot;); Cache.Insert(key, value, productsTableDependency, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration);</code></p>
<p>The Caching Layer&#x2019;s <code>ProductsCL</code> class currently caches data from the <code>Products</code> table using a time-based expiry of 60 seconds. Let&#x2019;s update this class so that it uses SQL cache dependencies instead. The <code>ProductsCL</code> class&#x2019;s <code>AddCacheItem</code> method, which is responsible for adding the data to the cache, currently contains the following code:</p>
<p><code>private void AddCacheItem(string rawKey, object value) { System.Web.Caching.Cache DataCache = HttpRuntime.Cache; // Make sure MasterCacheKeyArray[0] is in the cache DataCache[MasterCacheKeyArray[0]] = DateTime.Now; // Add a CacheDependency Caching.CacheDependency dependency = new Caching.CacheDependency(null, MasterCacheKeyArray); DataCache.Insert(GetCacheKey(rawKey), value, dependency, DateTime.Now.AddSeconds(CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration); }</code></p>
<p>Update this code to use a <code>SqlCacheDependency</code> object instead of the <code>MasterCacheKeyArray</code> cache dependency:</p>
<p><code>private void AddCacheItem(string rawKey, object value) { System.Web.Caching.Cache DataCache = HttpRuntime.Cache; // Add the SqlCacheDependency objects for Products Caching.SqlCacheDependency productsTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Products&quot;); // Add the item to the data cache using productsTableDependency DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration); }</code></p>
<p>To test this functionality, add a GridView to the page beneath the existing <code>ProductsDeclarative</code> GridView. Set this new GridView&#x2019;s <code>ID</code> to <code>ProductsProgrammatic</code> and, through its smart tag, bind it to a new ObjectDataSource named <code>ProductsDataSourceProgrammatic</code>. Configure the ObjectDataSource to use the <code>ProductsCL</code> class, setting the drop-down lists in the SELECT and UPDATE tabs to <code>GetProducts</code> and <code>UpdateProduct</code>, respectively.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig11CS.png"><img alt="Configure the ObjectDataSource to Use the ProductsCL&#xD;&#xA;Class" src="http://static.asp.net/asp.net/images/dataaccess3/61fig11CS.gif" /></a></p>
<p><strong>Figure 11</strong>: Configure the ObjectDataSource to Use the <code>ProductsCL</code> Class (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig11CS.png">Click to view full-size image</a>)</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig12CS.png"><img alt="Select the GetProducts Method from&#xD;&#xA;the SELECT Tab&#x2019;s Drop-Down List" src="http://static.asp.net/asp.net/images/dataaccess3/61fig12CS.gif" /></a></p>
<p><strong>Figure 12</strong>: Select the <code>GetProducts</code> Method from the SELECT Tab&#x2019;s Drop-Down List (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig12CS.png">Click to view full-size image</a>)</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/61fig13CS.png"><img alt="Choose the UpdateProduct Method from the UPDATE Tab&#x2019;s Drop-Down&#xD;&#xA;List" src="http://static.asp.net/asp.net/images/dataaccess3/61fig13CS.gif" /></a></p>
<p><strong>Figure 13</strong>: Choose the UpdateProduct Method from the UPDATE Tab&#x2019;s Drop-Down List (<a href="http://static.asp.net/asp.net/images/dataaccess3/61fig13CS.png">Click to view full-size image</a>)</p>
<p>After completing the Configure Data Source wizard, Visual Studio will create BoundFields and CheckBoxFields in the GridView for each of the data fields. Like with the first GridView added to this page, remove all fields but <code>ProductName</code>, <code>CategoryName</code>, and <code>UnitPrice</code>, and format these fields as you see fit. From the GridView&#x2019;s smart tag, check the Enable Paging, Enable Sorting, and Enable Editing checkboxes. As with the <code>ProductsDataSourceDeclarative</code> ObjectDataSource, Visual Studio will set the <code>ProductsDataSourceProgrammatic</code> ObjectDataSource&#x2019;s <code>OldValuesParameterFormatString</code> property to <code>original_{0}</code>. In order for the GridView&#x2019;s edit feature to work properly, set this property back to <code>{0}</code> (or remove the property assignment from the declarative syntax altogether).</p>
<p>After completing these tasks, the resulting GridView and ObjectDataSource declarative markup should look like the following:</p>
<p><code>&lt;asp:GridView ID=&quot;ProductsProgrammatic&quot; runat=&quot;server&quot; AutoGenerateColumns=&quot;False&quot; DataKeyNames=&quot;ProductID&quot; DataSourceID=&quot;ProductsDataSourceProgrammatic&quot; AllowPaging=&quot;True&quot; AllowSorting=&quot;True&quot;&gt; &lt;Columns&gt; &lt;asp:CommandField ShowEditButton=&quot;True&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Product&quot; SortExpression=&quot;ProductName&quot;&gt; &lt;EditItemTemplate&gt; &lt;asp:TextBox ID=&quot;ProductName&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;' /&gt; &lt;asp:RequiredFieldValidator ID=&quot;RequiredFieldValidator1&quot; ControlToValidate=&quot;ProductName&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must provide a name for the product.&quot; SetFocusOnError=&quot;True&quot; runat=&quot;server&quot;&gt;*&lt;/asp:RequiredFieldValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label2&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;asp:BoundField DataField=&quot;CategoryName&quot; HeaderText=&quot;Category&quot; ReadOnly=&quot;True&quot; SortExpression=&quot;CategoryName&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Price&quot; SortExpression=&quot;UnitPrice&quot;&gt; &lt;EditItemTemplate&gt; $&lt;asp:TextBox ID=&quot;UnitPrice&quot; runat=&quot;server&quot; Columns=&quot;8&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:N2}&quot;) %&gt;'&gt;&lt;/asp:TextBox&gt; &lt;asp:CompareValidator ID=&quot;CompareValidator1&quot; runat=&quot;server&quot; ControlToValidate=&quot;UnitPrice&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must enter a valid currency value with no currency symbols. Also, the value must be greater than or equal to zero.&quot; Operator=&quot;GreaterThanEqual&quot; SetFocusOnError=&quot;True&quot; Type=&quot;Currency&quot; ValueToCompare=&quot;0&quot;&gt;*&lt;/asp:CompareValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemStyle HorizontalAlign=&quot;Right&quot; /&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label1&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:c}&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;/Columns&gt; &lt;/asp:GridView&gt; &lt;asp:ObjectDataSource ID=&quot;ProductsDataSourceProgrammatic&quot; runat=&quot;server&quot; OldValuesParameterFormatString=&quot;{0}&quot; SelectMethod=&quot;GetProducts&quot; TypeName=&quot;ProductsCL&quot; UpdateMethod=&quot;UpdateProduct&quot;&gt; &lt;UpdateParameters&gt; &lt;asp:Parameter Name=&quot;productName&quot; Type=&quot;String&quot; /&gt; &lt;asp:Parameter Name=&quot;unitPrice&quot; Type=&quot;Decimal&quot; /&gt; &lt;asp:Parameter Name=&quot;productID&quot; Type=&quot;Int32&quot; /&gt; &lt;/UpdateParameters&gt; &lt;/asp:ObjectDataSource&gt;</code></p>
<p>To test the SQL cache dependency in the Caching Layer set a breakpoint in the <code>ProductCL</code> class&#x2019;s <code>AddCacheItem</code> method and then start debugging. When you first visit <code>SqlCacheDependencies.aspx</code>, the breakpoint should be hit as the data is requested for the first time and placed into the cache. Next, move to another page in the GridView or sort one of the columns. This causes the GridView to requery its data, but the data should be found in the cache since the <code>Products</code> database table has not been modified. If the data is repeatedly not found in the cache, make sure there is sufficient memory available on your computer and try again.</p>
<p>After paging through a few pages of the GridView, open a second browser window and navigate to the Basics tutorial in the Editing, Inserting, and Deleting section (<code>~/EditInsertDelete/Basics.aspx</code>). Update a record from the Products table and then, from the first browser window, view a new page or click on one of the sorting headers.</p>
<p>In this scenario you will see one of two things: either the breakpoint will be hit, indicating that the cached data was evicted due to the change in the database; or, the breakpoint will not be hit, meaning that <code>SqlCacheDependencies.aspx</code> is now showing stale data. If the breakpoint is not hit, it is likely because the polling service has not yet fired since the data was changed. Remember that the polling service is checking for changes to the <code>Products</code> table every <code>pollTime</code> milliseconds, so there is a delay between when the underlying data is updated and when the cached data is evicted.</p>
<p><b>Note: </b>This delay is more likely to appear when editing one of the products through the GridView in <code>SqlCacheDependencies.aspx</code>. In the <a href="http://www.asp.net/tutorial-59-cs.aspx">Caching Data in the Architecture</a> tutorial we added the <code>MasterCacheKeyArray</code> cache dependency to ensure that the data being edited through the <code>ProductsCL</code> class&#x2019;s <code>UpdateProduct</code> method was evicted from the cache. However, we replaced this cache dependency when modifying the <code>AddCacheItem</code> method earlier in this step and therefore the <code>ProductsCL</code> class will continue to show the cached data until the polling system notes the change to the <code>Products</code> table. We&#x2019;ll see how to reintroduce the <code>MasterCacheKeyArray</code> cache dependency in Step 7.</p>
<h1>Step 7: Associating Multiple Dependencies with a Cached Item</h1>
<p>Recall that the <code>MasterCacheKeyArray</code> cache dependency is used to ensure that <i>all</i> product-related data is evicted from the cache when any single item associated within it is updated. For example, the <code>GetProductsByCategoryID(<i>categoryID</i>)</code> method caches <code>ProductsDataTables</code> instances for each unique <i>categoryID</i> value. If one of these objects is evicted, the <code>MasterCacheKeyArray</code> cache dependency ensures that the others are also removed. Without this cache dependency, when the cached data is modified the possibility exists that other cached product data may be out of date. Consequently, it&#x2019;s important that we maintain the <code>MasterCacheKeyArray</code> cache dependency when using SQL cache dependencies. However, the data cache&#x2019;s <code>Insert</code> method only allows for a single dependency object.</p>
<p>Furthermore, when working with SQL cache dependencies we may need to associate multiple database tables as dependencies. For example, the <code>ProductsDataTable</code> cached in the <code>ProductsCL</code> class contains the category and supplier names for each product, but the <code>AddCacheItem</code> method only uses a dependency on <code>Products</code>. In this situation, if the user updates the name of a category or supplier, the cached product data will remain in the cache and be out of date. Therefore, we want to make the cached product data dependent on not only the <code>Products</code> table, but on the <code>Categories</code> and <code>Suppliers</code> tables as well.</p>
<p>The <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.aggregatecachedependency.aspx"><code>AggregateCacheDependency</code> class</a> provides a means for associating multiple dependencies with a cache item. Start by creating an <code>AggregateCacheDependency</code> instance. Next, add the set of dependencies using the <code>AggregateCacheDependency</code>&#x2019;s <code>Add</code> method. When inserting the item into the data cache thereafter, pass in the <code>AggregateCacheDependency</code> instance. When <i>any</i> of the <code>AggregateCacheDependency</code> instance&#x2019;s dependencies change, the cached item will be evicted.</p>
<p>The following shows the updated code for the <code>ProductsCL</code> class&#x2019;s <code>AddCacheItem</code> method. The method creates the <code>MasterCacheKeyArray</code> cache dependency along with <code>SqlCacheDependency</code> objects for the <code>Products</code>, <code>Categories</code>, and <code>Suppliers</code> tables. These are all combined into one <code>AggregateCacheDependency</code> object named <code>aggregateDependencies</code>, which is then passed into the <code>Insert</code> method.</p>
<p><code>private void AddCacheItem(string rawKey, object value) { System.Web.Caching.Cache DataCache = HttpRuntime.Cache; // Make sure MasterCacheKeyArray[0] is in the cache and create a depedency DataCache[MasterCacheKeyArray[0]] = DateTime.Now; Caching.CacheDependency masterCacheKeyDependency = new Caching.CacheDependency(null, MasterCacheKeyArray); // Add the SqlCacheDependency objects for Products, Categories, and Suppliers Caching.SqlCacheDependency productsTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Products&quot;); Caching.SqlCacheDependency categoriesTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Categories&quot;); Caching.SqlCacheDependency suppliersTableDependency = new Caching.SqlCacheDependency(&quot;NorthwindDB&quot;, &quot;Suppliers&quot;); // Create an AggregateCacheDependency Caching.AggregateCacheDependency aggregateDependencies = new Caching.AggregateCacheDependency(); aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, categoriesTableDependency, suppliersTableDependency); DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration); }</code></p>
<p>Test this new code out. Now changes to the <code>Products</code>, <code>Categories</code>, or <code>Suppliers</code> tables cause the cached data to be evicted. Moreover, the <code>ProductsCL</code> class&#x2019;s <code>UpdateProduct</code> method, which is called when editing a product through the GridView, evicts the <code>MasterCacheKeyArray</code> cache dependency, which causes the cached <code>ProductsDataTable</code> to be evicted and the data to be re-retrieved on the next request.</p>
<p><b>Note:</b> SQL cache dependencies can also be used with <a href="http://quickstarts.asp.net/QuickStartv20/aspnet/doc/caching/output.aspx">output caching</a>. For a demonstration of this functionality, see: <a href="http://msdn2.microsoft.com/en-us/library/e3w8402y(VS.80).aspx">Using ASP.NET Output Caching with SQL Server</a>.</p>
<h1>Summary</h1>
<p>When caching database data, the data will ideally remain in the cache until it is modified in the database. With ASP.NET 2.0, SQL cache dependencies can be created and used in both declarative and programmatic scenarios. One of the challenges with this approach is in discovering when the data has been modified. The full versions of Microsoft SQL Server 2005 provide notification capabilities that can alert an application when a query result has changed. For the Express Edition of SQL Server 2005 and older versions of SQL Server, a polling system must be used instead. Fortunately, setting up the necessary polling infrastructure is fairly straightforward.</p>
<p>Happy Programming!</p>
<h1>Further Reading</h1>
<p>For more information on the topics discussed in this tutorial, refer to the following resources:</p>
<ul>
<li><a href="http://msdn2.microsoft.com/en-us/library/ms175110.aspx">Using Query Notifications in Microsoft SQL Server 2005</a> </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/ms188669.aspx">Creating a Query Notification</a> </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/ms178604(VS.80).aspx">Caching in ASP.NET with the <code>SqlCacheDependency</code> Class</a> </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/ms229862(vs.80).aspx">ASP.NET SQL Server Registration Tool (<code>aspnet_regsql.exe</code>)</a> </li>
<li><a href="http://www.aspnetresources.com/blog/sql_cache_depedency_overview.aspx">Overview of <code>SqlCacheDependency</code></a> </li>
</ul>
<h1>About the Author</h1>
<p><a href="http://www.4guysfromrolla.com/ScottMitchell.shtml">Scott Mitchell</a>, author of seven ASP/ASP.NET books and founder of <a href="http://www.4guysfromrolla.com">4GuysFromRolla.com</a>, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer. His latest book is <a href="http://www.amazon.com/exec/obidos/ASIN/0672327384/4guysfromrollaco"><i>Sams Teach Yourself ASP.NET 2.0 in 24 Hours</i></a>. He can be reached at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a> or via his blog, which can be found at <a href="http://ScottOnWriting.NET">http://ScottOnWriting.NET</a>.</p>
<h1>Special Thanks To&#x2026;</h1>
<p>This tutorial series was reviewed by many helpful reviewers. Lead reviewers for this tutorial were Marko Rangel, Teresa Murphy, and Hilton Giesenow. Interested in reviewing my upcoming MSDN articles? If so, drop me a line at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2007/09/using-sql-cache-dependencies/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Caching Data in the Architecture</title>
		<link>http://blog.ovesens.net/2007/09/caching-data-in-the-architecture/</link>
		<comments>http://blog.ovesens.net/2007/09/caching-data-in-the-architecture/#comments</comments>
		<pubDate>Sat, 29 Sep 2007 14:17:12 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[.net]]></category>
		<category><![CDATA[architecture]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>

		<guid isPermaLink="false">/post/2007/09/29/Caching-Data-in-the-Architecture.aspx</guid>
		<description><![CDATA[Original post here: http://www.asp.net/learn/data-access/tutorial-59-cs.aspx &#8212; Introduction As we saw in the preceding tutorial, caching the ObjectDataSource&#x2019;s data is as simple as setting a couple of properties. Unfortunately, the ObjectDataSource applies caching at the Presentation Layer, which tightly couples the caching &#8230; <a href="http://blog.ovesens.net/2007/09/caching-data-in-the-architecture/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[<p>Original post here: <a title="http://www.asp.net/learn/data-access/tutorial-59-cs.aspx" href="http://www.asp.net/learn/data-access/tutorial-59-cs.aspx">http://www.asp.net/learn/data-access/tutorial-59-cs.aspx</a></p>
<p>&#8212;</p>
<h1>Introduction</h1>
<p>As we saw in the preceding tutorial, caching the ObjectDataSource&#x2019;s data is as simple as setting a couple of properties. Unfortunately, the ObjectDataSource applies caching at the Presentation Layer, which tightly couples the caching policies with the ASP.NET page. One of the reasons for creating a layered architecture is to allow such couplings to be broken. The Business Logic Layer, for instance, decouples the business logic from the ASP.NET pages, while the Data Access Layer decouples the data access details. This decoupling of business logic and data access details is preferred, in part, because it makes the system more readable, more maintainable, and more flexible to change. It also allows for domain knowledge and division of labor &#x2013; a developer working on the Presentation Layer doesn&#x2019;t need to be familiar with the database&#x2019;s details in order to do her job. Decoupling the caching policy from the Presentation Layer offers similar benefits.</p>
<p>In this tutorial we will augment our architecture to include a <i>Caching Layer</i> (or CL for short) that employs our caching policy. The Caching Layer will include a <code>ProductsCL</code> class that provides access to product information with methods like <code>GetProducts()</code>, <code>GetProductsByCategoryID(<i>categoryID</i>)</code>, and so forth, that, when invoked, will first attempt to retrieve the data from the cache. If the cache is empty, these methods will invoke the appropriate <code>ProductsBLL</code> method in the BLL, which would in turn get the data from the DAL. The <code>ProductsCL</code> methods cache the data retrieved from the BLL before returning it.</p>
<p>As Figure 1 shows, the CL resides between the Presentation and Business Logic Layers.</p>
<p><img alt="The Caching Layer (CL) is Another Layer in Our&#xD;&#xA;  Architecture" src="http://static.asp.net/asp.net/images/dataaccess3/59fig01CS.png" /></p>
<p><strong>Figure 1</strong>: The Caching Layer (CL) is Another Layer in Our Architecture</p>
<h1>Step 1: Creating the Caching Layer Classes</h1>
<p>In this tutorial we will create a very simple CL with a single class &#x2013; <code>ProductsCL</code> &#x2013; that has only a handful of methods. Building a complete Caching Layer for the entire application would require creating <code>CategoriesCL</code>, <code>EmployeesCL</code>, and <code>SuppliersCL</code> classes, and providing a method in these Caching Layer classes for each data access or modification method in the BLL. As with the BLL and DAL, the Caching Layer should ideally be implemented as a separate Class Library project; however, we will implement it as a class in the <code>App_Code</code> folder.</p>
<p>To more cleanly separate the CL classes from the DAL and BLL classes, let&#x2019;s create a new subfolder in the <code>App_Code</code> folder. Right-click on the <code>App_Code</code> folder in the Solution Explorer, choose New Folder, and name the new folder <code>CL</code>. After creating this folder, add to it a new class named <code>ProductsCL.cs</code>.</p>
<p><img alt="Add a New Folder Named CL and a&#xD;&#xA;  Class Named ProductsCL.cs" src="http://static.asp.net/asp.net/images/dataaccess3/59fig02CS.png" /></p>
<p><strong>Figure 2</strong>: Add a New Folder Named <code>CL</code> and a Class Named <code>ProductsCL.cs</code></p>
<p>The <code>ProductsCL</code> class should include the same set of data access and modification methods as found in its corresponding Business Logic Layer class (<code>ProductsBLL</code>). Rather than creating all of these methods, let&#x2019;s just build a couple here to get a feel for the patterns used by the CL. In particular, we&#x2019;ll add the <code>GetProducts()</code> and <code>GetProductsByCategoryID(<i>categoryID</i>)</code> methods in Step 3 and an <code>UpdateProduct</code> overload in Step 4. You can add the remaining <code>ProductsCL</code> methods and <code>CategoriesCL</code>, <code>EmployeesCL</code>, and <code>SuppliersCL</code> classes at your leisure.</p>
<h1>Step 2: Reading and Writing to the Data Cache</h1>
<p>The ObjectDataSource caching feature explored in the preceding tutorial internally uses the ASP.NET data cache to store the data retrieved from the BLL. The data cache can also be accessed programmatically from ASP.NET pages&#x2019; code-behind classes or from the classes in the web application&#x2019;s architecture. To read and write to the data cache from an ASP.NET page&#x2019;s code-behind class, use the following pattern:</p>
<p><code>// Read from the cache object value = Cache[&quot;key&quot;]; // Add a new item to the cache Cache[&quot;key&quot;] = value; Cache.Insert(key, value); Cache.Insert(key, value, CacheDependency); Cache.Insert(key, value, CacheDependency, DateTime, TimeSpan);</code></p>
<p>The <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.cache.aspx"><code>Cache</code> class</a>&#x2019;s <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.cache.insert.aspx"><code>Insert</code> method</a> has a number of overloads. <code>Cache[&quot;<i>key</i>&quot;] = value</code> and <code>Cache.Insert(<i>key</i>, <i>value</i>)</code> are synonymous and both add an item to the cache using the specified key without a defined expiry. Typically, we want to specify an expiry when adding an item to the cache, either as a dependency, a time-based expiry, or both. Use one of the other <code>Insert</code> method&#x2019;s overloads to provide dependency- or time-based expiry information.</p>
<p>The Caching Layer&#x2019;s methods need to first check if the requested data is in the cache and, if so, return it from there. If the requested data is not in the cache, the appropriate BLL method needs to be invoked. Its return value should be cached and then returned, as the following sequence diagram illustrates.</p>
<p><img alt="The Caching Layer&#x2019;s Methods Return Data&#xD;&#xA;  from the Cache if it&#x2019;s Available" src="http://static.asp.net/asp.net/images/dataaccess3/59fig03CS.png" /></p>
<p><strong>Figure 3</strong>: The Caching Layer&#x2019;s Methods Return Data from the Cache if it&#x2019;s Available</p>
<p>The sequence depicted in Figure 3 is accomplished in the CL classes using the following pattern:</p>
<p><code>Type instance = Cache[&quot;key&quot;] as Type; if (instance == null) { instance = BllMethodToGetInstance(); Cache.Insert(key, instance, ...); } return instance;</code></p>
<p>Here, <i>Type</i> is the type of data being stored in the cache &#x2013; <code>Northwind.ProductsDataTable</code>, for example &#x2013; while <i>key</i> is the key that uniquely identifies the cache item. If the item with the specified <i>key</i> is not in the cache, then <i>instance</i> will be <code>null</code> and the data will be retrieved from the appropriate BLL method and added to the cache. By the time <code>return <i>instance</i></code> is reached, <i>instance</i> contains a reference to the data, either from the cache or pulled from the BLL.</p>
<p>Be sure to use the above pattern when accessing data from the cache. The following pattern, which, at first glance, looks equivalent, contains a subtle difference that introduces a race condition. Race conditions are difficult to debug because they reveal themselves sporadically and are difficult to reproduce.</p>
<p><code>if (Cache[&quot;key&quot;] == null) { Cache.Insert(key, BllMethodToGetInstance(), ...); } return Cache[&quot;key&quot;];</code></p>
<p>The difference in this second, incorrect code snippet is that rather than storing a reference to the cached item in a local variable, the data cache is accessed directly in the conditional statement <i>and</i> in the <code>return</code>. Imagine that when this code is reached, <code>Cache[&quot;<i>key</i>&quot;]</code> is non-<code>null</code>, but before the <code>return</code> statement is reached, the system evicts <i>key</i> from the cache. In this rare case, the code will return a <code>null</code> value rather than an object of the expected type. For an anecdote on how using the incorrect caching pattern can lead to sporadic unexpected behavior, see <a href="http://scottcate.mykb.com/Article_5CB26.aspx">this blog entry</a> by <a href="http://scottcate.mykb.com/">Scott Cate</a>.</p>
<p><b>Note:</b> The data cache is thread-safe, so you don&#x2019;t need to synchronize thread access for simple reads or writes. However, if you need to perform multiple operations on data in the cache that need to be atomic, you are responsible for implementing a lock or some other mechanism to ensure thread safety. See <a href="http://www.ddj.com/184406369">Synchronizing Access to the ASP.NET Cache</a> for more information.</p>
<p>An item can be programmatically evicted from the data cache using the <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.cache.remove.aspx"><code>Remove</code> method</a> like so:</p>
<p><code>Cache.Remove(key);</code></p>
<h1>Step 3: Returning Product Information from the <code>ProductsCL</code> Class</h1>
<p>For this tutorial let&#x2019;s implement two methods for returning product information from the <code>ProductsCL</code> class: <code>GetProducts()</code> and <code>GetProductsByCategoryID(<i>categoryID</i>)</code>. Like with the <code>ProductsBL</code> class in the Business Logic Layer, the <code>GetProducts()</code> method in the CL returns information about all of the products as a <code>Northwind.ProductsDataTable</code> object, while <code>GetProductsByCategoryID(<i>categoryID</i>)</code> returns all of the products from a specified category.</p>
<p>The following code shows a portion of the methods in the <code>ProductsCL</code> class:</p>
<p><code>[System.ComponentModel.DataObject] public class ProductsCL { private ProductsBLL _productsAPI = null; protected ProductsBLL API { get { if (_productsAPI == null) _productsAPI = new ProductsBLL(); return _productsAPI; } } [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, true)] public Northwind.ProductsDataTable GetProducts() { const string rawKey = &quot;Products&quot;; // See if the item is in the cache Northwind.ProductsDataTable products = _ GetCacheItem(rawKey) as Northwind.ProductsDataTable; if (products == null) { // Item not found in cache - retrieve it and insert it into the cache products = API.GetProducts(); AddCacheItem(rawKey, products); } return products; } [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, false)] public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID) { if (categoryID &lt; 0) return GetProducts(); else { string rawKey = string.Concat(&quot;ProductsByCategory-&quot;, categoryID); // See if the item is in the cache Northwind.ProductsDataTable products = _ GetCacheItem(rawKey) as Northwind.ProductsDataTable; if (products == null) { // Item not found in cache - retrieve it and insert it into the cache products = API.GetProductsByCategoryID(categoryID); AddCacheItem(rawKey, products); } return products; } } }</code></p>
<p>First, note the <code>DataObject</code> and <code>DataObjectMethodAttribute</code> attributes applied to the class and methods. These attributes provide information to the ObjectDataSource&#x2019;s wizard, indicating what classes and methods should appear in the wizard&#x2019;s steps. Since the CL classes and methods will be accessed from an ObjectDataSource in the Presentation Layer, I added these attributes to enhance the design-time experience. Refer back to the <a href="http://www.asp.net/tutorial-02-cs.aspx">Creating a Business Logic Layer</a> tutorial for a more thorough description on these attributes and their effects.</p>
<p>In the <code>GetProducts()</code> and <code>GetProductsByCategoryID(<i>categoryID</i>)</code> methods, the data returned from the <code>GetCacheItem(<i>key</i>)</code> method is assigned to a local variable. The <code>GetCacheItem(<i>key</i>)</code> method, which we&#x2019;ll examine shortly, returns a particular item from the cache based on the specified <i>key</i>. If no such data is found in cache, it is retrieved from the corresponding <code>ProductsBLL</code> class method and then added to the cache using the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method.</p>
<p>The <code>GetCacheItem(<i>key</i>)</code> and <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> methods interface with the data cache, reading and writing values, respectively. The <code>GetCacheItem(<i>key</i>)</code> method is the simpler of the two. It simply returns the value from the Cache class using the passed-in <i>key</i>:</p>
<p><code>private object GetCacheItem(string rawKey) { return HttpRuntime.Cache[GetCacheKey(rawKey)]; } private readonly string[] MasterCacheKeyArray = {&quot;ProductsCache&quot;}; private string GetCacheKey(string cacheKey) { return string.Concat(MasterCacheKeyArray[0], &quot;-&quot;, cacheKey); }</code></p>
<p><code>GetCacheItem(<i>key</i>)</code> does not use <i>key</i> value as supplied, but instead calls the <code>GetCacheKey(<i>key</i>)</code> method, which returns the <i>key</i> prepended with &#x201C;ProductsCache-&#x201D;. The <code>MasterCacheKeyArray</code>, which holds the string &#x201C;ProductsCache&#x201D;, is also used by the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method, as we&#x2019;ll see momentarily.</p>
<p>From an ASP.NET page&#x2019;s code-behind class, the data cache can be accessed using the <code>Page</code> class&#x2019;s <a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.page.cache.aspx"><code>Cache</code> property</a>, and allows for syntax like <code>Cache[&quot;key&quot;] = value</code>, as discussed in Step 2. From a class within the architecture, the data cache can be accessed using either <code>HttpRuntime.Cache</code> or <code>HttpContext.Current.Cache</code>. <a href="http://weblogs.asp.net/pjohnson/default.aspx">Peter Johnson</a>&#x2019;s blog entry <a href="http://weblogs.asp.net/pjohnson/archive/2006/02/06/437559.aspx">HttpRuntime.Cache vs. HttpContext.Current.Cache</a> notes the slight performance advantage in using <code>HttpRuntime</code> instead of <code>HttpContext.Current</code>; consequently, <code>ProductsCL</code> uses <code>HttpRuntime</code>.</p>
<p><b>Note:</b> If your architecture is implemented using Class Library projects then you will need to add a reference to the <code>System.Web</code> assembly in order to use the <a href="http://msdn2.microsoft.com/en-us/library/system.web.httpruntime.aspx">HttpRuntime</a> and <a href="http://msdn2.microsoft.com/en-us/library/system.web.httpcontext.aspx">HttpContext</a> classes.</p>
<p>If the item is not found in the cache, the <code>ProductsCL</code> class&#x2019;s methods get the data from the BLL and add it to the cache using the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method. To add <i>value</i> to the cache we could use the following code, which uses a 60 second time expiry:</p>
<p><code>const double CacheDuration = 60.0; private void AddCacheItem(string rawKey, object value) { HttpRuntime.Cache.Insert(GetCacheKey(rawKey), value, null, DateTime.Now.AddSeconds(CacheDuration), Caching.Cache.NoSlidingExpiration); }</code></p>
<p><code>DateTime.Now.AddSeconds(CacheDuration)</code> specifies the time-based expiry &#x2013; 60 seconds in the future &#x2013; while <a href="http://msdn2.microsoft.com/en-us/library/system.web.caching.cache.noslidingexpiration(vs.80).aspx"><code>System.Web.Caching.Cache.NoSlidingExpiration</code></a> indicates that there&#x2019;s no sliding expiration. While this <code>Insert</code> method overload has input parameters for both an absolute and sliding expiry, you can only provide one of the two. If you attempt to specify both an absolute time and a time span, the <code>Insert</code> method will throw an <code>ArgumentException</code> exception.</p>
<p><b>Note:</b> This implementation of the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method currently has some shortcomings. We&#x2019;ll address and overcome these issues in Step 4.</p>
<h1>Step 4: Invalidating the Cache When the Data is Modified Through the Architecture</h1>
<p>Along with data retrieval methods, the Caching Layer needs to provide the same methods as the BLL for inserting, updating, and deleting data. The CL&#x2019;s data modification methods do not modify the cached data, but rather call the BLL&#x2019;s corresponding data modification method and then invalidate the cache. As we saw in the preceding tutorial, this is the same behavior that the ObjectDataSource applies when its caching features are enabled and its <code>Insert</code>, <code>Update</code>, or <code>Delete</code> methods are invoked.</p>
<p>The following <code>UpdateProduct</code> overload illustrates how to implement the data modification methods in the CL:</p>
<p><code>[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)] public bool UpdateProduct(string productName, decimal? unitPrice, int productID) { bool result = API.UpdateProduct(productName, unitPrice, productID); // TODO: Invalidate the cache return result; }</code></p>
<p>The appropriate data modification Business Logic Layer method is invoked, but before its response is returned we need to invalidate the cache. Unfortunately, invalidating the cache is not straightforward because the <code>ProductsCL</code> class&#x2019;s <code>GetProducts()</code> and <code>GetProductsByCategoryID(<i>categoryID</i>)</code> methods each add items to the cache with different keys, and the <code>GetProductsByCategoryID(<i>categoryID</i>)</code> method adds a different cache item for each unique <i>categoryID</i>.</p>
<p>When invalidating the cache, we need to remove <i>all</i> of the items that may have been added by the <code>ProductsCL</code> class. This can be accomplished by associating a <i>cache dependency</i> with the each item added to the cache in the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method. In general, a cache dependency can be another item in the cache, a file on the file system, or data from a Microsoft SQL Server database. When the dependency changes or is removed from the cache, the cache items it is associated with are automatically evicted from the cache. For this tutorial, we want to create an additional item in the cache that serves as a cache dependency for all items added through the <code>ProductsCL</code> class. That way, all of these items can be removed from the cache by simply removing the cache dependency.</p>
<p>Let&#x2019;s update the <code>AddCacheItem(<i>key</i>, <i>value</i>)</code> method so that each item added to the cache through this method is associated with a single cache dependency:</p>
<p><code>private void AddCacheItem(string rawKey, object value) { System.Web.Caching.Cache DataCache = HttpRuntime.Cache; // Make sure MasterCacheKeyArray[0] is in the cache - if not, add it if (DataCache[MasterCacheKeyArray[0]] == null) DataCache[MasterCacheKeyArray[0]] = DateTime.Now; // Add a CacheDependency System.Web.Caching.CacheDependency dependency = new CacheDependency(null, MasterCacheKeyArray); DataCache.Insert(GetCacheKey(rawKey), value, dependency, DateTime.Now.AddSeconds(CacheDuration), System.Web.Caching.Cache.NoSlidingExpiration); }</code></p>
<p><code>MasterCacheKeyArray</code> is a string array that holds a single value, &#x201C;ProductsCache&#x201D;. First, a cache item is added to the cache and assigned the current date and time. If the cache item already exists, it is updated. Next, a cache dependency is created. The <a href="http://msdn2.microsoft.com/en-US/library/system.web.caching.cachedependency(VS.80).aspx"><code>CacheDependency</code> class</a>&#x2019;s constructor has a number of overloads, but the one being used in here expects two <code>string</code> array inputs. The first one specifies the set of files to be used as dependencies. Since we don&#x2019;t want to use any file-based dependencies, a value of <code>null</code> is used for the first input parameter. The second input parameter specifies the set of cache keys to use as dependencies. Here we specify our single dependency, <code>MasterCacheKeyArray</code>. The <code>CacheDependency</code> is then passed into the <code>Insert</code> method.</p>
<p>With this modification to <code>AddCacheItem(<i>key</i>, <i>value</i>)</code>, invaliding the cache is as simple as removing the dependency.</p>
<p><code>[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)] public bool UpdateProduct(string productName, decimal? unitPrice, int productID) { bool result = API.UpdateProduct(productName, unitPrice, productID); // Invalidate the cache InvalidateCache(); return result; } public void InvalidateCache() { // Remove the cache dependency HttpRuntime.Cache.Remove(MasterCacheKeyArray[0]); }</code></p>
<h1>Step 5: Calling the Caching Layer from the Presentation Layer</h1>
<p>The Caching Layer&#x2019;s classes and methods can be used to work with data using the techniques we&#x2019;ve examined throughout these tutorials. To illustrate working with cached data, save your changes to the <code>ProductsCL</code> class and then open the <code>FromTheArchitecture.aspx</code> page in the <code>Caching</code> folder and add a GridView. From the GridView&#x2019;s smart tag, create a new ObjectDataSource. In the wizard&#x2019;s first step you should see the <code>ProductsCL</code> class as one of the options from the drop-down list.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/59fig04CS.png"><img alt="The ProductsCL Class is Included in&#xD;&#xA;  the Business Object Drop-Down List" src="http://static.asp.net/asp.net/images/dataaccess3/59fig04CSs.png" /></a></p>
<p><strong>Figure 4</strong>: The <code>ProductsCL</code> Class is Included in the Business Object Drop-Down List (<a href="http://static.asp.net/asp.net/images/dataaccess3/59fig04CS.png">Click to view full-size image</a>)</p>
<p>After selecting <code>ProductsCL</code>, click Next. The drop-down list in the SELECT tab has two items &#8211; <code>GetProducts()</code> and <code>GetProductsByCategoryID(<i>categoryID</i>)</code> &#x2013; and the UPDATE tab has the sole <code>UpdateProduct</code> overload. Choose the <code>GetProducts()</code> method from the SELECT tab and the <code>UpdateProducts</code> method from the UPDATE tab and click Finish.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/59fig05CS.png"><img alt="The ProductsCL Class&#x2019;s Methods&#xD;&#xA;  are Listed in the Drop-Down Lists" src="http://static.asp.net/asp.net/images/dataaccess3/59fig05CSs.png" /></a></p>
<p><strong>Figure 5</strong>: The <code>ProductsCL</code> Class&#x2019;s Methods are Listed in the Drop-Down Lists (<a href="http://static.asp.net/asp.net/images/dataaccess3/59fig05CS.png">Click to view full-size image</a>)</p>
<p>After completing the wizard, Visual Studio will set the ObjectDataSource&#x2019;s <code>OldValuesParameterFormatString</code> property to <code>original_{0}</code> and add the appropriate fields to the GridView. Change the <code>OldValuesParameterFormatString</code> property back to its default value, <code>{0}</code>, and configure the GridView to support paging, sorting, and editing. Since the <code>UploadProducts</code> overload used by the CL accepts only the edited product&#x2019;s name and price, limit the GridView so that only these fields are editable.</p>
<p>In the preceding tutorial we defined a GridView to include fields for the <code>ProductName</code>, <code>CategoryName</code>, and <code>UnitPrice</code> fields. Feel free to replicate this formatting and structure, in which case your GridView and ObjectDataSource&#x2019;s declarative markup should look similar to the following:</p>
<p><code>&lt;asp:GridView ID=&quot;Products&quot; runat=&quot;server&quot; AutoGenerateColumns=&quot;False&quot; DataKeyNames=&quot;ProductID&quot; DataSourceID=&quot;ProductsDataSource&quot; AllowPaging=&quot;True&quot; AllowSorting=&quot;True&quot;&gt; &lt;Columns&gt; &lt;asp:CommandField ShowEditButton=&quot;True&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Product&quot; SortExpression=&quot;ProductName&quot;&gt; &lt;EditItemTemplate&gt; &lt;asp:TextBox ID=&quot;ProductName&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;' /&gt; &lt;asp:RequiredFieldValidator ID=&quot;RequiredFieldValidator1&quot; ControlToValidate=&quot;ProductName&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must provide a name for the product.&quot; SetFocusOnError=&quot;True&quot; runat=&quot;server&quot;&gt;*&lt;/asp:RequiredFieldValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label2&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;'&gt;&lt;/asp:Label&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;asp:BoundField DataField=&quot;CategoryName&quot; HeaderText=&quot;Category&quot; ReadOnly=&quot;True&quot; SortExpression=&quot;CategoryName&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Price&quot; SortExpression=&quot;UnitPrice&quot;&gt; &lt;EditItemTemplate&gt; $&lt;asp:TextBox ID=&quot;UnitPrice&quot; runat=&quot;server&quot; Columns=&quot;8&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:N2}&quot;) %&gt;'&gt;&lt;/asp:TextBox&gt; &lt;asp:CompareValidator ID=&quot;CompareValidator1&quot; runat=&quot;server&quot; ControlToValidate=&quot;UnitPrice&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must enter a valid currency value with no currency symbols. Also, the value must be greater than or equal to zero.&quot; Operator=&quot;GreaterThanEqual&quot; SetFocusOnError=&quot;True&quot; Type=&quot;Currency&quot; ValueToCompare=&quot;0&quot;&gt;*&lt;/asp:CompareValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemStyle HorizontalAlign=&quot;Right&quot; /&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label1&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:c}&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;/Columns&gt; &lt;/asp:GridView&gt; &lt;asp:ObjectDataSource ID=&quot;ProductsDataSource&quot; runat=&quot;server&quot; OldValuesParameterFormatString=&quot;{0}&quot; SelectMethod=&quot;GetProducts&quot; TypeName=&quot;ProductsCL&quot; UpdateMethod=&quot;UpdateProduct&quot;&gt; &lt;UpdateParameters&gt; &lt;asp:Parameter Name=&quot;productName&quot; Type=&quot;String&quot; /&gt; &lt;asp:Parameter Name=&quot;unitPrice&quot; Type=&quot;Decimal&quot; /&gt; &lt;asp:Parameter Name=&quot;productID&quot; Type=&quot;Int32&quot; /&gt; &lt;/UpdateParameters&gt; &lt;/asp:ObjectDataSource&gt;</code></p>
<p>At this point we have a page that uses the Caching Layer. To see the cache in action, set breakpoints in the <code>ProductsCL</code> class&#x2019;s <code>GetProducts()</code> and <code>UpdateProduct</code> methods. Visit the page in a browser and step through the code when sorting and paging in order to see the data pulled from the cache. Then update a record and note that the cache is invalidated and, consequently, it is retrieved from the BLL when the data is rebound to the GridView.</p>
<p><b>Note:</b> The Caching Layer provided in the download accompanying this article is not complete. It contains only one class, <code>ProductsCL</code>, which only sports a handful of methods. Moreover, only a single ASP.NET page uses the CL (<code>~/Caching/FromTheArchitecture.aspx</code>) &#x2013; all others still reference the BLL directly. If you plan on using a CL in your application, all calls from the Presentation Layer should go to the CL, which would require that the CL&#x2019;s classes and methods covered those classes and methods in the BLL currently used by the Presentation Layer.</p>
<h1>Summary</h1>
<p>While caching can be applied at the Presentation Layer with ASP.NET 2.0&#x2019;s SqlDataSource and ObjectDataSource controls, ideally caching responsibilities would be delegated to a separate layer in the architecture. In this tutorial we created a Caching Layer that resides between the Presentation Layer and the Business Logic Layer. The Caching Layer needs to provide the same set of classes and methods that exist in the BLL and are called from the Presentation Layer.</p>
<p>The Caching Layer examples we explored in this and the preceding tutorials exhibited <i>reactive loading</i>. With reactive loading, the data is loaded into the cache only when a request for the data is made and that data is missing from the cache. Data can also be <i>proactively loaded</i> into the cache, a technique that loads the data into the cache before it is actually needed. In the next tutorial we&#x2019;ll see an example of proactive loading when we look at how to store static values into the cache at application startup.</p>
<p>Happy Programming!</p>
<h1>About the Author</h1>
<p><a href="http://www.4guysfromrolla.com/ScottMitchell.shtml">Scott Mitchell</a>, author of seven ASP/ASP.NET books and founder of <a href="http://www.4guysfromrolla.com">4GuysFromRolla.com</a>, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer. His latest book is <a href="http://www.amazon.com/exec/obidos/ASIN/0672327384/4guysfromrollaco"><i>Sams Teach Yourself ASP.NET 2.0 in 24 Hours</i></a>. He can be reached at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a> or via his blog, which can be found at <a href="http://ScottOnWriting.NET">http://ScottOnWriting.NET</a>.</p>
<h1>Special Thanks To&#x2026;</h1>
<p>This tutorial series was reviewed by many helpful reviewers. Lead reviewer for this tutorial was Teresa Murph. Interested in reviewing my upcoming MSDN articles? If so, drop me a line at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2007/09/caching-data-in-the-architecture/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Caching Data with the ObjectDataSource</title>
		<link>http://blog.ovesens.net/2007/09/caching-data-with-the-objectdatasource/</link>
		<comments>http://blog.ovesens.net/2007/09/caching-data-with-the-objectdatasource/#comments</comments>
		<pubDate>Sat, 29 Sep 2007 14:15:22 +0000</pubDate>
		<dc:creator>Mikkel Ovesen</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[caching]]></category>
		<category><![CDATA[data]]></category>

		<guid isPermaLink="false">/post/2007/09/29/Caching-Data-with-the-ObjectDataSource.aspx</guid>
		<description><![CDATA[Original post here: http://www.asp.net/learn/data-access/tutorial-58-cs.aspx &#8212; Introduction In computer science, caching is the process of taking data or information that is expensive to obtain and storing a copy of it in a location that is quicker to access. For data-driven applications, &#8230; <a href="http://blog.ovesens.net/2007/09/caching-data-with-the-objectdatasource/">Continue reading <span class="meta-nav">&#8594;</span></a>]]></description>
			<content:encoded><![CDATA[</p>
<h4>Original post here: <a title="http://www.asp.net/learn/data-access/tutorial-58-cs.aspx" href="http://www.asp.net/learn/data-access/tutorial-58-cs.aspx">http://www.asp.net/learn/data-access/tutorial-58-cs.aspx</a></h4>
<p>&#8212;</p>
<h1>Introduction</h1>
<p>In computer science, c<i>aching</i> is the process of taking data or information that is expensive to obtain and storing a copy of it in a location that is quicker to access. For data-driven applications, large and complex queries commonly consume the majority of the application&#x2019;s execution time. Such an application&#x2019;s performance, then, can often be improved by storing the results of expensive database queries in the application&#x2019;s memory.</p>
<p>ASP.NET 2.0 offers a variety of caching options. An entire web page or User Control&#x2019;s rendered markup can be cached through <i>output caching</i>. The ObjectDataSource and SqlDataSource controls provide caching capabilities as well, thereby allowing data to be cached at the control level. And ASP.NET&#x2019;s <i>data cache</i> provides a rich caching API that enables page developers to programmatically cache objects. In this tutorial and the next three we&#x2019;ll examine using the ObjectDataSource&#x2019;s caching features as well as the data cache. We&#x2019;ll also explore how to cache application-wide data at startup and how to keep cached data fresh through the use of SQL cache dependencies. These tutorials do not explore output caching. For a detailed look at output caching, see <a href="http://aspnet.4guysfromrolla.com/articles/121306-1.aspx">Output Caching in ASP.NET 2.0</a>.</p>
<p>Caching can be applied at any place in the architecture, from the Data Access Layer up through the Presentation Layer. In this tutorial we&#x2019;ll look at applying caching to the Presentation Layer through the ObjectDataSource control. In the next tutorial we&#x2019;ll examine caching data at the Business Logic Layer.</p>
<h2>Key Caching Concepts</h2>
<p>Caching can greatly improve an application&#x2019;s overall performance and scalability by taking data that is expensive to generate and storing a copy of it in a location that can be more efficiently accessed. Since the cache holds just a copy of the actual, underlying data, it can become outdated, or <i>stale</i>, if the underlying data changes. To combat this, a page developer can indicate criteria by which the cache item will be <i>evicted</i> from the cache, using either:</p>
<ul>
<li><b>Time-based criteria</b> &#x2013; an item may be added to the cache for an absolute or sliding duration. For example, a page developer may indicate a duration of, say, 60 seconds. With an absolute duration, the cached item is evicted 60 seconds after it was added to cache, regardless of how frequently it was accessed. With a sliding duration, the cached item is evicted 60 seconds after the last access. </li>
<li><b>Dependency-based criteria</b> &#x2013; a dependency can be associated with an item when added to the cache. When the item&#x2019;s dependency changes it is evicted from the cache. The dependency may be a file, another cache item, or a combination of the two. ASP.NET 2.0 also allows SQL cache dependencies, which enable developers to add an item to the cache and have it evicted when the underlying database data changes. We will examine SQL cache dependencies in the upcoming <a href="http://www.asp.net/tutorial-61-cs.aspx">Using SQL Cache Dependencies</a> tutorial. </li>
</ul>
<p>Regardless of the eviction criteria specified, an item in the cache may be <i>scavenged</i> before the time-based or dependency-based criteria has been met. If the cache has reached its capacity, existing items must be removed before new ones can be added. Consequently, when programmatically working with cached data it&#x2019;s vital that you always assume that the cached data may not be present. We&#x2019;ll look at the pattern to use when accessing data from the cache programmatically in our next tutorial, <i>Caching Data in the Architecture</i>.</p>
<p>Caching provides an economical means for squeezing more performance from an application. As <a href="http://aspadvice.com/blogs/ssmith/">Steven Smith</a> articulates in his article <a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspnet-cachingtechniquesbestpract.asp">ASP.NET Caching: Techniques and Best Practices</a>:</p>
<p>&#x201C;Caching can be a good way to get &#x2018;good enough&#x2019; performance without requiring a lot of time and analysis. &#x2026; Memory is cheap, so if you can get the performance you need by caching the output for 30 seconds instead of spending a day or a week trying to optimize your code or database, do the caching solution (assuming 30-second old data is ok) and move on. &#x2026; Eventually, poor design will probably catch up to you, so of course you should try to design your applications correctly. But if you just need to get good enough performance today, caching can be an excellent [approach], buying you time to refactor your application at a later date when you have the time to do so.&#x201D;</p>
<p>While caching can provide appreciable performance enhancements, it is not applicable in all situations, such as with applications that use real-time, frequently-updating data, or where even shortly-lived stale data is unacceptable. But for the majority of applications, caching should be used. For more background on caching in ASP.NET 2.0, refer to the <a href="http://quickstarts.asp.net/QuickStartv20/aspnet/doc/caching/default.aspx">Caching for Performance</a> section of the <a href="http://quickstarts.asp.net/QuickStartv20/aspnet/">ASP.NET 2.0 QuickStart Tutorials</a>.</p>
<h1>Step 1: Creating the Caching Web Pages</h1>
<p>Before we start our exploration of the ObjectDataSource&#x2019;s caching features, let&#x2019;s first take a moment to create the ASP.NET pages in our website project that we&#x2019;ll need for this tutorial and the next three. Start by adding a new folder named <code>Caching</code>. Next, add the following ASP.NET pages to that folder, making sure to associate each page with the <code>Site.master</code> master page:</p>
<ul>
<li><code>Default.aspx</code></li>
<li><code>ObjectDataSource.aspx</code></li>
<li><code>FromTheArchitecture.aspx</code></li>
<li><code>AtApplicationStartup.aspx</code></li>
<li><code>SqlCacheDependencies.aspx</code></li>
</ul>
<p><img alt="Add the ASP.NET Pages for the Caching-Related Tutorials" src="http://static.asp.net/asp.net/images/dataaccess3/58fig01CS.png" /></p>
<p><strong>Figure 1</strong>: Add the ASP.NET Pages for the Caching-Related Tutorials</p>
<p>Like in the other folders, <code>Default.aspx</code> in the <code>Caching</code> folder will list the tutorials in its section. Recall that the <code>SectionLevelTutorialListing.ascx</code> User Control provides this functionality. Therefore, add this User Control to <code>Default.aspx</code> by dragging it from the Solution Explorer onto the page&#x2019;s Design view.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig02CS.png"><img alt="Figure 2: Add the&#xD;&#xA;  SectionLevelTutorialListing.ascx User Control to&#xD;&#xA;  Default.aspx" src="http://static.asp.net/asp.net/images/dataaccess3/58fig02CSs.png" /></a></p>
<p><strong>Figure 2</strong>: Figure 2: Add the <code>SectionLevelTutorialListing.ascx</code> User Control to <code>Default.aspx</code> (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig02CS.png">Click to view full-size image</a>)</p>
<p>Lastly, add these pages as entries to the <code>Web.sitemap</code> file. Specifically, add the following markup after the &#x201C;Working with Binary Data&#x201D; <code>&lt;siteMapNode&gt;</code>:</p>
<p><code>&lt;siteMapNode title=&quot;Caching&quot; url=&quot;~/Caching/Default.aspx&quot; description=&quot;Learn how to use the caching features of ASP.NET 2.0.&quot;&gt; &lt;siteMapNode url=&quot;~/Caching/ObjectDataSource.aspx&quot; title=&quot;ObjectDataSource Caching&quot; description=&quot;Explore how to cache data directly from the ObjectDataSource control.&quot; /&gt; &lt;siteMapNode url=&quot;~/Caching/FromTheArchitecture.aspx&quot; title=&quot;Caching in the Architecture&quot; description=&quot;See how to cache data from within the architecture.&quot; /&gt; &lt;siteMapNode url=&quot;~/Caching/AtApplicationStartup.aspx&quot; title=&quot;Caching Data at Application Startup&quot; description=&quot;Learn how to cache expensive or infrequently-changing queries at the start of the application.&quot; /&gt; &lt;siteMapNode url=&quot;~/Caching/SqlCacheDependencies.aspx&quot; title=&quot;Using SQL Cache Dependencies&quot; description=&quot;Examine how to have data automatically expire from the cache when its underlying database data is modified.&quot; /&gt; &lt;/siteMapNode&gt;</code></p>
<p>After updating <code>Web.sitemap</code>, take a moment to view the tutorials website through a browser. The menu on the left now includes items for the caching tutorials.</p>
<p><img alt="The Site Map Now Includes Entries for the Caching&#xD;&#xA;  Tutorials" src="http://static.asp.net/asp.net/images/dataaccess3/58fig03CS.png" /></p>
<p><strong>Figure 3</strong>: The Site Map Now Includes Entries for the Caching Tutorials</p>
<h1>Step 2: Displaying a List of Products in a Web Page</h1>
<p>This tutorial explores how to use the ObjectDataSource control&#x2019;s built-in caching features. Before we can look at these features, though, we first need a page to work from. Let&#x2019;s create a web page that uses a GridView to list product information retrieved by an ObjectDataSource from the <code>ProductsBLL</code> class.</p>
<p>Start by opening the <code>ObjectDataSource.aspx</code> page in the <code>Caching</code> folder. Drag a GridView from the Toolbox onto the Designer, set its <code>ID</code> property to <code>Products</code>, and, from its smart tag, choose to bind it to a new ObjectDataSource control named <code>ProductsDataSource</code>. Configure the ObjectDataSource to work with the <code>ProductsBLL</code> class.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig04CS.png"><img alt="Configure the ObjectDataSource to Use the&#xD;&#xA;  ProductsBLL Class" src="http://static.asp.net/asp.net/images/dataaccess3/58fig04CSs.png" /></a></p>
<p><strong>Figure 4</strong>: Configure the ObjectDataSource to Use the <code>ProductsBLL</code> Class (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig04CS.png">Click to view full-size image</a>)</p>
<p>For this page, let&#x2019;s create an editable GridView so that we can examine what happens when data cached in the ObjectDataSource is modified through the GridView&#x2019;s interface. Leave the drop-down list in the SELECT tab set to its default, <code>GetProducts()</code>, but change the selected item in the UPDATE tab to the <code>UpdateProduct</code> overload that accepts <code>productName</code>, <code>unitPrice</code>, and <code>productID</code> as its input parameters.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig05CS.png"><img alt="Set the UPDATE Tab&#x2019;s Drop-Down List to the&#xD;&#xA;  Appropriate UpdateProduct Overload" src="http://static.asp.net/asp.net/images/dataaccess3/58fig05CSs.png" /></a></p>
<p><strong>Figure 5</strong>: Set the UPDATE Tab&#x2019;s Drop-Down List to the Appropriate <code>UpdateProduct</code> Overload (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig05CS.png">Click to view full-size image</a>)</p>
<p>Finally, set the drop-down lists in the INSERT and DELETE tabs to &#x201C;(None)&#x201D; and click Finish. Upon completing the Configure Data Source wizard, Visual Studio sets the ObjectDataSource&#x2019;s <code>OldValuesParameterFormatString</code> property to <code>original_{0}</code>. As discussed in the <a href="http://www.asp.net/tutorial-16-cs.aspx">An Overview of Inserting, Updating, and Deleting Data</a> tutorial, this property needs to be removed from the declarative syntax or set back to its default value, <code>{0}</code>, in order for our update workflow to proceed without error.</p>
<p>Furthermore, at the completion of the wizard Visual Studio adds a field to the GridView for each of the product data fields. Remove all but the <code>ProductName</code>, <code>CategoryName</code>, and <code>UnitPrice</code> BoundFields. Next, update the <code>HeaderText</code> properties of each of these BoundFields to &#x201C;Product&#x201D;, &#x201C;Category&#x201D;, and &#x201C;Price&#x201D;, respectively. Since the <code>ProductName</code> field is required, convert the BoundField into a TemplateField and add a RequiredFieldValidator to the <code>EditItemTemplate</code>. Similarly, convert the <code>UnitPrice</code> BoundField into a TemplateField and add a CompareValidator to ensure that the value entered by the user is a valid currency value that&#x2019;s greater than or equal to zero. In addition to these modifications, feel free to perform any aesthetic changes, such as right-aligning the <code>UnitPrice</code> value, or specifying the formatting for the <code>UnitPrice</code> text in its read-only and editing interfaces.</p>
<p>Make the GridView editable by checking the &#x201C;Enable Editing&#x201D; checkbox in the GridView&#x2019;s smart tag. Also check the &#x201C;Enable Paging&#x201D; and &#x201C;Enable Sorting&#x201D; checkboxes.</p>
<p><b>Note:</b> Need a review of how to customize the GridView&#x2019;s editing interface? If so, refer back to the <a href="http://www.asp.net/tutorial-20-cs.aspx">Customizing the Data Modification Interface</a> tutorial.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig06CS.png"><img alt="Enable GridView Support for Editing, Sorting, and&#xD;&#xA;  Paging" src="http://static.asp.net/asp.net/images/dataaccess3/58fig06CSs.png" /></a></p>
<p><strong>Figure 6</strong>: Enable GridView Support for Editing, Sorting, and Paging (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig06CS.png">Click to view full-size image</a>)</p>
<p>After making these GridView modifications, the GridView and ObjectDataSource&#x2019;s declarative markup should look similar to the following:</p>
<p><code>&lt;asp:GridView ID=&quot;Products&quot; runat=&quot;server&quot; AutoGenerateColumns=&quot;False&quot; DataKeyNames=&quot;ProductID&quot; DataSourceID=&quot;ProductsDataSource&quot; AllowPaging=&quot;True&quot; AllowSorting=&quot;True&quot;&gt; &lt;Columns&gt; &lt;asp:CommandField ShowEditButton=&quot;True&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Product&quot; SortExpression=&quot;ProductName&quot;&gt; &lt;EditItemTemplate&gt; &lt;asp:TextBox ID=&quot;ProductName&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;'&gt;&lt;/asp:TextBox&gt; &lt;asp:RequiredFieldValidator ID=&quot;RequiredFieldValidator1&quot; Display=&quot;Dynamic&quot; ControlToValidate=&quot;ProductName&quot; SetFocusOnError=&quot;True&quot; ErrorMessage=&quot;You must provide a name for the product.&quot; runat=&quot;server&quot;&gt;*&lt;/asp:RequiredFieldValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label2&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;ProductName&quot;) %&gt;'&gt;&lt;/asp:Label&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;asp:BoundField DataField=&quot;CategoryName&quot; HeaderText=&quot;Category&quot; ReadOnly=&quot;True&quot; SortExpression=&quot;CategoryName&quot; /&gt; &lt;asp:TemplateField HeaderText=&quot;Price&quot; SortExpression=&quot;UnitPrice&quot;&gt; &lt;EditItemTemplate&gt; $&lt;asp:TextBox ID=&quot;UnitPrice&quot; runat=&quot;server&quot; Columns=&quot;8&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:N2}&quot;) %&gt;'&gt;&lt;/asp:TextBox&gt; &lt;asp:CompareValidator ID=&quot;CompareValidator1&quot; ControlToValidate=&quot;UnitPrice&quot; Display=&quot;Dynamic&quot; ErrorMessage=&quot;You must enter a valid currency value with no currency symbols. Also, the value must be greater than or equal to zero.&quot; Operator=&quot;GreaterThanEqual&quot; SetFocusOnError=&quot;True&quot; Type=&quot;Currency&quot; runat=&quot;server&quot; ValueToCompare=&quot;0&quot;&gt;*&lt;/asp:CompareValidator&gt; &lt;/EditItemTemplate&gt; &lt;ItemStyle HorizontalAlign=&quot;Right&quot; /&gt; &lt;ItemTemplate&gt; &lt;asp:Label ID=&quot;Label1&quot; runat=&quot;server&quot; Text='&lt;%# Bind(&quot;UnitPrice&quot;, &quot;{0:c}&quot;) %&gt;' /&gt; &lt;/ItemTemplate&gt; &lt;/asp:TemplateField&gt; &lt;/Columns&gt; &lt;/asp:GridView&gt; &lt;asp:ObjectDataSource ID=&quot;ProductsDataSource&quot; runat=&quot;server&quot; OldValuesParameterFormatString=&quot;{0}&quot; SelectMethod=&quot;GetProducts&quot; TypeName=&quot;ProductsBLL&quot; UpdateMethod=&quot;UpdateProduct&quot;&gt; &lt;UpdateParameters&gt; &lt;asp:Parameter Name=&quot;productName&quot; Type=&quot;String&quot; /&gt; &lt;asp:Parameter Name=&quot;unitPrice&quot; Type=&quot;Decimal&quot; /&gt; &lt;asp:Parameter Name=&quot;productID&quot; Type=&quot;Int32&quot; /&gt; &lt;/UpdateParameters&gt; &lt;/asp:ObjectDataSource&gt;</code></p>
<p>As Figure 7 shows, the editable GridView lists the name, category, and price of each of the products in the database. Take a moment to test out the page&#x2019;s functionality &#x2013; sort the results, page through them, and edit a record.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig07CS.png"><img alt="Each Product&#x2019;s Name, Category, and Price is&#xD;&#xA;  Listed in a Sortable, Pageable, Editable GridView" src="http://static.asp.net/asp.net/images/dataaccess3/58fig07CSs.png" /></a></p>
<p><strong>Figure 7</strong>: Each Product&#x2019;s Name, Category, and Price is Listed in a Sortable, Pageable, Editable GridView (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig07CS.png">Click to view full-size image</a>)</p>
<h1>Step 3: Examining When the ObjectDataSource is Requesting Data</h1>
<p>The <code>Products</code> GridView retrieves its data to display by invoking the <code>Select</code> method of the <code>ProductsDataSource</code> ObjectDataSource. This ObjectDataSource creates an instance of the Business Logic Layer&#x2019;s <code>ProductsBLL</code> class and calls its <code>GetProducts()</code> method, which in turn calls the Data Access Layer&#x2019;s <code>ProductsTableAdapter</code>&#x2019;s <code>GetProducts()</code> method. The DAL method connects to the Northwind database and issues the configured <code>SELECT</code> query. This data is then returned to the DAL, which packages it up in a <code>NorthwindDataTable</code>. The DataTable object is returned to the BLL, which returns it to the ObjectDataSource, which returns it to the GridView. The GridView then creates a <code>GridViewRow</code> object for each <code>DataRow</code> in the DataTable, and each <code>GridViewRow</code> is eventually rendered into the HTML that is returned to the client and displayed on the visitor&#x2019;s browser.</p>
<p>This sequence of events happens each and every time the GridView needs to bind to its underlying data. That happens when the page is first visited, when moving from one page of data to another, when sorting the GridView, or when modifying the GridView&#x2019;s data through its built-in editing or deleting interfaces. If the GridView&#x2019;s view state is disabled, the GridView will be rebound on each and every postback as well. The GridView can also be explicitly rebound to its data by calling its <code>DataBind()</code> method.</p>
<p>To fully appreciate the frequency with which the data is retrieved from the database, let&#x2019;s display a message indicating when the data is being re-retrieved. Add a Label Web control above the GridView named <code>ODSEvents</code>. Clear out its <code>Text</code> property and set its <code>EnableViewState</code> property to <code>false</code>. Underneath the Label, add a Button Web control and set its <code>Text</code> property to &#x201C;Postback&#x201D;.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig08CS.png"><img alt="Add a Label and Button to the Page Above the&#xD;&#xA;  GridView" src="http://static.asp.net/asp.net/images/dataaccess3/58fig08CSs.png" /></a></p>
<p><strong>Figure 8</strong>: Add a Label and Button to the Page Above the GridView (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig08CS.png">Click to view full-size image</a>)</p>
<p>During the data access workflow, the ObjectDataSource&#x2019;s <code>Selecting</code> event fires before the underlying object is created and its configured method invoked. Create an event handler for this event and add the following code:</p>
<p><code>protected void ProductsDataSource_Selecting(object sender, ObjectDataSourceSelectingEventArgs e) { ODSEvents.Text = &quot;-- Selecting event fired&quot;; }</code></p>
<p>Each time the ObjectDataSource makes a request to the architecture for data, the Label will display the text &#x201C;Selecting event fired&#x201D;.</p>
<p>Visit this page in a browser. When the page is first visited, the text &#x201C;Selecting event fired&#x201D; is shown. Click the &#x201C;Postback&#x201D; button and note that the text disappears (assuming that the GridView&#x2019;s <code>EnableViewState</code> property is set to <code>true</code>, the default). This is because, on postback, the GridView is reconstructed from its view state and therefore doesn&#x2019;t turn to the ObjectDataSource for its data. Sorting, paging, or editing the data, however, causes the GridView to rebind to its data source, and therefore the &#x201C;Selecting event fired&#x201D; text reappears.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig09CS.png"><img alt="Whenever the GridView is Rebound to its Data&#xD;&#xA;  Source, &#x201C;Selecting event fired&#x201D; is Displayed" src="http://static.asp.net/asp.net/images/dataaccess3/58fig09CSs.png" /></a></p>
<p><strong>Figure 9</strong>: Whenever the GridView is Rebound to its Data Source, &#x201C;Selecting event fired&#x201D; is Displayed (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig09CS.png">Click to view full-size image</a>)</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig10CS.png"><img alt="Clicking the Postback Button Causes the GridView&#xD;&#xA;  to be Reconstructed from its View State" src="http://static.asp.net/asp.net/images/dataaccess3/58fig10CSs.png" /></a></p>
<p><strong>Figure 10</strong>: Clicking the Postback Button Causes the GridView to be Reconstructed from its View State (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig10CS.png">Click to view full-size image</a>)</p>
<p>It may seem wasteful to retrieve the database data each time the data is paged through or sorted. After all, since we&#x2019;re using default paging, the ObjectDataSource has retrieved all of the records when displaying the first page. Even if the GridView does not provide sorting and paging support, the data must be retrieved from the database each time the page is first visited by any user (and on every postback, if view state is disabled). But if the GridView is showing the same data to all users, these extra database requests are superfluous. Why not cache the results returned from the <code>GetProducts()</code> method and bind the GridView to those cached results?</p>
<h1>Step 4: Caching the Data Using the ObjectDataSource</h1>
<p>By simply setting a few properties, the ObjectDataSource can be configured to automatically cache its retrieved data in the ASP.NET data cache. The following list summarizes the cache-related properties of the ObjectDataSource:</p>
<ul>
<li><a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.enablecaching.aspx">EnableCaching</a> &#x2013; must be set to <code>true</code> to enable caching. The default is <code>false</code>. </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.cacheduration.aspx">CacheDuration</a> &#x2013; the amount of time, in seconds, that the data is cached. The default is 0. The ObjectDataSource will only cache data if <code>EnableCaching</code> is <code>true</code> and <code>CacheDuration</code> is set to a value greater than zero. </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.cacheexpirationpolicy.aspx">CacheExpirationPolicy</a> &#x2013; can be set to <code>Absolute</code> or <code>Sliding</code>. If <code>Absolute</code>, the ObjectDataSource caches its retrieved data for <code>CacheDuration</code> seconds; if <code>Sliding</code>, the data expires only after it has not been accessed for <code>CacheDuration</code> seconds. The default is <code>Absolute</code>. </li>
<li><a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.cachekeydependency.aspx">CacheKeyDependency</a> &#x2013; use this property to associate the ObjectDataSource&#x2019;s cache entries with an existing cache dependency. The ObjectDataSource&#x2019;s data entries can be prematurely evicted from the cache by expiring its associated <code>CacheKeyDependency</code>. This property is most commonly used to associate a SQL cache dependency with the ObjectDataSource&#x2019;s cache, a topic we&#x2019;ll explore in the future <a href="http://www.asp.net/tutorial-61-cs.aspx">Using SQL Cache Dependencies</a> tutorial. </li>
</ul>
<p>Let&#x2019;s configure the <code>ProductsDataSource</code> ObjectDataSource to cache its data for 30 seconds on an absolute scale. Set the ObjectDataSource&#x2019;s <code>EnableCaching</code> property to <code>true</code> and its <code>CacheDuration</code> property to 30. Leave the <code>CacheExpirationPolicy</code> property set to its default, <code>Absolute</code>.</p>
<p><a href="http://static.asp.net/asp.net/images/dataaccess3/58fig11CS.png"><img alt="Configure the ObjectDataSource to Cache its Data&#xD;&#xA;  for 30 Seconds" src="http://static.asp.net/asp.net/images/dataaccess3/58fig11CSs.png" /></a></p>
<p><strong>Figure 11</strong>: Configure the ObjectDataSource to Cache its Data for 30 Seconds (<a href="http://static.asp.net/asp.net/images/dataaccess3/58fig11CS.png">Click to view full-size image</a>)</p>
<p>Save your changes and revisit this page in a browser. The &#x201C;Selecting event fired&#x201D; text will appear when you first visit the page, as initially the data is not in the cache. But subsequent postbacks triggered by clicking the &#x201C;Postback&#x201D; button, sorting, paging, or clicking the Edit or Cancel buttons does <i>not</i> redisplay the &#x201C;Selecting event fired&#x201D; text. This is because the <code>Selecting</code> event only fires when the ObjectDataSource gets its data from its underlying object; the <code>Selecting</code> event does not fire if the data is pulled from the data cache.</p>
<p>After 30 seconds, the data will be evicted from the cache. The data will also be evicted from the cache if the ObjectDataSource&#x2019;s <code>Insert</code>, <code>Update</code>, or <code>Delete</code> methods are invoked. Consequently, after 30 seconds have passed or the Update button has been clicked, sorting, paging, or clicking the Edit or Cancel buttons will cause the ObjectDataSource to get its data from its underlying object, displaying the &#x201C;Selecting event fired&#x201D; text when the <code>Selecting</code> event fires. These returned results are placed back into the data cache.</p>
<p><b>Note:</b> If you see the &#x201C;Selecting event fired&#x201D; text frequently, even when you expect the ObjectDataSource to be working with cached data, it may be due to memory constraints. If there is not enough free memory, the data added to the cache by the ObjectDataSource may have been scavenged. If the ObjectDataSource doesn&#x2019;t appear to be correctly caching the data or only caches the data sporadically, close some applications to free memory and try again.</p>
<p>Figure 12 illustrates the ObjectDataSource&#x2019;s caching workflow. When the &#x201C;Selecting event fired&#x201D; text appears on your screen, it is because the data was not in the cache and had to be retrieved from the underlying object. When this text is missing, however, it&#x2019;s because the data was available from the cache. When the data is returned from the cache there&#x2019;s no call to the underlying object and, therefore, no database query executed.</p>
<p><img alt="The ObjectDataSource Stores and Retrieves its&#xD;&#xA;  Data from the Data Cache" src="http://static.asp.net/asp.net/images/dataaccess3/58fig12CS.png" /></p>
<p><strong>Figure 12</strong>: The ObjectDataSource Stores and Retrieves its Data from the Data Cache</p>
<p>Each ASP.NET application has its own data cache instance that&#x2019;s shared across all pages and visitors. That means that the data stored in the data cache by the ObjectDataSource is likewise shared across all users who visit the page. To verify this, open the <code>ObjectDataSource.aspx</code> page in a browser. When first visiting the page, the &#x201C;Selecting event fired&#x201D; text will appear (assuming that the data added to the cache by previous tests has, by now, been evicted). Open a second browser instance and copy and paste the URL from the first browser instance to the second. In the second browser instance, the &#x201C;Selecting event fired&#x201D; text is not shown because it&#x2019;s using the same cached data as the first.</p>
<p>When inserting its retrieved data into the cache, the ObjectDataSource uses a cache key value that includes: the <code>CacheDuration</code> and <code>CacheExpirationPolicy</code> property values; the type of the underlying business object being used by the ObjectDataSource, which is specified via the <a href="http://msdn2.microsoft.com/en-us/library/system.web.ui.webcontrols.objectdatasource.typename.aspx"><code>TypeName</code> property</a> (<code>ProductsBLL</code>, in this example); the value of the <code>SelectMethod</code> property and the name and values of the parameters in the <code>SelectParameters</code> collection; and the values of its <code>StartRowIndex</code> and <code>MaximumRows</code> properties, which are used when implementing <a href="http://www.asp.net/tutorial-25-cs.aspx">custom paging.</a></p>
<p>Crafting the cache key value as a combination of these properties ensures a unique cache entry as these values change. For example, in past tutorials we&#x2019;ve looked at using the <code>ProductsBLL</code> class&#x2019;s <code>GetProductsByCategoryID(<i>categoryID</i>)</code>, which returns all products for a specified category. One user might come to the page and view beverages, which has a <code>CategoryID</code> of 1. If the ObjectDataSource cached its results without regard for the <code>SelectParameters</code>&#x2019; values, when another user came to the page to view condiments while the beverages products were in the cache, they&#x2019;d see the cached beverage products rather than condiments. By varying the cache key by these properties, which include the values of the <code>SelectParameters</code>, the ObjectDataSource maintains a separate cache entry for beverages and condiments.</p>
<h1>Stale Data Concerns</h1>
<p>The ObjectDataSource automatically evicts its items from the cache when any one of its <code>Insert</code>, <code>Update</code>, or <code>Delete</code> methods is invoked. This helps protect against stale data by clearing out the cache entries when the data is modified through the page. However, it is possible for an ObjectDataSource using caching to still display stale data. In the simplest case, it can be due to the data changing directly within the database. Perhaps a database administrator just ran a script that modifies some of the records in the database.</p>
<p>This scenario could also unfold in a more subtle way. While the ObjectDataSource evicts its items from the cache when one of its data modification methods is called, the cached items removed are for the ObjectDataSource&#x2019;s particular combination of property values (<code>CacheDuration</code>, <code>TypeName</code>, <code>SelectMethod</code>, and so on). If you have two ObjectDataSources that use different <code>SelectMethods</code> or <code>SelectParameters</code>, but still can update the same data, then one ObjectDataSource may update a row and invalidate its own cache entries, but the corresponding row for the second ObjectDataSource will still be served from the cached. I encourage you to create pages to exhibit this functionality. Create a page that displays an editable GridView that pulls its data from an ObjectDataSource that uses caching and is configured to get data from the <code>ProductsBLL</code> class&#x2019;s <code>GetProducts()</code> method. Add another editable GridView and ObjectDataSource to this page (or another one), but for this second ObjectDataSource have it use the <code>GetProductsByCategoryID(<i>categoryID</i>)</code> method. Since the two ObjectDataSources&#x2019; <code>SelectMethod</code> properties differ, they&#x2019;ll each have their own cached values. If you edit a product in one grid, the next time you bind the data back to the other grid (by paging, sorting, and so forth), it will still serve the old, cached data and not reflect the change that was made from the other grid.</p>
<p>In short, only use time-based expiries if you are willing to have the potential of stale data, and use shorter expiries for scenarios where the freshness of data is important. If stale data is not acceptable, either forgo caching or use SQL cache dependencies (assuming it is database data you&#x2019;re caching). We&#x2019;ll explore SQL cache dependencies in a future tutorial.</p>
<h1>Summary</h1>
<p>In this tutorial we examined the ObjectDataSource&#x2019;s built-in caching capabilities. By simply setting a few properties, we can instruct the ObjectDataSource to cache the results returned from the specified <code>SelectMethod</code> into the ASP.NET data cache. The <code>CacheDuration</code> and <code>CacheExpirationPolicy</code> properties indicate the duration the item is cached and whether it is an absolute or sliding expiration. The <code>CacheKeyDependency</code> property associates all of the ObjectDataSource&#x2019;s cache entries with an existing cache dependency. This can be used to evict the ObjectDataSource&#x2019;s entries from the cache before the time-based expiration is reached, and is typically used with SQL cache dependencies.</p>
<p>Since the ObjectDataSource simply caches its values to the data cache, we could replicate the ObjectDataSource&#x2019;s built-in functionality programmatically. It doesn&#x2019;t make sense to do this at the Presentation Layer, since the ObjectDataSource offers this functionality out of the box, but we can implement caching capabilities in a separate layer of the architecture. To do so, we&#x2019;ll need to repeat the same logic used by the ObjectDataSource. We&#x2019;ll explore how to programmatically work with the data cache from within the architecture in our next tutorial.</p>
<p>Happy Programming!</p>
<h1>Further Reading</h1>
<p>For more information on the topics discussed in this tutorial, refer to the following resources:</p>
<ul>
<li><a href="http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnaspp/html/aspnet-cachingtechniquesbestpract.asp">ASP.NET Caching: Techniques and Best Practices</a></li>
<li><a href="http://msdn.microsoft.com/practices/compcat/default.aspx?pull=/library/en-us/dnbda/html/cachingarch.asp">Caching Architecture Guide for .NET Framework Applications</a></li>
<li><a href="http://aspnet.4guysfromrolla.com/articles/121306-1.aspx">Output Caching in ASP.NET 2.0</a></li>
</ul>
<h1>About the Author</h1>
<p><a href="http://www.4guysfromrolla.com/ScottMitchell.shtml">Scott Mitchell</a>, author of seven ASP/ASP.NET books and founder of <a href="http://www.4guysfromrolla.com">4GuysFromRolla.com</a>, has been working with Microsoft Web technologies since 1998. Scott works as an independent consultant, trainer, and writer. His latest book is <a href="http://www.amazon.com/exec/obidos/ASIN/0672327384/4guysfromrollaco"><i>Sams Teach Yourself ASP.NET 2.0 in 24 Hours</i></a>. He can be reached at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a> or via his blog, which can be found at <a href="http://ScottOnWriting.NET">http://ScottOnWriting.NET</a>.</p>
<h1>Special Thanks To&#x2026;</h1>
<p>This tutorial series was reviewed by many helpful reviewers. Lead reviewer for this tutorial was Teresa Murphy. Interested in reviewing my upcoming MSDN articles? If so, drop me a line at <a href="mailto:mitchell@4GuysFromRolla.com">mitchell@4GuysFromRolla.com.</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.ovesens.net/2007/09/caching-data-with-the-objectdatasource/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

