简介
前两篇教程探讨了在表示层和缓存层缓存数据。在使用 ObjectDataSource 缓存数据 教程 中 , 我们探讨了在表示层使用 ObjectDataSource 的缓存功能来缓存数据。在架构中缓存数据 教程 探讨了在一个新的独立的缓存层中缓存数据。这两篇教程都是使用 应激装载方法来处理数据缓存。使用应激装载方法,每次请求数据时,系统首先检查数据是否在缓存中。如果不在,则从数据源,比如数据库,来获取数据,然后将其保存在缓存中。应激装载的主要优点是易于实现。其缺点之一就是程序的性能对于各个请求来说是不一致的。设想一下,某页面使用前面教程中创建的缓存层来展示产品信息。首次访问该页面时,或者在缓存数据由于内存限制或指定的有效期到期而被清除后首次访问该页面时,只能从数据库中提取数据。因此,这些用户请求所花的时间就比由缓存服务的用户请求长。
预装载提供了另外一种缓存管理策略 ,该策略 通过在需要缓存数据之前就将其装载来使程序性能在各请求间变得均衡。典型情况是,预装载使用一个进程,该进程周期性地检查底层数据更新,或者在底层数据更新时得到通知。该进程于是更新缓存以使其保持最新。当底层数据来自较慢的数据库连接、 Web 服务或其它某个特别缓慢的数据源时,预装载方法特别有用。但该预装载方法较难实施,因为它需要我们创建、配置和部署一个进程来检查数据更改并更新缓存。
在此教程中,我们要探讨另一种预装载方法 , 就是在应用程序启动时将数据装载到缓存。该方法对于缓存静态数据(比如数据库查找表中的记录)特别有用。
注意 : 有关预装载和应激装载间的区别、pros 、cons 列表 , 以及实现建议的深入探讨 , 请参阅 .NET 框架应用的缓存架构指南 中的管理缓存内容 一节。
步骤1 : 确定应用启动时要缓存的数据
我们在前两篇教程中探讨的使用应激装载方法的缓存案例适合于可能会周期性改变并且生成时不需要太长时间的数据。但是如果缓存数据从不改变,则应激装载方法中使用的缓存有效期是多余的。另外,如果要花很长的时间来生成缓存的数据,则那些在缓存为空时请求数据的用户就不得不在程序提取底层数据时长时间地等待。对此,可以考虑在应用程序启动时就将静态数据和要花很长时间才能生成的数据缓存。
虽然数据库有很多动态的、经常改变的值 ,但大多数数据库也有 不少的静态数据。例如,实际上所有的数据模型都有一个或多个这样的列:其值源于一个固定的选择集。比如, Patients 数据库表可能有一个 PrimaryLanguage 列,其值可能是 English 、 Spanish 、 French 、 Russian 、 Japanese 等等。通常,这些类型的列使用查找表来实现。我们不是将字符串 “English” 或 “French” 直接存储在 Patients 表中,而是创建第二个表,该表通常有两列 – 唯一标识符列和字符串说明列,每个可能的值都在该表中有一条记录。 Patients 表中的 PrimaryLanguage 列存储查找表中相应的唯一标识符。图 1 中,患者 John Doe 的第一语言为 English ,而 Ed Johnson 的第一语言为 Russian 。
图1 :Languages 表为 Patients 表所使用的查找表
在编辑或创建新患者的用户界面上有一个下拉列表 ,其中会 列出 Languages 表的所有记录中的语言项以供用户选择。如果没有缓存,每次访问该界面时,系统都要查询 Languages 表。这既浪费又没有必要,因为查找表值即使有变化也很少。
我们可以使用在前面教程中探讨的应激装载技术来缓存Languages 数据。然而应激装载使用基于时间的有效期,这对静态查找表数据来说没有必要。尽管使用应激装载的缓存总比没有缓存好,但最好的方法还是在应用程序启动时将查找表数据预先装载到缓存中。
本教程中 , 我们将探讨如何缓存查找表数据和其它静态信息。
步骤2 : 探讨缓存数据的不同方法
在一个 ASP.NET 应用程序中 , 可以使用多种方法来缓存信息。在前面的教程中,我们已看到如何使用数据缓存。另外,我们还可以通过编码使用 静态成员或应用状态来缓存对象。
在我们可以 访问一个类的成员之前 ,首先 必须将该类实例化。例如,为了调用业务逻辑层中一个类的方法,我们首先必须创建该类的实例:
- ProductsBLL productsAPI = new ProductsBLL();
- productsAPI.SomeMethod();
- productsAPI.SomeProperty = "Hello, World!";
在可以调用 SomeMethod 或使用 SomeProperty 之前 , 我们必须先用关键字 New 来创建该类的一个实例。 SomeMethod 和 SomeProperty 与一个具体的实例相关联。这些成员的生命周期取决于其相关对象的生命周期。另一方面, 静态成员是该类的所有实例共享的变量、属性、方法,因此其生命周期与该类的生命周期一样长。静态成员用关键字 Static 表示。
除了静态成员,还可以使用应用程序状态来缓存数据。每个 ASP.NET 应用程序都有一个 name/value 集合,该集合为和该应用程序的所有用户和页面所共享。可以通过HttpContext 类 的 Application 属性 来访问该集合,在 ASP.NET 页面的代码文件类中可以这样使用该集合:
Application["key"] = value Dim value As Object = Application["key"]
数据缓存提供了更为丰富的、可缓存数据 的API , 提供了基于时间和基于依赖项的有效期机制 , 以及缓存条目优先级等机制。在使用静态成员和应用程序状态时,页面开发者只能手工添加这些特性。不过,如果在应用程序启动时缓存数据并且在应用程序的生命周期内保持该数据,数据缓存的优点就毫无意义了。在本教程中,我们将看到使用三种缓存静态数据技术的代码。
步骤3 : 缓存Suppliers 表数据
目前我们已实现的 Northwind 数据库表不包括任何传统的查找表。我们在 DAL 中实现的四个 DataTable 都是其值为非静态的模型表。在本教程中,我们没有花时间在 DAL 层增加一个新的 DataTable ,再在 BLL 层增加新的类和方法,而只是假设 Suppliers 表的数据是静态的。因此,我们可在应用程序启动时缓存其数据。
首先 , 在CL 文件夹中创建一个名为 StaticCache.cs 的新类。
图2 : 在 CL 文件夹中创建 StaticCache.cs 类
我们需要添加一个方法来在程序启动时将数据装载到适当的缓存存储器中 , 还需要添加方法来从该缓存返回数据。
- [System.ComponentModel.DataObject]
- public class StaticCache
- {
- private static Northwind.SuppliersDataTable suppliers = null;
-
- public static void LoadStaticCache()
- {
- // Get suppliers - cache using a static member variable
- SuppliersBLL suppliersBLL = new SuppliersBLL();
- suppliers = suppliersBLL.GetSuppliers();
- }
- [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
- public static Northwind.SuppliersDataTable GetSuppliers()
- {
- return suppliers;
- }
- }
上述代码在 LoadStaticCache() 方法中调用 SuppliersBLL 类的 GetSuppliers() 方法 ,并将该方法返回的结果保存在 一个静态成员变量 suppliers 中 。我们打算在应用程序启动时调用 LoadStaticCache() 方法。一旦应用程序启动时装载了该数据,任何需要使用供应商数据的页面都可以调用 StaticCache 类的 GetSuppliers() 方法。因此,访问数据库获取 suppliers 的情况只会发生一次,就是在应用程序启动时。
除了使用静态成员变量用作缓存存储器外 , 我们还可以使用应用程序状态或数据缓存。下面的代码对类进行修改以使用应用程序状态:
- [System.ComponentModel.DataObject]
- public class StaticCache
- {
- public static void LoadStaticCache()
- {
- // Get suppliers - cache using application state
- SuppliersBLL suppliersBLL = new SuppliersBLL();
- HttpContext.Current.Application["key"] = suppliersBLL.GetSuppliers();
- }
-
- [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
- public static Northwind.SuppliersDataTable GetSuppliers()
- {
- return HttpContext.Current.Application["key"] as Northwind.SuppliersDataTable;
- }
- }
LoadStaticCache() 方法将 供应商信息存储在应用程序变量 key 中。GetSuppliers() 方法将其当作相应的类型 (Northwind.SuppliersDataTable) 而返回。虽然在 ASP.NET 页面的 code-behind 类中可以使用 Application["key"] 访问应用程序状态,但在架构中,我们必须使用 HttpContext.Current.Application["key"]以获得当前的 HttpContext 。
同样 ,也可以使用 数据缓存作为缓存存储器 , 如下列代码所示 :
- [System.ComponentModel.DataObject]
- public class StaticCache
- {
- public static void LoadStaticCache()
- {
- // Get suppliers - cache using the data cache
- SuppliersBLL suppliersBLL = new SuppliersBLL();
- HttpRuntime.Cache.Insert(
- /* key */ "key",
- /* value */ suppliers,
- /* dependencies */ null,
- /* absoluteExpiration */ Cache.NoAbsoluteExpiration,
- /* slidingExpiration */ Cache.NoSlidingExpiration,
- /* priority */ CacheItemPriority.NotRemovable,
- /* onRemoveCallback */ null);
- }
-
- [DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
- public static Northwind.SuppliersDataTable GetSuppliers()
- {
- return HttpRuntime.Cache["key"] as Northwind.SuppliersDataTable;
- }
- }
为了向数据缓存添加条目并且不指定基于时间的有效期 ,我们 使用了System.Web.Caching.Cache.NoAbsoluteExpiration 和System.Web.Caching.Cache.NoSlidingExpiration 值作为输入参数。这里我们使用了数据缓存的 Insert 方法的一个特定重载,这样我们可以指定缓存条目的 优先级。优先级用于确定在可用内存降低时要从缓存中清除的条目。此处我们使用优先级 NotRemovable ,这可确保该缓存条目不会被清除。
注意 : 本教程的下载代码在实现 StaticCache 类时使用的是静态成员变量方法。可在类文件的注释中找到使用应用程序状态和数据缓存技术的相应代码。
步骤4 : 应用程序启动时执行代码
为了在web 应用程序最初启动时执行代码 , 我们需要创建一个名为 Global.asax 的特殊文件。该文件可以包含应用级、会话级以及请求级事件的 event handler,我们可在此添加应用程序启动时要执行的代码。
要在web 应用程序的根目录中添加 Global.asax 文件 , 在Visual Studio 的 Solution Explorer 中 , 右键单击该 网站项目的名称, 选择Add New Item 。在 Add New Item 对话框中,选择 Global Application Class 条目类型,然后单击 Add 按钮。
注意 : 如果该项目中已有 Global.asax 文件,则在 Add New Item 对话框中不会列出 Global Application Class 条目类型。
图3 : 在 Web 应用程序的根目录中添加 Global.asax 文件
缺省的Global.asax 文件模板在一个服务器端 <script> 标记里包含五个方法 :
- Application_Start – web 应用程序最初启动时执行
- Application_End – 应用程序关闭时运行
- Application_Error – 每当 应用程序出现未处理的异常时执行
- Session_Start – 创建新会话时执行
- Session_End – 会话到期或停止时运行
在应用程序的生命周期内只会调用一次Application_Start event handler 。应用程序起始于一个 ASP.NET 资源首次被请求时 , 继续运行直到应用程序重启为止 , 修改/Bin 文件夹的内容、修改 Global.asax 、修改 App_Code 文件夹的内容、修改 Web.config 文件以及其它一些原因都可导致应用程序重启。有关应用程序生命周期的更详细的讨论,请参阅 ASP.NET 应用程序生命周期概述 。
这些教程中 , 我们只需要为Application_Start 方法添加代码 , 因此可放心地删除其它方法。在 Application_Start 中,只是调用 StaticCache 类的 LoadStaticCache() 方法来装载并缓存供应商信息:
- <%@ Application Language="C#" %>
- <script runat="server">
- void Application_Start(object sender, EventArgs e)
- {
- StaticCache.LoadStaticCache();
- }
- </script>
这样就可以了 ! 应用程序启动时 ,LoadStaticCache() 方法将从 BLL 获取 供应商 信息 , 然后将其存储在一个静态成员变量中 ( 或者您在StaticCache 类中最终使用的其它缓存存储器 ) 。要验证该行为,在 Application_Start 方法中设置一个断点并运行应用程序。请注意应用程序启动时触发了该断点。然而,后续的请求却不会引发程序执行 Application_Start 方法。
图4 : 使用断点验证 Application_Start Event Handler 的执行
注意 : 如果首次调试时没有触发 Application_Start 断点 ,那是因为 应用程序已经启动了。可以修改 Global.asax 或 Web.config 文件强制重启应用程序,然后重试。可在这些文件末尾简单地添加(或删除)一个空行来快速地重启应用程序。
步骤5 : 显示缓存数据
现在 ,StaticCache 类在应用程序启动时缓存了 供应商 数据 , 而该数据可以通过该类的 GetSuppliers() 方法来访问。要从表示层使用该数据,我们可以使用 ObjectDataSource ,也可以通过编码从 ASP.NET 页面的代码文件类中调用 StaticCache 类的 GetSuppliers() 方法。我们看看如何使用 ObjectDataSource 和 GridView 控件来显示缓存的供应商信息。
首先 , 打开Caching 文件夹中的 AtApplicationStartup.aspx 页面。从 Toolbox 中将一个 GridView 拖放到设计器中,将其 ID 属性设置为 Suppliers 。然后,从 GridView 的 smart 标签中选择创建一个名为 SuppliersCachedDataSource 的新 ObjectDataSource 。配置该 ObjectDataSource 使用 StaticCache 类的 GetSuppliers() 方法。
图5 : 配置 ObjectDataSource 使用 StaticCache 类
图6 : 使用 GetSuppliers() 方法提取缓存的 供应商 数据
完成向导后 ,Visual Studio 将自动为 SuppliersDataTable 的每个数据字段添加 BoundFields 。GridView 和 ObjectDataSource 的声明式标记看起来应该像下面这样 :
- <asp:GridView ID="Suppliers" runat="server" AutoGenerateColumns="False"
- DataKeyNames="SupplierID" DataSourceID="SuppliersCachedDataSource"
- EnableViewState="False">
- <Columns>
- <asp:BoundField DataField="SupplierID" HeaderText="SupplierID"
- InsertVisible="False" ReadOnly="True"
- SortExpression="SupplierID" />
- <asp:BoundField DataField="CompanyName" HeaderText="CompanyName"
- SortExpression="CompanyName" />
- <asp:BoundField DataField="Address" HeaderText="Address"
- SortExpression="Address" />
- <asp:BoundField DataField="City" HeaderText="City"
- SortExpression="City" />
- <asp:BoundField DataField="Country" HeaderText="Country"
- SortExpression="Country" />
- <asp:BoundField DataField="Phone" HeaderText="Phone"
- SortExpression="Phone" />
- </Columns>
- </asp:GridView>
-
- <asp:ObjectDataSource ID="SuppliersCachedDataSource" runat="server"
- OldValuesParameterFormatString="original_{0}"
- SelectMethod="GetSuppliers" TypeName="StaticCache" />
图7 显示的是 从浏览器查看时的页面。页面输出与我们从BLL 的 SuppliersBLL 类中获取数据时的输出相同 , 不同的是使用了 StaticCache 类在应用程序启动时缓存供应商数据并将其返回。您可在 StaticCache 类的 GetSuppliers() 方法中设置断点来验证该行为。
图7 : 在 GridView 中显示缓存的 供应商 数据
小结
几乎每一种数据模型都包含相当数量的静态数据 , 且通常都以查找表形式实现。由于该信息是静态的,没有必要每次需要显示该信息时都访问数据库。此外,由于其静态特性,缓存数据时没有必要设置有效期。在本教程中,我们了解了如何提取此类数据并将其缓存在数据缓存、应用程序状态以及静态成员变量中。该信息在应用程序启动时就被缓存,且在应用程序的整个生命周期内都会保留在缓存中。
在本教程以及前两篇教程中 , 我们探讨了在应用程序的生命周期内缓存数据 , 以及使用基于时间的有效期来缓存数据。然而,在缓存数据库数据时,使用基于时间的有效期可能不太理想。与周期性地刷新缓存相比,仅在基础数据库数据被修改时才清除相应的缓存条目算是最佳方案。可通过使用 SQL 缓存依赖项来实现该想法,对此,我们将在下一篇教程中探讨。