使用 SQL 缓存依赖项 (VB)

作者 :斯科特·米切尔

下载 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缓存依赖项中的每个表上包含触发器。 每当插入、更新或删除行并递增表 changeIdAspNet_SqlCacheTablesForChangeNotification时,就会执行这些触发器。

ASP.NET 运行时在使用SqlCacheDependency对象缓存数据时,跟踪表的当前changeId。 数据库会定期检查,并且将驱逐任何与数据库中值不同的 SqlCacheDependency 对象,因为不同的 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_securityadmindb_ddladmin角色中。

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

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

添加数据库级基础结构后,我们需要将触发器添加到将在 SQL 缓存依赖项中使用的表。 再次使用 aspnet_regsql.exe 命令行程序,指定表名称时请使用 -t 开关,并改用 -et 而不是 -ed 开关,如下所示:

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

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

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表。 我们将了解步骤 3 中的特定命令行语法。

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

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

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

附加到 localhost\SQLExpress Server

图 1:连接到 localhost\SQLExpress 服务器

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

从 App_Data 文件夹附加 NORTHWND.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)、新的存储过程以及表上的ProductsCategoriesSuppliers触发器。

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

图 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 缓存依赖项时,需要引用此处定义的数据库名称以及缓存数据所基于的表。 我们将了解如何使用 SqlCacheDependency 类以编程方式将 SQL 缓存依赖项与步骤 6 中的缓存数据相关联。

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

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

注释

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

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

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

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

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

创建一个新 ObjectDataSource,并将其命名为 ProductsDataSourceDeclarative

图 5:创建名为 ProductsDataSourceDeclarative 的新 ObjectDataSource (单击可查看全尺寸图像

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

使用有三个输入参数的 UpdateProduct 重载函数

图 6:将 UpdateProduct 重载与三个输入参数配合使用(单击可查看全尺寸图像

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

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

完成“配置数据源”向导后,Visual Studio 将在 GridView 中为每个数据字段创建 BoundFields 和 CheckBoxFields。 删除所有字段,只保留ProductNameCategoryNameUnitPrice,并根据需要设置这些字段的格式。 在 GridView 智能标记中,选中“启用分页”、“启用排序”和“启用编辑”复选框。 Visual Studio 会将 ObjectDataSource s 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 Sub ProductsDataSourceDeclarative_Selecting _
    (sender As Object, e As ObjectDataSourceSelectingEventArgs) _
    Handles ProductsDataSourceDeclarative.Selecting
    ODSEvents.Text = "-- Selecting event fired"
End Sub

回想一下,仅当从其基础对象检索数据时,ObjectDataSource 事件 Selecting 才会触发。 如果 ObjectDataSource 从其自己的缓存访问数据,则不会触发此事件。

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

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

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

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

databaseName1:tableName1;databaseName2:tableName2;...

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

注释

可以通过设置 SQL 缓存依赖项和基于时间的到期,将EnableCaching设置为True,将CacheDuration设置为时间间隔,以及将SqlCacheDependency设置为数据库和表名,来实现。 当达到基于时间的过期时间或轮询系统检测到基础数据库数据已更改时,ObjectDataSource 将逐出其数据,以先发生的情况为准。

网格视图在SqlCacheDependencies.aspx中显示来自两个表的数据 - ProductsCategories(产品的CategoryName字段通过在Categories上执行的JOIN检索)。 因此,我们需要指定两个 SQL 缓存依赖项:NorthwindDB:Products;NorthwindDB:Categories.

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

图 9:配置 ObjectDataSource,以通过 SQL 缓存依赖项支持 ProductsCategories 上的缓存(单击以查看全尺寸图像

将 ObjectDataSource 配置为支持缓存后,请通过浏览器重新访问页面。 同样,文本“选择事件触发”的内容应该在首次访问页面时显示,但在翻页、进行排序操作,或点击“编辑”或“取消”按钮时应消失。 这是因为在将数据加载到 ObjectDataSource 的缓存后,它将一直保留在该缓存中,直到 ProductsCategories 表被修改,或者通过 GridView 更新数据。

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

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

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

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

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

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

Dim productsTableDependency As _
    New Caching.SqlCacheDependency("NorthwindDB", "Products")

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

若要将 SqlCacheDependency 与添加到数据缓存的项关联,请使用其中一个包含依赖项的方法重载 Insert。 以下代码在无限期内向数据缓存添加,但将其与表上的ProductsSqlCacheDependency相关联。 简言之, 将保留在缓存中,直到由于内存约束被逐出,或者轮询系统检测到 Products 表自缓存以来已更改。

Dim productsTableDependency As _
    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 将数据添加到缓存的类 s AddCacheItem 方法当前包含以下代码:

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Make sure MasterCacheKeyArray(0) is in the cache - if not, add it
    If DataCache(MasterCacheKeyArray(0)) Is Nothing Then
        DataCache(MasterCacheKeyArray(0)) = DateTime.Now
    End If
    ' Add a CacheDependency
    Dim dependency As _
        New Caching.CacheDependency(Nothing, MasterCacheKeyArray)
    DataCache.Insert(GetCacheKey(rawKey), value, dependency, _
        DateTime.Now.AddSeconds(CacheDuration), _
        Caching.Cache.NoSlidingExpiration)
End Sub

更新此代码以使用 SqlCacheDependency 对象而不是 MasterCacheKeyArray 缓存依赖项:

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Add the SqlCacheDependency objects for Products
    Dim productsTableDependency As New _
        Caching.SqlCacheDependency("NorthwindDB", "Products")
    DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, _
        Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub

若要测试此功能,请将 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 列表中选择方法(单击以查看全尺寸图像

从 UPDATE 选项卡 Drop-Down 列表中选择 UpdateProduct 方法

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

完成“配置数据源”向导后,Visual Studio 将在 GridView 中为每个数据字段创建 BoundFields 和 CheckBoxFields。 如同本页第一个网格视图一样,删除所有字段,保留ProductNameCategoryNameUnitPrice,并按照需要格式化这些字段。 在 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 缓存依赖项,请在ProductCL类的AddCacheItem方法中设置断点,然后开始调试。 首次访问 SqlCacheDependencies.aspx 时,请求数据并将其放入缓存后,应触发断点。 接下来,移动到 GridView 中的另一个页面或对其中一列进行排序。 这会导致 GridView 重新查询其数据,但应在缓存中找到数据,因为 Products 数据库表尚未修改。 如果缓存中反复找不到数据,请确保计算机上有足够的可用内存,然后重试。

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

在此方案中,你将看到以下两项内容之一:断点将被触发,指示由于数据库中的更改,缓存的数据被清除;否则,断点不会被触发,这意味着 SqlCacheDependencies.aspx 显示的是过时的数据。 如果断点未命中,则可能是因为轮询服务尚未触发,因为数据已更改。 请记住,轮询服务每pollTime毫秒检查一次Products表的更改,因此在更新基础数据和剔除缓存数据之间有延迟。

注释

通过 GridView SqlCacheDependencies.aspx编辑其中一个产品时,此延迟更有可能出现。 在 缓存数据架构 教程中,我们添加了 MasterCacheKeyArray 缓存依赖项,以确保通过 ProductsCL 类的 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实例的依赖项发生更改时,将逐出缓存的项。

下文显示了ProductsCL类的AddCacheItem方法的更新代码。 该方法为ProductsCategoriesSuppliers表创建MasterCacheKeyArray缓存依赖项以及SqlCacheDependency对象。 这些都组合成一aggregateDependencies个名为AggregateCacheDependency的对象,然后传递到Insert该方法中。

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Make sure MasterCacheKeyArray(0) is in the cache - if not, add it.
    If DataCache(MasterCacheKeyArray(0)) Is Nothing Then
        DataCache(MasterCacheKeyArray(0)) = DateTime.Now
    End If
    'Create the CacheDependency
    Dim masterCacheKeyDependency As _
        New Caching.CacheDependency(Nothing, MasterCacheKeyArray)
    ' Add the SqlCacheDependency objects for Products, Categories, and Suppliers
    Dim productsTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Products")
    Dim categoriesTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Categories")
    Dim suppliersTableDependency As _
        New Caching.SqlCacheDependency("NorthwindDB", "Suppliers")
    ' Create an AggregateCacheDependency
    Dim aggregateDependencies As New Caching.AggregateCacheDependency()
    aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, _
        categoriesTableDependency, suppliersTableDependency)
    DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, _
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub

测试此新代码。现在,对ProductsCategoriesSuppliers表的更改会导致缓存数据被驱逐。 此外, ProductsCL 类 s UpdateProduct 方法(在通过 GridView 编辑产品时调用)将逐出 MasterCacheKeyArray 缓存依赖项,这会导致 ProductsDataTable 缓存被逐出,并在下一个请求上重新检索数据。

注释

SQL 缓存依赖项还可用于 输出缓存。 有关此功能的演示,请参阅: 对 SQL Server 使用 ASP.NET 输出缓存

概要

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

快乐编程!

深入阅读

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

关于作者

斯科特·米切尔,七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自1998年以来一直在与Microsoft Web 技术合作。 斯科特担任独立顾问、教练和作家。 他的最新书是《Sams 教你自己学 ASP.NET 2.0 只需24小时》。 可以通过 mitchell@4GuysFromRolla.com 联系到他。

特别致谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是 Marko Rangel、Teresa Murphy 和希尔顿 Giesenow。 有兴趣查看即将发布的 MSDN 文章? 如果是这样,请给我写信。mitchell@4GuysFromRolla.com