使用 SQL 缓存依赖项 (C#)

作者 :Scott Mitchell

下载 PDF

最简单的缓存策略是允许缓存的数据在指定时间段后过期。 但是,这种简单的方法意味着缓存的数据与其基础数据源没有关联,从而导致数据保存时间过长或当前数据过期过快。 更好的方法是使用 SqlCacheDependency 类,使数据保持缓存状态,直到在 SQL 数据库中修改其基础数据为止。 本教程将告诉你方法。

简介

使用 ObjectDataSource 缓存数据 以及 体系结构教程中的缓存数据中 介绍的缓存技术使用基于时间的过期时间,在指定时间段后从缓存中逐出数据。 此方法是平衡缓存性能提升与数据过期性的最简单方法。 通过选择 x 秒的过期时间,页面开发人员承认能够享受缓存仅 x 秒的性能优势,但可以放心地发现,数据过期时间永远不会超过最大 x 秒。 当然,对于静态数据, x 可以延长到 Web 应用程序的生存期,如 应用程序启动时缓存数据 教程中所述。

缓存数据库数据时,通常会选择基于时间的过期时间,以方便使用,但通常是一种不适当的解决方案。 理想情况下,在数据库中修改基础数据之前,数据库数据将保持缓存状态;只有这样,缓存才会被逐出。 此方法可最大程度地提高缓存的性能优势,并最大程度地缩短过时数据的持续时间。 但是,为了享受这些优势,必须有一些系统知道何时修改了基础数据库数据,并从缓存中逐出相应的项。 在 ASP.NET 2.0 之前,页面开发人员负责实现此系统。

ASP.NET 2.0 提供了一个 SqlCacheDependency 和必要的基础结构来确定数据库中何时发生了更改,以便可以逐出相应的缓存项。 有两种技术可用于确定基础数据何时发生更改:通知和轮询。 在讨论通知和轮询之间的差异后,我们将创建支持轮询所需的基础结构,然后探索如何在声明性方案中和以编程方式使用 SqlCacheDependency 类。

了解通知和轮询

有两种技术可用于确定何时修改数据库中的数据:通知和轮询。 通过通知,数据库会在自上次执行查询以来更改特定查询的结果时自动向 ASP.NET 运行时发出警报,此时会逐出与查询关联的缓存项。 通过轮询,数据库服务器维护有关特定表上次更新时间的信息。 ASP.NET 运行时定期轮询数据库,以检查哪些表在进入缓存后发生了更改。 数据已修改的表会逐出其关联的缓存项。

通知选项所需的设置比轮询少,而且更精细,因为它在查询级别而不是表级别跟踪更改。 遗憾的是,通知仅在 Microsoft SQL Server 2005 (完整版本中可用,即非 Express 版本) 。 但是,轮询选项可用于从 7.0 到 2005 年的所有 Microsoft SQL Server版本。 由于这些教程使用 SQL Server 2005 的 Express 版本,因此我们将重点介绍如何设置和使用轮询选项。 有关 SQL Server 2005 通知功能的进一步资源,请参阅本教程末尾的“进一步阅读”部分。

使用轮询时,必须将数据库配置为包含名为 AspNet_SqlCacheTablesForChangeNotification 的表,该表具有三列 - tableNamenotificationCreatedchangeId。 此表包含每个表的一行,其中包含可能需要在 Web 应用程序中的 SQL 缓存依赖项中使用的数据。 列 tableName 指定表的名称,同时 notificationCreated 指示将行添加到表中的日期和时间。 列 changeId 的类型 int 为 ,初始值为 0。 其值随对表的每次修改而递增。

除了表, AspNet_SqlCacheTablesForChangeNotification 数据库还需要在 SQL 缓存依赖项中可能出现的每个表上包括触发器。 每当插入、更新或删除行并递增 中的AspNet_SqlCacheTablesForChangeNotification表值changeId时,都会执行这些触发器。

ASP.NET 运行时在使用 对象缓存数据时跟踪表的SqlCacheDependency当前changeId数据。 定期检查数据库,并且会逐出与数据库中的值不同的任何 SqlCacheDependency 对象 changeId ,因为不同的 changeId 值表示自缓存数据以来表发生了更改。

步骤 1:浏览aspnet_regsql.exe命令行程序

使用轮询方法时,必须将数据库设置为包含上述基础结构:预定义表 (AspNet_SqlCacheTablesForChangeNotification) 、少量存储过程,以及可在 Web 应用程序中的 SQL 缓存依赖项中使用的每个表上的触发器。 可以通过命令行程序创建这些表、存储过程和触发器,该程序 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_ddladmin 角色中db_securityadmin

例如,若要使用 Windows 身份验证将用于轮询的基础结构添加到名为 pubs 的数据库服务器上名为 的 ScottsServer Microsoft SQL Server 数据库,请导航到相应的目录,并在命令行中输入:

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

添加数据库级基础结构后,我们需要将触发器添加到将在 SQL 缓存依赖项中使用的那些表。 aspnet_regsql.exe再次使用命令行程序,但使用 -t 开关指定表名称,而不是使用 -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

若要将触发器添加到 authors 上的 ScottsServer数据库上的 pubstitles 表,请使用:

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

对于本教程,请将触发器添加到 ProductsCategoriesSuppliers 表中。 我们将在步骤 3 中查看特定的命令行语法。

步骤 2:在 中引用 Microsoft SQL Server 2005 Express Edition 数据库App_Data

命令行 aspnet_regsql.exe 程序需要数据库和服务器名称才能添加必要的轮询基础结构。 但是,驻留App_Data在 文件夹中的 Microsoft SQL Server 2005 Express 数据库的数据库和服务器名称是什么? 我发现,最简单的方法是将数据库附加到localhost\SQLExpress数据库实例并使用SQL Server Management Studio重命名数据,而不必发现数据库和服务器的名称。 如果你的计算机上安装了 SQL Server 2005 的完整版本之一,则可能已在计算机上安装了SQL Server Management Studio。 如果只有 Express 版本,则可以下载免费的 Microsoft SQL Server Management Studio Express Edition

首先关闭 Visual Studio。 接下来,打开SQL Server Management Studio并选择使用 Windows 身份验证连接到localhost\SQLExpress服务器。

附加到 localhost\SQLExpress Server

图 1:附加到 localhost\SQLExpress 服务器

连接到服务器后,Management Studio 将显示服务器,并具有数据库、安全性等的子文件夹。 右键单击“数据库”文件夹,然后选择“附加”选项。 这将打开“附加数据库”对话框, (请参阅图 2) 。 单击“添加”按钮并选择 NORTHWND.MDF Web 应用程序文件夹中的数据库 App_Data 文件夹。

附加 NORTHWND。App_Data 文件夹中的 MDF 数据库

图 2:从App_Data文件夹附加NORTHWND.MDF数据库 (单击以查看全尺寸图像)

这会将数据库添加到“数据库”文件夹。 数据库名称可以是数据库文件的完整路径,也可以是 GUID 前面附加的完整路径。 若要避免在使用 aspnet_regsql.exe 命令行工具时必须键入此冗长的数据库名称,请右键单击刚附加的数据库并选择“重命名”,将数据库重命名为更方便用户的名称。 我已将数据库重命名为 DataTutorials 。

将附加数据库重命名为更 Human-Friendly 名称

图 3:将附加数据库重命名为更 Human-Friendly 的名称

步骤 3:将轮询基础结构添加到 Northwind 数据库

现在,我们已从 App_Data 文件夹附加数据库NORTHWND.MDF,接下来可以添加轮询基础结构。 假设已将数据库重命名为 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

运行这四个命令后,右键单击 Management Studio 中的数据库名称,转到“任务”子菜单,然后选择“分离”。 然后关闭 Management Studio 并重新打开 Visual Studio。

重新打开 Visual Studio 后,通过服务器资源管理器钻取到数据库。 请注意新表 (AspNet_SqlCacheTablesForChangeNotification) 、新的存储过程以及 、 CategoriesSuppliers 表上的Products触发器。

数据库现在包括必要的轮询基础结构

图 4:数据库现在包括必要的轮询基础结构

步骤 4:配置轮询服务

在数据库中创建所需的表、触发器和存储过程后,最后一步是配置轮询服务,方法是 Web.config 指定要使用的数据库和轮询频率(以毫秒为单位)。 以下标记每秒轮询一次 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>

name NorthwindDB ( 元素中的<add>值 ) 将人类可读的名称与特定数据库相关联。 使用 SQL 缓存依赖项时,需要引用此处定义的数据库名称以及缓存数据所基于的表。 我们将在步骤 6 中了解如何使用 SqlCacheDependency 类以编程方式将 SQL 缓存依赖项与缓存数据相关联。

建立 SQL 缓存依赖项后,轮询系统将每pollTime毫秒连接到元素中<databases>定义的数据库,AspNet_SqlCachePollingStoredProcedure并执行存储过程。 此存储过程(使用aspnet_regsql.exe命令行工具添加回步骤 3)返回 tableName 中每条记录的 AspNet_SqlCacheTablesForChangeNotificationchangeId 值。 过时的 SQL 缓存依赖项将从缓存中逐出。

设置 pollTime 在性能和数据过时之间引入权衡。 较小的 pollTime 值会增加对数据库的请求数,但更快地从缓存中逐出过时的数据。 较大的 pollTime 值可以减少数据库请求数,但会增加后端数据更改和逐出相关缓存项之间的延迟。 幸运的是,数据库请求正在执行一个简单的存储过程,该存储过程仅从简单的轻型表中返回几行。 但是,请试验不同的 pollTime 值,以便在应用程序的数据库访问和数据过期之间找到理想的平衡点。 允许的最小值 pollTime 为 500。

注意

上面的示例在 元素中提供了单个pollTime值,但可以选择在 元素中<add>指定pollTime值。<sqlCacheDependency> 如果指定了多个数据库,并且想要自定义每个数据库的轮询频率,这非常有用。

步骤 5:以声明方式使用 SQL 缓存依赖项

在步骤 1 到步骤 4 中,我们了解了如何设置必要的数据库基础结构和配置轮询系统。 建立此基础结构后,我们现在可以使用编程或声明性技术将项添加到具有关联 SQL 缓存依赖项的数据缓存。 在此步骤中,我们将了解如何以声明方式使用 SQL 缓存依赖项。 在步骤 6 中,我们将介绍编程方法。

使用 ObjectDataSource 缓存数据教程探讨了 ObjectDataSource 的声明性缓存功能。 通过将 属性设置为 EnableCachingtrue ,并将 CacheDuration 属性设置为某个时间间隔,ObjectDataSource 将自动缓存从其基础对象返回的指定时间间隔的数据。 ObjectDataSource 还可以使用一个或多个 SQL 缓存依赖项。

若要以声明方式演示如何使用 SQL 缓存依赖项,请打开 文件夹中的页面SqlCacheDependencies.aspxCaching,并将 GridView 从“工具箱”拖动到Designer。 将 GridView ID 设置为 ProductsDeclarative ,并从其智能标记中选择将其绑定到名为 ProductsDataSourceDeclarative的新 ObjectDataSource。

创建名为 ProductsDataSourceDeclarative 的新 ObjectDataSource

图 5:创建名为 ProductsDataSourceDeclarative 的新对象DataSource (单击以查看全尺寸图像)

将 ObjectDataSource 配置为使用 类, ProductsBLL 并将 SELECT 选项卡中的下拉列表设置为 GetProducts()。 在“更新”选项卡中,选择具有三个 UpdateProduct 输入参数的重载 - productNameunitPriceproductID。 在“插入”和“删除”选项卡中,将下拉列表设置为 (“无”) 。

将 UpdateProduct 重载与三个输入参数一起使用

图 6:使用具有三个输入参数的 UpdateProduct 重载 (单击以查看全尺寸图像)

将“Drop-Down 列表”设置为“INSERT”和“DELETE”选项卡 (“无”)

图 7:将“插入”和“删除”选项卡的“Drop-Down (列表”设置为“无”) (单击以查看全尺寸图像)

完成“配置数据源”向导后,Visual Studio 将在 GridView 中为每个数据字段创建 BoundFields 和 CheckBoxFields。 删除除 、CategoryName、 和 UnitPrice之外ProductName的所有字段,并根据需要设置这些字段的格式。 在 GridView 的智能标记中,检查“启用分页”、“启用排序”和“启用编辑”复选框。 Visual Studio 会将 ObjectDataSource 的 OldValuesParameterFormatString 属性设置为 original_{0}。 为了使 GridView 的编辑功能正常工作,请从声明性语法中完全删除此属性,或将其设置回默认值 {0}

最后,在 GridView 上方添加标签 Web 控件,并将其 ID 属性设置为 ODSEvents ,将其 EnableViewState 属性设置为 false。 进行这些更改后,页面声明性标记应如下所示。 请注意,我已对 GridView 字段进行了许多美观自定义,这些自定义不是演示 SQL 缓存依赖项功能所必需的。

<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 事件 Selecting 仅在从其基础对象检索数据时触发。 如果 ObjectDataSource 从其自己的缓存访问数据,则不会触发此事件。

现在,请通过浏览器访问此页面。 由于我们尚未实现任何缓存,因此每次对网格进行分页、排序或编辑时,页面都应显示文本“选择触发的事件,如图 8 所示。

每次对 GridView 进行分页、编辑或排序时,ObjectDataSource s 选择事件都会触发

图 8:每次对 GridView 进行分页、编辑或排序时,ObjectDataSource 事件 Selecting 都会触发 (单击以查看全尺寸图像)

正如我们在 使用 ObjectDataSource 缓存数据 教程中看到的,将 属性设置为 EnableCachingtrue 会导致 ObjectDataSource 在其 属性指定的持续时间内缓存其 CacheDuration 数据。 ObjectDataSource 还具有 属性SqlCacheDependency,该属性使用 模式将一个或多个 SQL 缓存依赖项添加到缓存的数据:

databaseName1:tableName1;databaseName2:tableName2;...

其中 databaseName 是 中 Web.config元素的 <add> 属性中指定的name数据库名称,tableName 是数据库表的名称。 例如,若要创建基于 SQL 缓存依赖项对 Northwind 表 Products 无限期缓存数据的 ObjectDataSource,请将 ObjectDataSource 属性 EnableCaching 设置为 true ,将其 SqlCacheDependency 属性设置为 NorthwindDB:Products 。

注意

可以使用 SQL 缓存依赖项基于时间的过期时间,方法是将 设置为 trueCacheDuration将 设置为 EnableCaching 时间间隔,并将 SqlCacheDependency 设置为 数据库和表名 () 。 当达到基于时间的到期时间或轮询系统注意到基础数据库数据已更改时,ObjectDataSource 将逐出其数据,以先发生者为准。

中的 SqlCacheDependencies.aspx GridView 显示两个表中的数据,CategoriesProducts (通过) 上的 Categories 检索JOIN产品 CategoryName 字段。 因此,我们想要指定两个 SQL 缓存依赖项:NorthwindDB:Products;NorthwindDB:Categories 。

配置 ObjectDataSource 以支持在产品和类别上使用 SQL 缓存依赖项进行缓存

图 9:配置 ObjectDataSource 以支持在 上 Products 使用 SQL 缓存依赖项进行缓存, Categories (单击以查看全尺寸图像)

将 ObjectDataSource 配置为支持缓存后,请通过浏览器重新访问页面。 同样,文本“选择触发的事件应显示在第一页访问中,但在分页、排序或单击”编辑“或”取消“按钮时应消失。 这是因为将数据加载到 ObjectDataSource 缓存中后,数据会一直保留到 Products 修改 或 Categories 表或通过 GridView 更新数据。

在网格中分页并注意到缺少“选择触发的事件”文本后,打开一个新的浏览器窗口,导航到“编辑”、“插入”和“删除”部分中的“基本信息”教程, (~/EditInsertDelete/Basics.aspx) 。 更新产品的名称或价格。 然后,从 到第一个浏览器窗口,查看不同的数据页,对网格进行排序,或单击一行的“编辑”按钮。 这一次,触发的“选择”事件应再次出现,因为基础数据库数据已修改 (见图 10) 。 如果文本未显示,请稍等片刻,然后重试。 请记住,轮询服务每毫秒检查一次pollTime表的更改Products,因此在更新基础数据和逐出缓存数据之间会有延迟。

修改 Products 表会逐出缓存的产品数据

图 10:修改“产品”表会逐出缓存的产品数据 (单击以查看全尺寸图像)

步骤 6:以编程方式使用SqlCacheDependency

体系结构中的缓存数据教程介绍了在体系结构中使用单独的缓存层的好处,而不是将缓存与 ObjectDataSource 紧密耦合在一起。 在该教程中,我们创建了一个 ProductsCL 类来演示以编程方式处理数据缓存。 若要利用缓存层中的 SQL 缓存依赖项,请使用 SqlCacheDependency 类。

对于轮询系统, SqlCacheDependency 对象必须与特定的数据库和表对相关联。 例如,以下代码基于 Northwind 数据库 表Products创建 SqlCacheDependency 对象:

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

构造函数的 SqlCacheDependency 两个输入参数分别是数据库和表名称。 与 ObjectDataSource 属性SqlCacheDependency一样,使用的数据库名称与 中 Web.config元素的 <add> 属性中指定的name值相同。 表名是数据库表的实际名称。

若要将 与添加到数据缓存的项相关联 SqlCacheDependency ,请使用接受依赖项的方法重载之 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);

缓存层 类 ProductsCL 当前使用基于时间的 60 秒过期时间缓存 Products 表中的数据。 让我们更新此类,使其改用 SQL 缓存依赖项。 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 缓存依赖项:

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

若要测试此功能,请将 GridView 添加到现有 ProductsDeclarative GridView 下方的页面。 将此新 GridView ID 设置为 ProductsProgrammatic ,并通过其智能标记将其绑定到名为 ProductsDataSourceProgrammatic的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 ProductsCL 类,并将 SELECT 和 UPDATE 选项卡中的下拉列表分别设置为 GetProductsUpdateProduct

将 ObjectDataSource 配置为使用 ProductsCL 类

图 11:将 ObjectDataSource 配置为使用 ProductsCL 类 (单击以查看全尺寸图像)

从 SELECT 选项卡 Drop-Down 列表中选择 GetProducts 方法

图 12GetProducts 从 SELECT 选项卡 Drop-Down 列表中选择方法 (单击以查看全尺寸图像)

从“更新”选项卡 Drop-Down 列表中选择 UpdateProduct 方法

图 13:从“UPDATE”选项卡的“Drop-Down 列表”中选择“UpdateProduct 方法” (单击以查看全尺寸图像)

完成“配置数据源”向导后,Visual Studio 将在 GridView 中为每个数据字段创建 BoundFields 和 CheckBoxFields。 与添加到此页面的第一个 GridView 一样,删除除 、 CategoryNameUnitPrice之外ProductName的所有字段,并根据需要设置这些字段的格式。 在 GridView 的智能标记中,检查“启用分页”、“启用排序”和“启用编辑”复选框。 与 ProductsDataSourceDeclarative ObjectDataSource 一样,Visual Studio 会将 ObjectDataSource 属性OldValuesParameterFormatString设置为 ProductsDataSourceProgrammaticoriginal_{0}。 为了使 GridView 的编辑功能正常工作,请将此属性设置回 {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 缓存依赖项,请在 类中AddCacheItem设置一个断点 ProductCL 方法,然后开始调试。 首次访问 SqlCacheDependencies.aspx时,应命中断点,因为首次请求数据并将其放入缓存中。 接下来,移动到 GridView 中的另一个页面或对其中一列进行排序。 这会导致 GridView 重新查询其数据,但数据应在缓存中找到, Products 因为数据库表尚未修改。 如果在缓存中重复找不到数据,请确保计算机上有足够的可用内存,然后重试。

分页浏览 GridView 的几个页面后,打开第二个浏览器窗口并导航到“编辑、插入和删除”部分中的“基本信息”教程, (~/EditInsertDelete/Basics.aspx) 。 更新 Products 表中的记录,然后从第一个浏览器窗口查看新页面或单击其中一个排序标题。

在此方案中,你将看到以下两种情况之一:命中断点,指示缓存数据因数据库中的更改而被逐出;或者,不会命中断点,这意味着 SqlCacheDependencies.aspx 现在显示过时的数据。 如果未命中断点,则可能是因为自数据更改后尚未触发轮询服务。 请记住,轮询服务每毫秒检查一次pollTime表的更改Products,因此在更新基础数据和逐出缓存数据之间会有延迟。

注意

通过 中的 SqlCacheDependencies.aspxGridView 编辑其中一个产品时,更可能出现此延迟。 在 体系结构中的缓存数据教程中 ,我们添加了 MasterCacheKeyArray 缓存依赖项,以确保通过 ProductsCL 类 s UpdateProduct 方法编辑的数据已从缓存中逐出。 但是,在此步骤前面修改 AddCacheItem 方法时,我们替换了此缓存依赖项,因此类 ProductsCL 将继续显示缓存的数据,直到轮询系统记录表的 Products 更改。 我们将在步骤 7 中了解如何重新引入 MasterCacheKeyArray 缓存依赖项。

步骤 7:将多个依赖项与缓存项相关联

回想一下, MasterCacheKeyArray 缓存依赖项用于确保在更新与产品相关的任何单个项时从缓存中逐出 所有 与产品相关的数据。 例如, GetProductsByCategoryID(categoryID) 方法缓存 ProductsDataTables 每个唯一 categoryID 值的实例。 如果逐出其中一个对象,缓存 MasterCacheKeyArray 依赖项可确保也删除其他对象。 如果没有此缓存依赖项,则修改缓存数据时,可能存在其他缓存产品数据已过期的可能性。 因此,在使用 SQL 缓存依赖项时, MasterCacheKeyArray 保持缓存依赖项非常重要。 但是,数据缓存 方法 Insert 仅允许单个依赖项对象。

此外,在使用 SQL 缓存依赖项时,我们可能需要将多个数据库表关联为依赖项。 例如, ProductsDataTable 类中 ProductsCL 缓存的 包含每个产品的类别和供应商名称,但 AddCacheItem 方法仅使用对 的 Products依赖项。 在这种情况下,如果用户更新类别或供应商的名称,缓存的产品数据将保留在缓存中并过期。 因此,我们希望缓存的产品数据不仅 Products 依赖于 表,还 Categories 依赖于 和 Suppliers 表。

AggregateCacheDependency提供了一种将多个依赖项与缓存项相关联的方法。 首先创建 实例 AggregateCacheDependency 。 接下来,使用 AggregateCacheDependency s Add 方法添加依赖项集。 之后将项插入数据缓存时,传入 AggregateCacheDependency 实例。 当任何AggregateCacheDependency实例依赖项发生更改时,将逐出缓存的项。

下面显示了 类方法的ProductsCLAddCacheItem更新代码。 方法创建MasterCacheKeyArray缓存依赖项以及 SqlCacheDependencyCategoriesSuppliers 表的对象Products。 这些都合并到一个名为 AggregateCacheDependencyaggregateDependencies的对象中,然后该对象将 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对 、 CategoriesSuppliers 表的更改会导致逐出缓存的数据。 此外, ProductsCL 类 s UpdateProduct 方法(在通过 GridView 编辑产品时调用)会逐出 MasterCacheKeyArray 缓存依赖项,这会导致缓存 ProductsDataTable 被逐出,并在下一个请求中重新检索数据。

注意

SQL 缓存依赖项还可以与 输出缓存一起使用。 有关此功能的演示,请参阅:将 ASP.NET 输出缓存与 SQL Server 配合使用

总结

缓存数据库数据时,理想情况下,数据将保留在缓存中,直到在数据库中修改。 使用 ASP.NET 2.0,可以在声明性和编程方案中创建和使用 SQL 缓存依赖项。 此方法的挑战之一是发现何时修改了数据。 Microsoft SQL Server 2005 的完整版本提供了可在查询结果发生更改时向应用程序发出警报的通知功能。 对于 express Edition of SQL Server 2005 和早期版本的 SQL Server,必须改用轮询系统。 幸运的是,设置必要的轮询基础结构相当简单。

编程愉快!

深入阅读

有关本教程中讨论的主题的详细信息,请参阅以下资源:

关于作者

Scott Mitchell 是七本 ASP/ASP.NET 书籍的作者, 4GuysFromRolla.com 的创始人,自 1998 年以来一直从事 Microsoft Web 技术工作。 Scott 担任独立顾问、培训师和作家。 他的最新书是 山姆斯在24小时内 ASP.NET 2.0自学。 可以在 上联系 mitchell@4GuysFromRolla.com他, 也可以通过他的博客联系到他,该博客可在 http://ScottOnWriting.NET中找到。

特别感谢

本教程系列由许多有用的审阅者查看。 本教程的主要审阅者是 Marko Rangel、Teresa Murphy 和 Hilton Giesenow。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处放置一行 mitchell@4GuysFromRolla.com。