ASP.NET 2.0数据教程之六十一:使用SQL Cache Dependencies(转载)

当缓存数据时,基于 时间周期的技术因为其易用性而常常被采用,不过又常常不那么完美。理想的状 态是这样的:数据库数据还是应缓存在内存,直到源数据(underlying data)发生 改变时才从内存清除。这样的话可以最大化的获取缓存带来的性能上的好处,同 时使“过时数据”(stale data)持续的时间最短。然而,为此,我们 需要建立一种机制来探测数据库数据什么时候发生了改变,并将对应的缓存条目 清除掉。

ASP.NET 2.0提供的SqlCacheDependency class类和必要的下部 基础构造(infrastructure)可以判断数据库什么时候发生了更改,以便将对应的 缓存条目从内存清除掉。有2种技术可以判断源数据在什么时候发生了改变: polling 和notification。讨论完这2者之间的差别后,我们将创建必要的下部基 础构造来支持polling,然后探讨如何使用SqlCacheDependency class类

理解Notification and Polling

正如前面所述,有2种方法来判断一个数 据库里的数据在什么时候修改过:notification 和 polling.当使用 notification的时候,数据库提示(alerts)ASP.NET对应某个具体查询的数据已经 发生了改变;于是对应的缓存条目将被清除。使用polling的时候,数据库服务器 将包含某个表(tables)最近发生更改时的相关信息。ASP.NET周期性的对数据库进 行检查,看哪些表在数据被缓存以后发生过改动,若改动过,对应的缓存条目将 被清除。

notification是对查询(query)而不是表(table)进行跟踪检查, 相对polling而言,需要采取的步骤要少些。不过遗憾的是,只有在Microsoft SQL Server 2005的完整版(也就是non-Express版本)才能使用该功能。而 Microsoft SQL Server的所有版本,从7.0 到2005都可以使用polling功能,因为 本系列教程使用的是SQL Server 2005的Express版本,在此我们将集中探讨建立 和使用polling。关于SQL Server 2005的notification功能,你可以参阅本文结 束部分的Further Reading。

要使用polling,我们将设置数据库包含一个 名为AspNet_SqlCacheTablesForChangeNotification的表。该表有3列: tableName, notificationCreated, 和changeId.对于哪些在web应用程序的SQL cache dependency里要用到的表,该表都有一条记录与之对应。tableName就是具 体某个表的名称;notificationCreated指明了添加记录时的date 和 time;而列 changeId的类型是int,初始值是0,每当对应的表发生一次改动,其值就自动增 加一次。

除了表AspNet_SqlCacheTablesForChangeNotification外,数据 库还需要为出现在SQL cache dependency里的每个表包含一个触发器(triggers), 任何时候,只要表插入、更新、删除一条记录或在表 AspNet_SqlCacheTablesForChangeNotification里的对应的changeId值增大的情 况下就会执行触发器。

当使用SqlCacheDependency对象(object)来缓存数 据时,ASP.NET将关注某个表的当前(current)的changeId值,一旦发现当前其值与 数据库里面的changeId值不同时,就将该SqlCacheDependency对象清除。因为, changeId不吻合就意味着在完成数据缓存后,表又发生过改动。

第一步: 考察命令行程序aspnet_regsql.exe

如上所述,使用polling方法时,必须 对数据库进行设置以包含这些基础构造:一个预先定义的表 (AspNet_SqlCacheTablesForChangeNotification),一些存储过程,以及基于在 SQL cache dependencies里要用到的表的触发器。诸如这些表、存储过程、触发 器等都可以通过命令行程序aspnet_regsql.exe来创建,该命令位于$WINDOWS$\Microsoft.NET\Framework\version文件夹。要创建表 AspNet_SqlCacheTablesForChangeNotification以及相关的存储过程,可以在命 令行这样运行:

/* 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

注意:要运行这些命令,必须以 db_securityadmin 和 db_ddladmin的身份登录数据库,更多详情请参阅作者博客 :http://scottonwriting.net/sowblog/posts/10709.aspx

比如:在 Windows身份认证模式下,对某个数据库服务器ScottsServer里的数据库pubs添加 基础构造时,在命令行键入:

aspnet_regsql.exe -S ScottsServer -E -d pubs -ed

完成了数据库级(database-level) 基础构造的添加后,我们需要添加触发器,再次使用aspnet_regsql.exe命令,不 过用-t来指定"表名"(table name),且将-ed替换为-et,如下:

/* For SQL Server authentication... */
aspnet_regsql.exe -S <i>server</i>
-U <i>user</i> -P <i>password</i> -d <i>database</i> -t <i>tableName</i> -et

/* For Windows Authentication... */
aspnet_regsql.exe -S <i>server</i>
-E -d <i>database</i> -t <i>tableName</i> -et

要对ScottsServer里的表 authors和titles添加触发器,这样做:

aspnet_regsql.exe -S ScottsServer -E -d pubs -t authors -et
aspnet_regsql.exe -S ScottsServer -E -d pubs -t titles -et

就本文而言,我们要对 表Products, Categories,和Suppliers添加触发器,具体的命令在第三步探讨。

第二步:在文件夹App_Data里引用一个Microsoft SQL Server 2005 Express版的数据库

我们刚刚说过,为了添加必要的基础构造, aspnet_regsql.exe命令需要用到数据库和服务器的名称。但是对于放在文件夹 App_Data里的一个Microsoft SQL Server 2005 Express的数据库而言,它的数据 库名和服务器名又是什么呢?犯不着探究其数据库名和服务器名到底是什么,我 发现最简单的方法是用SQL服务管理器(SQL Server Management Studio)来将该数 据库认作localhost\SQLExpress database数据库,并重新命名。如果你的机器里 已经安装了SQL Server 2005完整版,自然也就安装了SQL服务管理器。如果你安 装的是Express版本的话,你可以免费下载Microsoft SQL Server Management Studio Express Edition.

(http://www.microsoft.com/downloads/details.aspx? displaylang=en&FamilyID=C243A5AE-4BD1-4E3D-94B8-5A0F62BF7796)

首先,关闭Visual Studio,然后打开SQL Server Management Studio, 在Windows Authentication模式里选择连接到localhost\SQLExpress.

图1:连接到localhost\SQLExpress Server

连接到服务器后,管 理器将显示服务器,并将数据库、安全等以折叠的形式显示出来。在数据库文件 夹上右击,选添加(Attach)项,这样将弹出Attach Databases对话框(见图2),点 Add按钮,选择我们的web应用程序的App_Data文件夹里的NORTHWND.MDF数据库。

图2:选App_Data文件夹里的NORTHWND.MDF数据库

这样将会把数 据库添加到Databases文件夹,且数据库的名称可能是该数据库文件的绝对路径 (full path).出于简便的原则,我们将其重命名为一个更友好(human-friendly) 的名字,我将其命名为“DataTutorials”.

图3:将新添加的数据库重命名

第三步:对Northwind数据库添 加Polling基础构造

现在我们添加了App_Data文件夹里的NORTHWND.MDF数 据库,让我们添加polling 基础构造吧,假定你已经将数据库重命名为 “DataTutorials”, 运行如下的命令:

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

完成上述4个命令后,在Management Studio里右击 数据库,进入任务子菜单(Tasks submenu),选分派(Detach)。然后关闭 Management Studio并重新打开Visual Studio.

打开Visual Studio后,在 服务器资源管理器里展开数据库,你可以看到有新增的表 (AspNet_SqlCacheTablesForChangeNotification),新的存储过程,以及对应于表 Products, Categories, 和Suppliers的触发器.

图4:数据库包含了必需的Polling基础构造

第四步:设置 Polling服务

完成上述步骤后,最后我们需要设置polling服务。这要用到 Web.config文件,在里面指定要用到的数据库,以及检测频率(polling frequency),单位为毫秒。下面的代码是每隔1秒对Northwind数据库检测一次。

<?xml version="1.0"?>
<configuration>
  <connectionStrings>
    <add name="NORTHWNDConnectionString" connectionString=
     "Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;
      Integrated Security=True;User Instance=True"
       providerName="System.Data.SqlClient"/>
  </connectionStrings>
  <system.web>
   ...

   <!-- Configure the polling service used for SQL cache dependencies -->
   <caching>
     <sqlCacheDependency enabled="true" pollTime="1000" >
      <databases>
        <add name="NorthwindDB"
           connectionStringName="NORTHWNDConnectionString" />
      </databases>
     </sqlCacheDependency>
   </caching>
  </system.web>
</configuration>

在 <add>元素里的name值(“NorthwindDB”)是一个易读(human- readable)的名称,它与某个具体的数据库对应。当使用SQL cache dependencies 的时候,我们需要引用在这里定义的数据库名。我们将在第六步考察怎样使用 SqlCacheDependency class类来缓存数据。

一旦确定了一个SQL cache dependency后,检测系统(polling system)每隔定义的pollTime那么多毫秒对 <databases>元素里的数据库进行连接,并执行名为 AspNet_SqlCachePollingStoredProcedure的存储过——该存储过程是 我们在第三步使用aspnet_regsql.exe命令行工具添加的,它返回的是表 AspNet_SqlCacheTablesForChangeNotification里的每条记录的tableName 和 changeId值。那些“过时”的SQL cache dependencies将会从内存清 除掉。

应在权衡性能和数据刷新(data staleness)的基础上设置 pollTime.较小的pollTime值虽然导致请求数据库的次数增加,但能更快的将 “过时"的数据清除掉;较大的pollTime值虽然减少了对数据库的请求 次数,但增加了“过时”的缓存条目的呆滞时间。还好,对数据库的 请求只是执行一个简单的存储过程而已,该存储过程仅仅从一个简单的表返回很 少的几行。你最好多测试几个不同的pollTime值,在平衡数据库访问和数据刷新2 方面的情况下找出一个理想的值。pollTime值最小允许为500.

注意:在上 面的代码里,我们在<sqlCacheDependency>元素里指定了一个单一的 pollTime值。其实你也可以在<add>元素里随意的指定一个pollTime值。当 你指定了很多个数据库,且你想为每个数据库都指定一个检测频率(polling frequency)时,这样做很有用。

第五:声明SQL Cache Dependencies

在第一到第四步骤,我们探讨了如何建立必需的数据库基础 构造,以及设置检测系统(polling system).完成上述步骤后,现在我们可以通过 编程或声明的方式,在添加缓存条目时使用SQL cache dependency.在本节,我们 探讨如何使用声明的方式使用SQL cache dependencies,在第六步再探讨通过编 程的方式。

在《Caching Data with the ObjectDataSource》教程里,我 们考察了声明ObjectDataSource控件的缓存功能。仅仅将EnableCaching属性设置 为true,并将acheDuration属性设置为某个时间间 (timeinterval),ObjectDataSource控件就会自动地将从“源对象” (underlying object)返回的数据进行缓存。ObjectDataSource控件可以使用单个 或多个SQL cache dependencies.

为此,打开文件夹Caching里的 SqlCacheDependencies.aspx页面,在设计模式里,从工具箱拖一个GridView控件 到页面上,设置其ID为ProductsDeclarative ,从其智能标签里将其绑定到一个 名为ProductsDataSourceDeclarative的ObjectDataSource.

图5:创建一个名为ProductsDataSourceDeclarative的 ObjectDataSource

设置该ObjectDataSource使用ProductsBLL类。在 SELECT标签里选GetProducts()方法;在UPDATE标签里,选择包含3个输入参数 ——productName,unitPrice,和productID的UpdateProduct重载方法 ;在INSERT 和 DELETE标签里选“(None)”.

图6:使用包含3个输入参数的UpdateProduct重载方法

图7:在INSERT和DELETE标签的下拉列表里选“(None) ”

完成设置后,Visual Studio会为GridView里的每一列创建绑定列 (BoundFields) 和CheckBoxFieldsL列。将ProductName, CategoryName, 和 UnitPrice以外的列都删除,对其应用什么格式化都可以。在GridView的智能标签 里启用分页、排序、编辑功能。Visual Studio会将ObjectDataSource控件的 OldValuesParameterFormatString属性设置为original_{0},为使GridView的编 辑功能运行正常,要么删除该属性,要么将其设置为默认值:{0}.

最后, 在GridView上面添加一个Label Web控件,设置其ID为ODSEvents,再将其 EnableViewState属性设置为false.做完上述修改后,页面的声明代码看起来应该 和下面的差不多。注意,我已经对GridView列的外观做了些定制,虽然这对SQL cache dependency功能来说并不是必要的。

<asp:Label ID="ODSEvents" runat="server" EnableViewState="False" />

<asp:GridView ID="ProductsDeclarative" runat="server"
   AutoGenerateColumns="False" DataKeyNames="ProductID"
   DataSourceID="ProductsDataSourceDeclarative"
   AllowPaging="True" AllowSorting="True">
   <Columns>
    <asp:CommandField ShowEditButton="True" />
     <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
       <EditItemTemplate>
        <asp:TextBox ID="ProductName" runat="server"
           Text='<%# Bind("ProductName") %>' />
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
           ControlToValidate="ProductName" Display="Dynamic"
           ErrorMessage="You must provide a name for the product."
          SetFocusOnError="True"
            runat="server">*</asp:RequiredFieldValidator>
      </EditItemTemplate>
       <ItemTemplate>
        <asp:Label ID="Label2" runat="server"
           Text='<%# Bind("ProductName") %>' />
      </ItemTemplate>
     </asp:TemplateField>
    <asp:BoundField DataField="CategoryName" HeaderText="Category"
      ReadOnly="True" SortExpression="CategoryName" />
     <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
       <EditItemTemplate>
        $<asp:TextBox ID="UnitPrice" runat="server" Columns="8"
          Text='<%# Bind ("UnitPrice", "{0:N2}") % >'></asp:TextBox>
         <asp:CompareValidator ID="CompareValidator1" runat="server"
           ControlToValidate="UnitPrice"
           ErrorMessage="You must enter a valid currency value with
             no currency symbols. Also, the value must be greater than
            or equal to zero."
          Operator="GreaterThanEqual" SetFocusOnError="True"
           Type="Currency" Display="Dynamic"
            ValueToCompare="0">*</asp:CompareValidator>
       </EditItemTemplate>
      <ItemStyle HorizontalAlign="Right" />
       <ItemTemplate>
        <asp:Label ID="Label1" runat="server"
           Text='<%# Bind("UnitPrice", "{0:c}") % >' />
      </ItemTemplate>
     </asp:TemplateField>
  </Columns>
</asp:GridView>

<asp:ObjectDataSource ID="ProductsDataSourceDeclarative" runat="server"
  SelectMethod="GetProducts" TypeName="ProductsBLL"
   UpdateMethod="UpdateProduct">
   <UpdateParameters>
    <asp:Parameter Name="productName" Type="String" />
     <asp:Parameter Name="unitPrice" Type="Decimal" />
    <asp:Parameter Name="productID" Type="Int32" />
  </UpdateParameters>
</asp:ObjectDataSource>

下一步,为ObjectDataSource 控件的Selecting事件创建一个事件处理器:

protected void ProductsDataSourceDeclarative_Selecting
  (object sender, ObjectDataSourceSelectingEventArgs e)
{
  ODSEvents.Text = "-- Selecting event fired";
}

我们知道,只 有当ObjectDataSource控件从它的相关“源对象”(underlying object)获取数据时才会触发它的Selecting事件。如果ObjectDataSource是从内 存检索数据的话,将不会触发Selecting事件.

现在,在浏览器里登录该页 面,因为我们还没有进行缓存,所以每当你分页、排序、编辑时,页面都会显示 文本——“—Selecting event fired”, 如图8所示 :

图8:当分页、排序、编辑时都会触发ObjectDataSource的Selecting事 件。

就像我们在教程《Caching Data with the ObjectDataSource》里探 讨的一样,除了EnableCaching属性以外,ObjectDataSource控件还有 SqlCacheDependency property属性,它可以为缓存数据添加一个或更多的

SQL cache dependencies.像下面这样:

databaseName1:tableName1;databaseName2:tableName2;...

其中 ,databaseName是Web.config文件里<add>元素的name属性指定的数据库名 ,而tableName就是数据库里的一个表。举个例,要创建一个ObjectDataSource, 它用SQL cache dependency来缓存数据,当我们指定要用Northwind数据库里的 Products表时,我们将ObjectDataSource的EnableCaching属性设置为true,且 SqlCacheDependency属性为“NorthwindDB:Products”.

注意 :你可以通过设置EnableCaching属性为true来使用一个 SQL cache dependency 和基于时间的缓存期(time-based expiry)。CacheDuration对应时间间隔; SqlCacheDependency对应数据库名和表名。不管是缓存到期还是检查系统 (polling system)发现“源数据”发生改变,只要其中一个发生, ObjectDataSource 都会将清除其数据。

在SqlCacheDependencies.aspx页 面里的GridView 控件从2个表获取数据——Products 和 Categories (产品的CategoryName列是通过语法 JOIN on Categories来获取的). 因此,我们 想指定2个SQL cache dependencies:

“NorthwindDB:Products;NorthwindDB:Categories& rdquo;.

图9:设置ObjectDataSource支持缓存,且使用基于表Products 和 Categories的SQL Cache Dependencies

设置SQL Cache Dependencies支持 缓存后,再次来浏览器里登录页面。最开始,文本“—Selecting event fired”依然会出现在页面里,但当进行分页、排序或点击编辑和取 消按钮时,文本就消失了。这是因为对数据进行缓存后,其缓存状态一直持续, 直到Products 或 Categories表发生了改变,或我们通过GridView对数据进行了 更新。

做个实验,在第一个浏览器窗口进行分页操作,请注意文本 “—Selecting event fired”并没有显现出来。再打开第2个浏 览器窗口,导航到页面Basics.aspx页面(~/EditInsertDelete/Basics.aspx). 对 某个产品的name 或 price进行更新。 再次返回到第一个浏览器窗口,查看下一 个页面或进行排序操作或点击某行的编辑按钮,这一次,文本 “—Selecting event fired又出现了,这是因此“源数据 ”发生了更改(见图10)。如果文本没有出现,请稍等一下再试一回。我们知 道,polling服务每隔设定的pollTime那么多毫秒对Products表进行检查,开是否 改动过。因此在源数据的更新和“过时”数据的清除之间有个延迟期 。

图10:改动Products表将导致清除“过时”的Product缓存 数据

第六步:通过编程的方式处理SqlCacheDependency类

在教程 《Caching Data in the Architecture 》我们看到了使用单独的缓存层 Caching Layer的好处。在那篇教程,我们创建了一个ProductsCL 类来处理data cache.要 在缓存层Caching Layer利用SQL cache dependencies的话,要用到 SqlCacheDependency 类。

在检测系统(polling system)里,一个 SqlCacheDependency对象必须与某个具体的数据库和表挂钩。下面的代码,创建 了一个SqlCacheDependency对象,它基于Northwind数据库的Products表:

Caching.SqlCacheDependency productsTableDependency =
  new Caching.SqlCacheDependency("NorthwindDB", "Products");

上面的2个参数分别对应数据库名和表名 。与ObjectDataSource控件的属性SqlCacheDependency类似,数据库名是使用的 Web.config.文件里<add> 元素的name属性指定的值,而表名是实际的数据 库表名.。

要将一个SqlCacheDependency与添加到内存的条目联系起来, 可以使用一个重载的接受dependency的Insert方法。下面代码里的 SqlCacheDependency基于表Products,且缓存时间未定。换句话说,数据会一直 保存在内存,除非内存不足或表Products发生了改变才被清除掉。

Caching.SqlCacheDependency productsTableDependency =
  new Caching.SqlCacheDependency("NorthwindDB", "Products");
Cache.Insert(key,
       value,
       productsTableDependency,
       System.Web.Caching.Cache.NoAbsoluteExpiration,
       System.Web.Caching.Cache.NoSlidingExpiration);

目前,缓存 层Caching Layer的ProductsCL类从表Products获取数据,缓存时间为60秒。 让 我们对其进行更新,使其使用SQL cache dependencies. 类ProductsCL的 AddCacheItem方法是用来向内存添加数据的,其当前代码如下:

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);
}

让我 们对其进行更新,用一个SqlCacheDependency对象来替换掉MasterCacheKeyArray cache dependency:

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 ("NorthwindDB", "Products");

  // Add the item to the data cache using productsTableDependency
   DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency,
     Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration);
}

我们来进行测试 。在名为ProductsDeclarative的GridView控件下再添加一个GridView,设置其ID 为ProductsProgrammatic,在其智能标里将其绑定到一个名为 ProductsDataSourceProgrammatic的新的ObjectDataSource,设置该 ObjectDataSource使用ProductsCL类,分别在SELECT 和 UPDATE标签里选 GetProducts 和 UpdateProduct方法。

图11:设置新ObjectDataSource使用ProductsCL类

图12:在SELECT标签里选GetProducts方法

图13:在UPDATE标签选UpdateProduct方法

完成设置后,Visual Studio会自动地为GridView控件添加BoundFields和 CheckBoxFields。就像上面 那个GridView控件一样,将ProductName, CategoryName, 和 UnitPrice以外的列 都删除掉。在其智能标签里,启用分页、排序、编辑功能。同时,为使GridView 控件的编辑功能正常工作,将OldValuesParameterFormatString属性改成默认值 {0}. 或干脆在代码声明里将该属性删除。

完成上述修改后,最终的 GridView 和 ObjectDataSource的声明代码看起来应该和下面的差不多:

<asp:GridView ID="ProductsProgrammatic" runat="server"
  AutoGenerateColumns="False" DataKeyNames="ProductID"
   DataSourceID="ProductsDataSourceProgrammatic" AllowPaging="True"
   AllowSorting="True">
  <Columns>
     <asp:CommandField ShowEditButton="True" />
     <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
       <EditItemTemplate>
        <asp:TextBox ID="ProductName" runat="server"
           Text='<%# Bind("ProductName") %>' />
        <asp:RequiredFieldValidator ID="RequiredFieldValidator1" 
           ControlToValidate="ProductName" Display="Dynamic"
           ErrorMessage="You must provide a name for the product."
          SetFocusOnError="True"
            runat="server">*</asp:RequiredFieldValidator>
      </EditItemTemplate>
       <ItemTemplate>
        <asp:Label ID="Label2" runat="server"
           Text='<%# Bind("ProductName") %>' />
      </ItemTemplate>
     </asp:TemplateField>
    <asp:BoundField DataField="CategoryName" HeaderText="Category"
      ReadOnly="True" SortExpression="CategoryName" />
     <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
       <EditItemTemplate>
        $<asp:TextBox ID="UnitPrice" runat="server" Columns="8"
          Text='<%# Bind ("UnitPrice", "{0:N2}") % >'></asp:TextBox>
         <asp:CompareValidator ID="CompareValidator1" runat="server"
           ControlToValidate="UnitPrice" Display="Dynamic"
          ErrorMessage="You must enter a valid currency value with
            no currency symbols. Also, the value must be greater than
            or equal to zero."
           Operator="GreaterThanEqual" SetFocusOnError="True"
           Type="Currency" ValueToCompare="0">*</asp:CompareValidator>
       </EditItemTemplate>
      <ItemStyle HorizontalAlign="Right" />
       <ItemTemplate>
        <asp:Label ID="Label1" runat="server"
           Text='<%# Bind("UnitPrice", "{0:c}") % >' />
      </ItemTemplate>
     </asp:TemplateField>
  </Columns>
</asp:GridView>

<asp:ObjectDataSource ID="ProductsDataSourceProgrammatic" runat="server"
   OldValuesParameterFormatString="{0}" SelectMethod="GetProducts"
   TypeName="ProductsCL" UpdateMethod="UpdateProduct">
   <UpdateParameters>
    <asp:Parameter Name="productName" Type="String" />
     <asp:Parameter Name="unitPrice" Type="Decimal" />
    <asp:Parameter Name="productID" Type="Int32" />
  </UpdateParameters>
</asp:ObjectDataSource>

要测试位于缓存层的SQL cache dependency,先在ProductCL类的AddCacheItem方法里设置断点 (breakpoint),然后启动调试。当你首次登录SqlCacheDependencies.aspx页面时, 应该可以发生断点,因为是第一次请求数据,且把数据添加到内存。然后,在 GridView里跳转到下一页或对某个列排序,这将导致GridView控件查询所需的数 据,数据应该还驻存在内存因为表Products没有改动过。如果一直无法在内存找 到所需的数据,务必确保内存够大,然后再试一次。

在GridView里多跳转 几页,再另外打开一个浏览器窗口,导航到Basics.aspx页面 (~/EditInsertDelete/Basics.aspx). 更新一条记录。再回到第一个浏览器窗口 ,再跳转页面或实施排序。

此时,你会遇到下面2种情况之一:要么程序 发生断点,提示你数据被清除了,原图是数据库发生了改动;要么程序没有发生 断点,这意味着页面SqlCacheDependencies.aspx显示的是“过时”的 数据。如果没有发生断点,很可能是当数据改变时没有触发polling服务(polling service).我们知道,polling服务每隔设定的pollTime那么多毫秒对Products表 进行检查,看是否改动过。因此在源数据的更新和“过时”数据的清 除之间有个延迟期。

注意:延迟很可能是当我们在 SqlCacheDependencies.aspx页面里的GridView里编辑产品信息时发生的。在教程 《Caching Data in the Architecture》里,

我们添加 MasterCacheKeyArray cache dependency来确保数据从内存清除。但在前面我们 修改AddCacheItem方法时将其替换掉了,因此ProductsCL类将继续显示“过 时”的数据,直到检测系统发现Products发生过改动。我们将在第七步看如 何重新引入MasterCacheKeyArray cache dependency.

第七步:对缓存条 目附加多个Dependencies

我们知道, MasterCacheKeyArray cache dependency的用处在于:与 product相关的所有条目中,只要其中任意一条的相 关数据发生更改后,所有的条目都会被清除掉。举个例, GetProductsByCategoryID(categoryID)方法根据指定的categoryID获取多条产品 记录并缓存,只要其中任何一条记录被清除掉的话, MasterCacheKeyArray cache dependency 会确保剩下的其它记录也会被清除掉。

没有 MasterCacheKeyArray cache dependency的话,就会存在这种可能性,当某个条 目更改过后,剩余的条目仍然驻留在内存而显得“过时”。因此,在 使用SQL cache dependencies的时候包含MasterCacheKeyArray cache dependency是很重要的。然而,data cache的Insert 方法只允许存在一个 dependency 对象。

此外,当使用SQL cache dependencies的时候,我们 可能要依赖多个表。比如,ProductsCL类的ProductsDataTable里还包含了每个产 品的种类(category)和供应商名称(supplier names).但是在AddCacheItem方法里 ,只依赖 表Products.设想,如果用户更新了种类或供应商,那么缓存的product 数据仍然驻留在内存,显然已经"过时"了。因此,我们想时缓存的 product数据不仅依赖Products表,还要依赖Categories 和 Suppliers 表.

不过类AggregateCacheDependency提供了这个途径,将一个缓存条目 与多个dependencies联系起来。首先,创建一个AggregateCacheDependency实例 ;然后用AggregateCacheDependency的 Add 方法添加设置好的dependencies.当 AggregateCacheDependency 实例里的任何一个dependencies发生改动以后,缓存 条目就会被清除掉。

下面的代码是更新过的ProductsCL类的 AddCacheItem 方法。该方法不仅创建了MasterCacheKeyArray cache dependency ,还创建了基于表Products, Categories,和 Suppliers的多个 SqlCacheDependency objects对象。再把它们组合起来构成一个名为 aggregateDependencies的AggregateCacheDependency object 对象,将该对象传 递给Insert方法.

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("NorthwindDB", "Products");
  Caching.SqlCacheDependency categoriesTableDependency =
    new Caching.SqlCacheDependency("NorthwindDB", "Categories");
  Caching.SqlCacheDependency suppliersTableDependency =
    new Caching.SqlCacheDependency ("NorthwindDB", "Suppliers");

  // 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);
}

对代码进行测试 。现在,更改表Products、 Categories, 或Suppliers的话将清除掉缓存数据。 另外,当在GridView控件里编辑某个产品的话将调用ProductsCL 类的 UpdateProduct方法,该方法清除掉 MasterCacheKeyArray cache dependency, 进而导致连锁反应清除掉缓存的ProductsDataTable.最后的结果是,当下次请求 数据时将重新从数据库检索数据。

注意:也可以通过output caching来使 用SQL cache dependencies.欲见详情,请参考《Using ASP.NET Output Caching with SQL Server.》(http://msdn2.microsoft.com/en-us/library/e3w8402y (VS.80).aspx)

结语:

当缓存数据库数据时,最理想的状态是数据 一直驻留在内存,直到数据库发生了改动。在ASP.NET 2.0,可以通过编程或声明 代码的方式使用SQL cache dependencies ,该方法面临的挑战是及时检测数据发 生的改动。Microsoft SQL Server 2005 的完整版提供了notification功能,该 功能向应用程序通告某个数据查询返回的结果已经改变了。而对SQL Server 2005 的Express版,以及更旧的版本而言,只有使用polling检测系统了。不过还好, 为polling设置必要的构造是很简单的。

祝编程快乐!

作者简介

Scott Mitchell,著有六本ASP/ASP.NET方面的书,是 4GuysFromRolla.com的创始人,自1998年以来一直应用 微软Web技术。Scott是个 独立的技术咨询顾问,培训师,作家,最近完成了将由Sams出版社出版的新作, 24小时内精通ASP.NET 2.0。他的联系电邮为mitchell@4guysfromrolla.com,也 可以通过他的博客http://ScottOnWriting.NET与他联系。

posted @ 2011-12-19 11:44  ^_^肥仔John  阅读(286)  评论(0编辑  收藏  举报