使用 ObjectDataSource 缓存数据 (VB)
缓存可能意味着慢速 Web 应用程序与快速 Web 应用程序之间的差异。 本教程是四个教程中的第一个,详细介绍了 ASP.NET 中的缓存。 了解缓存的关键概念以及如何通过 ObjectDataSource 控件将缓存应用到表示层。
简介
在计算机科学中, 缓存 是获取成本高昂的数据或信息并将其副本存储在访问速度更快的位置的过程。 对于数据驱动的应用程序,大型和复杂的查询通常占用应用程序的大部分执行时间。 因此,通常可以通过将昂贵的数据库查询结果存储在应用程序内存中来提高此类应用程序的性能。
ASP.NET 2.0 提供多种缓存选项。 可以通过输出缓存来缓存整个网页或用户控件呈现 的标记。 ObjectDataSource 和 SqlDataSource 控件还提供缓存功能,从而允许在控件级别缓存数据。 ASP.NET 数据缓存 提供了丰富的缓存 API,使页面开发人员能够以编程方式缓存对象。 在本教程和接下来的三个教程中,我们将探讨如何使用 ObjectDataSource 的缓存功能以及数据缓存。 我们还将探讨如何在启动时缓存应用程序范围的数据,以及如何通过使用 SQL 缓存依赖项使缓存数据保持最新。 这些教程不介绍输出缓存。 有关输出缓存的详细信息,请参阅 ASP.NET 2.0 中的输出缓存。
缓存可以在体系结构中的任何位置应用,从数据访问层到表示层。 本教程介绍如何通过 ObjectDataSource 控件将缓存应用到表示层。 在下一教程中,我们将探讨在业务逻辑层缓存数据。
密钥缓存概念
缓存可以极大地提高应用程序的整体性能和可伸缩性,方法是获取生成成本高昂的数据,并将数据的副本存储在可以更高效访问的位置。 由于缓存只包含实际基础数据的副本,因此,如果基础数据发生更改,缓存可能会过时或 过时。 为了解决此问题,页面开发人员可以使用以下任一方法指示缓存项从缓存 中逐出 的条件:
- 可以在绝对或滑动持续时间内将项添加到缓存的基于时间的条件。 例如,页面开发人员可能指示持续时间为 60 秒。 在绝对持续时间内,缓存项在添加到缓存的 60 秒后将被逐出,无论其访问频率如何。 随着持续时间的滑动,缓存的项在上次访问后 60 秒被逐出。
- 基于依赖项的条件 ,依赖项在添加到缓存时可以与项相关联。 当项的依赖项发生更改时,将从缓存中逐出。 依赖项可以是文件、另一个缓存项或两者的组合。 ASP.NET 2.0 还允许 SQL 缓存依赖项,使开发人员能够将项添加到缓存,并在基础数据库数据更改时将其逐出。 我们将在即将推出的使用 SQL 缓存依赖项教程中检查 SQL 缓存依赖项 。
无论指定的逐出条件如何,缓存中的项在满足基于时间或基于依赖项的条件之前可能会被 清除 。 如果缓存已达到其容量,必须先删除现有项,然后才能添加新项。 因此,在以编程方式处理缓存数据时,请务必始终假定缓存数据可能不存在。 在下一教程“ 在体系结构中缓存数据”中,我们将介绍以编程方式从缓存访问数据时要使用的模式。
缓存为从应用程序挤压更多性能提供了一种经济的方法。 正如 史蒂文·史密斯 在他的文章 ASP.NET 缓存:技术和最佳做法中阐明的:
缓存是获取足够好性能的好方法,无需花费大量时间和分析。 内存很便宜,因此,如果可以通过将输出缓存 30 秒(而不是花费一天或一周来尝试优化代码或数据库)获得所需的性能,请执行缓存解决方案 (假设 30 秒的旧数据没问题) 继续操作。 最终,糟糕的设计可能会迎头赶上你,因此你当然应该尝试正确设计应用程序。 但是,如果你现在只需要获得足够的良好性能,缓存可能是一种非常出色的 [方法],因此可以在以后有时间重构应用程序。
虽然缓存可以提供明显的性能增强,但它并非适用于所有情况,例如使用实时、频繁更新数据的应用程序,或者即使短暂的过时数据也是不可接受的。 但对于大多数应用程序,应使用缓存。 有关 ASP.NET 2.0 中缓存的更多背景信息,请参阅 ASP.NET 2.0 快速入门教程的“性能缓存”部分。
步骤 1:创建缓存网页
在开始探索 ObjectDataSource 缓存功能之前,让我们先花点时间在网站项目中创建本教程和后续三个页面所需的 ASP.NET 页面。 首先添加名为 Caching
的新文件夹。 接下来,将以下 ASP.NET 页添加到该文件夹,确保将每个页面与 Site.master
母版页相关联:
Default.aspx
ObjectDataSource.aspx
FromTheArchitecture.aspx
AtApplicationStartup.aspx
SqlCacheDependencies.aspx
图 1:为 Caching-Related 教程添加 ASP.NET 页
与其他文件夹中一样, Default.aspx
文件夹中 Caching
会列出其部分的教程。 回想一下, SectionLevelTutorialListing.ascx
用户控件提供此功能。 因此,将此用户控件Default.aspx
从解决方案资源管理器拖到页面设计视图中,将其添加到 。
图 2:图 2:将用户控件添加到 SectionLevelTutorialListing.ascx
Default.aspx
(单击以查看全尺寸图像)
最后,将这些页面作为条目添加到文件中 Web.sitemap
。 具体而言,在“处理二进制数据 <siteMapNode>
”之后添加以下标记:
<siteMapNode title="Caching" url="~/Caching/Default.aspx"
description="Learn how to use the caching features of ASP.NET 2.0.">
<siteMapNode url="~/Caching/ObjectDataSource.aspx"
title="ObjectDataSource Caching"
description="Explore how to cache data directly from the
ObjectDataSource control." />
<siteMapNode url="~/Caching/FromTheArchitecture.aspx"
title="Caching in the Architecture"
description="See how to cache data from within the
architecture." />
<siteMapNode url="~/Caching/AtApplicationStartup.aspx"
title="Caching Data at Application Startup"
description="Learn how to cache expensive or infrequently-changing
queries at the start of the application." />
<siteMapNode url="~/Caching/SqlCacheDependencies.aspx"
title="Using SQL Cache Dependencies"
description="Examine how to have data automatically expire from the
cache when its underlying database data is modified." />
</siteMapNode>
更新 Web.sitemap
后,请花点时间通过浏览器查看教程网站。 左侧菜单现在包含缓存教程的项。
图 3:站点地图现在包含缓存教程的条目
步骤 2:在网页中显示产品列表
本教程探讨如何使用 ObjectDataSource 控件的内置缓存功能。 但是,在了解这些功能之前,我们首先需要一个页面。 让我们创建一个网页,该网页使用 GridView 列出 ObjectDataSource 从 类检索到的 ProductsBLL
产品信息。
首先打开 ObjectDataSource.aspx
文件夹中的页面 Caching
。 将 GridView 从工具箱拖到Designer,将其ID
属性设置为 Products
,并从其智能标记中选择将其绑定到名为 ProductsDataSource
的新 ObjectDataSource 控件。 配置 ObjectDataSource 以使用 ProductsBLL
类。
图 4:将 ObjectDataSource 配置为使用 ProductsBLL
类 (单击以查看全尺寸图像)
对于此页面,让我们创建一个可编辑的 GridView,以便我们可以检查通过 GridView 接口修改 ObjectDataSource 中缓存的数据时会发生什么情况。 将 SELECT 选项卡中的下拉列表设置为默认值, GetProducts()
但将“更新”选项卡中 UpdateProduct
的选定项更改为接受 productName
、 unitPrice
和 productID
作为其输入参数的重载。
图 5:将 UPDATE 选项卡 Drop-Down 列表设置为相应的 UpdateProduct
重载 (单击以查看全尺寸图像)
最后,将“插入”和“删除”选项卡中的下拉列表设置为 (“无”) 并单击“完成”。 完成“配置数据源”向导后,Visual Studio 将 ObjectDataSource 的 OldValuesParameterFormatString
属性设置为 original_{0}
。 如 插入、更新和删除数据概述 教程中所述,需要从声明性语法中删除此属性或设置回其默认值 {0}
,以便更新工作流不会出错。
此外,在向导完成时,Visual Studio 会将每个产品数据字段的字段添加到 GridView。 删除除 、 CategoryName
和 UnitPrice
BoundFields 外ProductName
的所有字段。 接下来,将 HeaderText
其中每个 BoundFields 的属性分别更新为“产品”、“类别”和“价格”。 由于 字段ProductName
是必需的,因此请将 BoundField 转换为 TemplateField,并将 RequiredFieldValidator 添加到 。EditItemTemplate
同样,将 UnitPrice
BoundField 转换为 TemplateField 并添加 CompareValidator,以确保用户输入的值是大于或等于零的有效货币值。 除了这些修改之外,还可以随意执行任何美观更改,例如右对齐 UnitPrice
值,或在只读和编辑界面中指定文本的格式 UnitPrice
。
通过选中 GridView 智能标记中的“启用编辑”复选框,使 GridView 可编辑。 另检查“启用分页”和“启用排序”复选框。
注意
需要了解如何自定义 GridView 编辑界面? 如果是这样,请参阅 自定义数据修改接口 教程。
图 6:启用 GridView 对编辑、排序和分页的支持 (单击以查看全尺寸图像)
进行这些 GridView 修改后,GridView 和 ObjectDataSource 的声明性标记应如下所示:
<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False"
DataKeyNames="ProductID" DataSourceID="ProductsDataSource"
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:TextBox>
<asp:RequiredFieldValidator
ID="RequiredFieldValidator1" Display="Dynamic"
ControlToValidate="ProductName" SetFocusOnError="True"
ErrorMessage="You must provide a name for the product."
runat="server">*</asp:RequiredFieldValidator>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server"
Text='<%# Bind("ProductName") %>'></asp:Label>
</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"
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" runat="server"
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="ProductsDataSource" runat="server"
OldValuesParameterFormatString="{0}" 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>
如图 7 所示,可编辑的 GridView 列出了数据库中每个产品的名称、类别和价格。 花点时间测试页面功能,对结果进行排序、分页和编辑记录。
图 7:每个产品的名称、类别和价格都列在可排序、可分页、可编辑的 GridView (单击以查看全尺寸图像)
步骤 3:检查 ObjectDataSource 何时请求数据
Products
GridView 通过调用 Select
ObjectDataSource 的 ProductsDataSource
方法检索要显示的数据。 此 ObjectDataSource 创建业务逻辑层 类的 ProductsBLL
实例并调用其 GetProducts()
方法,后者又调用数据访问层 的 ProductsTableAdapter
GetProducts()
方法。 DAL 方法连接到 Northwind 数据库并发出配置的 SELECT
查询。 然后,此数据将返回到 DAL,DAL 将其打包在 中 NorthwindDataTable
。 DataTable 对象返回到 BLL,后者将其返回到 ObjectDataSource,后者将其返回到 GridView。 然后,GridView 为 DataRow
DataTable 中的每个 对象创建一个GridViewRow
对象,最终每个GridViewRow
对象都呈现到 HTML 中,该 HTML 返回到客户端并显示在访问者的浏览器中。
每当 GridView 需要绑定到其基础数据时,都会发生此事件序列。 首次访问页面时、从一页数据移动到另一页数据时、对 GridView 进行排序时,或通过 GridView 的内置编辑或删除界面修改 GridView 数据时,就会发生这种情况。 如果 GridView 视图状态处于禁用状态,则 GridView 也会在每次回发时被重新绑定。 还可以通过调用 GridView 的 方法显式重新绑定到其 DataBind()
数据。
为了充分了解从数据库中检索数据的频率,让我们显示一条消息,指示何时重新检索数据。 在 GridView 的上方添加一个名为 的 ODSEvents
标签 Web 控件。 清除其 Text
属性并将其 属性设置为 EnableViewState
False
。 在 Label 下面,添加一个按钮 Web 控件,并将其 Text
属性设置为回发。
图 8:将标签和按钮添加到 GridView (单击以查看全尺寸图像)
在数据访问工作流期间,在创建基础对象并调用其配置的方法之前,将触发 ObjectDataSource 事件 Selecting
。 为此事件创建事件处理程序并添加以下代码:
Protected Sub ProductsDataSource_Selecting(sender As Object, _
e As ObjectDataSourceSelectingEventArgs) _
Handles ProductsDataSource.Selecting
ODSEvents.Text = "-- Selecting event fired"
End Sub
每次 ObjectDataSource 向体系结构请求数据时,Label 将显示文本 Selection 事件触发。
在浏览器中访问此页面。 首次访问页面时,将显示文本“选择触发的事件”。 单击“回发”按钮,请注意,文本消失 (假定 GridView 的 EnableViewState
属性设置为 True
,默认) 。 这是因为,在回发时,GridView 从其视图状态重新构造,因此不会将其数据转向 ObjectDataSource。 但是,对数据进行排序、分页或编辑会导致 GridView 重新绑定到其数据源,因此,“选择”事件触发的文本再次出现。
图 9:每当 GridView 重新绑定到其数据源时,“选择”触发的事件会显示 (单击以查看全尺寸图像)
图 10:单击回发按钮会导致 GridView 从其视图状态重新构造 (单击以查看全尺寸图像)
每次对数据进行分页或排序时,检索数据库数据似乎都是浪费的。 毕竟,由于我们使用默认分页,ObjectDataSource 在显示第一页时检索了所有记录。 即使 GridView 不提供排序和分页支持,也必须在每次用户首次访问页面时从数据库中检索数据, (每次回发(如果视图状态被禁用) )。 但是,如果 GridView 向所有用户显示相同的数据,则这些额外的数据库请求是多余的。 为什么不缓存从 GetProducts()
方法返回的结果,并将 GridView 绑定到那些缓存的结果?
步骤 4:使用 ObjectDataSource 缓存数据
只需设置几个属性,ObjectDataSource 就可以配置为自动将其检索到的数据缓存在 ASP.NET 数据缓存中。 以下列表汇总了 ObjectDataSource 的缓存相关属性:
- EnableCaching 必须设置为
True
才能启用缓存。 默认为False
。 - CacheDuration 缓存数据的时间量(以秒为单位)。 默认值为 0。 如果
EnableCaching
为True
并且CacheDuration
设置为大于零的值,则 ObjectDataSource 将仅缓存数据。 - CacheExpirationPolicy 可以设置为
Absolute
或Sliding
。 如果Absolute
为 ,则 ObjectDataSource 将缓存其检索到的数据数CacheDuration
秒;如果Sliding
为 ,则数据仅在几秒钟未访问CacheDuration
后过期。 默认为Absolute
。 - CacheKeyDependency 使用此属性将 ObjectDataSource 缓存条目与现有缓存依赖项相关联。 ObjectDataSource 的数据条目可以通过将其关联的
CacheKeyDependency
过期来过早地从缓存中逐出。 此属性最常用于将 SQL 缓存依赖项与 ObjectDataSource 缓存相关联,我们将在以后 的使用 SQL 缓存依赖项 教程中探讨这一主题。
让我们将 ProductsDataSource
ObjectDataSource 配置为以绝对比例缓存其数据 30 秒。 将 ObjectDataSource 的 EnableCaching
属性设置为 True
,将其 CacheDuration
属性设置为 30。 将 CacheExpirationPolicy
属性设置为其默认值 Absolute
。
图 11:配置 ObjectDataSource 以缓存其数据 30 秒 (单击以查看全尺寸图像)
保存更改并在浏览器中重新访问此页面。 首次访问页面时,将显示“选择”事件触发的文本,因为最初数据不在缓存中。 但是,通过单击“回发”按钮、排序、分页或单击“编辑”或“取消”按钮触发的后续回发 不会 重新显示“选择”事件触发的文本。 这是因为 Selecting
仅当 ObjectDataSource 从其基础对象获取其数据时才会触发事件; Selecting
如果从数据缓存中拉取数据,则不会触发该事件。
30 秒后,数据将从缓存中逐出。 如果调用 ObjectDataSource 、 Insert
Update
或 Delete
方法,数据也将从缓存中逐出。 因此,经过 30 秒或单击“更新”按钮后,排序、分页或单击“编辑”或“取消”按钮将导致 ObjectDataSource 从其基础对象获取其数据,并在事件触发时 Selecting
显示 Selection 事件触发的文本。 这些返回的结果将放回数据缓存中。
注意
如果经常看到 Selection 事件触发的文本,即使你期望 ObjectDataSource 处理缓存的数据,也可能是由于内存限制。 如果可用内存不足,则 ObjectDataSource 添加到缓存中的数据可能已被清理。 如果 ObjectDataSource 似乎未正确缓存数据或只是偶尔缓存数据,请关闭某些应用程序以释放内存,然后重试。
图 12 说明了 ObjectDataSource 缓存工作流。 当屏幕上显示 Selection 事件触发的文本时,这是因为数据不在缓存中,必须从基础对象中检索。 但是,如果缺少此文本,是因为缓存中提供了数据。 从缓存返回数据时,不会调用基础对象,因此不会执行数据库查询。
图 12:ObjectDataSource 从数据缓存中存储和检索其数据
每个 ASP.NET 应用程序都有自己的数据缓存实例,该实例在所有页面和访问者之间共享。 这意味着 ObjectDataSource 存储在数据缓存中的数据同样在访问页面的所有用户之间共享。 若要验证这一点,请在 ObjectDataSource.aspx
浏览器中打开页面。 首次访问页面时,如果先前测试添加到缓存的数据现已逐出) ,则“选择”事件触发的文本将显示 (。 打开第二个浏览器实例,将 URL 从第一个浏览器实例复制并粘贴到第二个浏览器实例。 在第二个浏览器实例中,不显示 Selecting 事件触发的文本,因为它使用与第一个浏览器相同的缓存数据。
将检索到的数据插入缓存时,ObjectDataSource 使用缓存键值,其中包括: CacheDuration
和 CacheExpirationPolicy
属性值;ObjectDataSource 使用的基础业务对象的类型,该类型通过 TypeName
属性 (ProductsBLL
指定,在此示例中) ;属性的值 SelectMethod
以及集合中 SelectParameters
参数的名称和值;以及其 StartRowIndex
和 MaximumRows
属性的值, 实现 自定义分页时使用的 。
将这些属性组合创建缓存键值可确保这些值更改时具有唯一的缓存项。 例如,在以前的教程中,我们学习了如何使用 ProductsBLL
类 ,该类 GetProductsByCategoryID(categoryID)
返回指定类别的所有产品。 一个用户可能会来到页面并查看饮料,该页面的 为 CategoryID
1。 如果 ObjectDataSource 缓存其结果而不考虑 SelectParameters
值,则当另一个用户在饮料产品位于缓存中时来到页面查看调味品时,他们会看到缓存的饮料产品,而不是调味品。 通过通过这些属性(包括 的值 SelectParameters
)改变缓存键,ObjectDataSource 为饮料和调味品维护单独的缓存条目。
过时的数据问题
当调用其任何一个 、 或 Delete
方法时,Update
ObjectDataSource 会自动从缓存中逐出其Insert
项。 这有助于在通过页面修改数据时清除缓存条目,从而防止过时的数据。 但是,使用缓存的 ObjectDataSource 仍可能显示过时的数据。 在最简单的情况下,这可能是由于数据直接在数据库中发生更改。 也许数据库管理员刚刚运行了一个脚本来修改数据库中的某些记录。
这种情况也可能以更微妙的方式展开。 虽然 ObjectDataSource 在调用其数据修改方法之一时从缓存中逐出其项,但删除的缓存项适用于 ObjectDataSource 属性值的特定组合, (CacheDuration
、、 TypeName
SelectMethod
等) 。 如果你有两个使用不同 SelectMethods
或 SelectParameters
的 ObjectDataSource,但仍可以更新相同的数据,则一个 ObjectDataSource 可能会更新一行并使其自己的缓存条目失效,但第二个 ObjectDataSource 的相应行仍将从缓存中提供。 建议创建页面来展示此功能。 创建一个页面,该页面显示一个可编辑的 GridView,该网格视图从使用缓存的 ObjectDataSource 中提取其数据,并且配置为从 ProductsBLL
类 s GetProducts()
方法获取数据。 将另一个可编辑的 GridView 和 ObjectDataSource 添加到此页面 (或另一个) ,但对于第二个 ObjectDataSource,请使用 GetProductsByCategoryID(categoryID)
方法。 由于这两个 ObjectDataSources SelectMethod
属性不同,因此它们各自具有自己的缓存值。 如果在一个网格中编辑产品,下次通过分页、排序等方式将数据绑定回另一个网格 () ,它仍将提供旧的缓存数据,并且不会反映从另一个网格所做的更改。
简言之,仅当愿意具有过时数据的可能性时,才使用基于时间的过期时间;对于数据新鲜度非常重要的情况,请使用较短的过期时间。 如果不接受过时数据,请放弃缓存或使用 SQL 缓存依赖项, (假设它是你) 缓存的数据库数据。 我们将在将来的教程中探讨 SQL 缓存依赖项。
总结
在本教程中,我们了解了 ObjectDataSource 的内置缓存功能。 只需设置几个属性,即可指示 ObjectDataSource 将从指定 SelectMethod
返回的结果缓存到 ASP.NET 数据缓存中。 CacheDuration
和 CacheExpirationPolicy
属性指示缓存项的持续时间,以及它是绝对过期还是滑动过期。 属性 CacheKeyDependency
将所有 ObjectDataSource 的缓存条目与现有缓存依赖项相关联。 这可用于在达到基于时间的过期之前从缓存中逐出 ObjectDataSource 的条目,并且通常用于 SQL 缓存依赖项。
由于 ObjectDataSource 只是将其值缓存到数据缓存中,因此我们可以以编程方式复制 ObjectDataSource 的内置功能。 在表示层执行此操作是没有意义的,因为 ObjectDataSource 提供了现成的此功能,但我们可以在体系结构的单独层中实现缓存功能。 为此,我们需要重复 ObjectDataSource 使用的相同逻辑。 在下一教程中,我们将探讨如何以编程方式从体系结构内部处理数据缓存。
编程快乐!
深入阅读
有关本教程中讨论的主题的详细信息,请参阅以下资源:
关于作者
斯科特·米切尔是七本 ASP/ASP.NET 书籍的作者和 4GuysFromRolla.com 的创始人,自 1998 年以来一直在使用 Microsoft Web 技术。 Scott 担任独立顾问、培训师和作家。 他的最新一本书是 山姆斯在 24 小时内 ASP.NET 2.0。 可以在 上mitchell@4GuysFromRolla.com联系他,也可以通过他的博客(可在 中找到http://ScottOnWriting.NET)。
特别感谢
本教程系列由许多有用的审阅者审阅。 本教程的首席审阅者是 Teresa Murphy。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。