在应用程序启动时缓存数据 (C#)

作者 :斯科特·米切尔

下载 PDF

在任何 Web 应用程序中,都会经常使用一些数据,某些数据很少使用。 我们可以通过提前加载常用数据(称为缓存技术)来提高 ASP.NET 应用程序的性能。 本教程演示了主动加载的一种方法,即在应用程序启动时将数据加载到缓存中。

介绍

前面的两个教程介绍了在演示文稿层和缓存层中缓存数据。 在 使用 ObjectDataSource 缓存数据时,我们了解了如何使用 ObjectDataSource 的缓存功能在呈现层中缓存数据。 在体系结构中缓存数据 检查了新的单独缓存层中的缓存。 这两个教程都使用 被动加载 处理数据缓存。 每次请求数据时,系统先检查数据是否已在缓存中。 否则,它会从原始源(如数据库)获取数据,然后将其存储在缓存中。 反应式加载的主要优点是其易于实现。 其缺点之一是其跨请求的性能不均衡。 假设有一个页面,该页面使用前面的教程中的缓存层来显示产品信息。 首次访问此页面时,或者由于内存限制或达到指定的到期时间,缓存数据被逐出后首次访问,则必须从数据库检索数据。 因此,这些用户请求所需的时间比缓存可以提供的用户请求长。

主动加载 提供了一种替代缓存管理策略,可在需要之前加载缓存的数据,从而优化请求的性能。 通常,主动加载使用一些过程,这些过程会定期检查或在基础数据更新时收到通知。 然后,此过程将更新缓存以使其保持最新状态。 如果基础数据来自数据库连接缓慢、Web 服务或其他某些特别缓慢的数据源,则主动加载尤其有用。 但是,主动加载的此方法很难实现,因为它需要创建、管理和部署进程来检查更改并更新缓存。

我们将在本教程中探索的另一种主动加载方式是在应用程序启动时将数据加载到缓存中。 此方法特别适用于缓存静态数据,例如数据库查找表中的记录。

注释

若要更深入地了解主动加载和被动加载之间的差异,以及优点、缺点和实现建议的列表,请参阅 .NET Framework 应用程序的缓存体系结构指南“管理缓存”部分的内容。

步骤 1:确定应用程序启动时要缓存的数据

使用我们在前两个教程中检查的被动加载的缓存示例可以很好地处理可能会定期更改的数据,并且不会花费很长的时间生成。 但是,如果缓存的数据永远不会更改,则反应式加载使用的过期是多余的。 同样,如果缓存的数据需要很长时间才能生成,则那些请求查找缓存为空的用户在检索基础数据时必须忍受长时间的等待。 考虑缓存静态数据和在应用程序启动时生成时间异常长的数据。

虽然数据库具有许多动态且频繁变化的值,但大多数数据库也具有相当数量的静态数据。 例如,几乎所有数据模型都有一个或多个列,这些列包含固定选项集中的特定值。 Patients数据库表可能有一列PrimaryLanguage,其值集可以是英语、西班牙语、法语、俄语、日语等。 通常,这些类型的列是使用 查阅表实现的。 而不是将字符串英语或法语 Patients 存储在表中,而是创建第二个表,该表通常包含两列(唯一标识符和字符串说明)以及每个可能值的记录。 PrimaryLanguage表中的Patients列将相应的唯一标识符存储在查阅表中。 在图 1 中,病人约翰·杜的主要语言是英语,而埃德·约翰逊是俄语。

语言表是患者表使用的查阅表格

图 1Languages 表 是 Patients 表 使用的 查找表

用于编辑或创建新患者的用户界面将包含表格中 Languages 记录填充的允许语言的下拉列表。 如果没有缓存,每次访问此接口时,系统都必须查询 Languages 表。 这是浪费且不必要的,因为查找表的值几乎不发生更改,即使发生也很少。

可以使用前面教程中检查的相同反应加载技术来缓存 Languages 数据。 但是,被动加载使用基于时间的过期,静态查找表数据不需要此过期。 虽然使用被动加载的缓存比根本不缓存更好,但最佳方法是在应用程序启动时主动将查找表数据加载到缓存中。

本教程介绍如何缓存查阅表数据和其他静态信息。

步骤 2:检查缓存数据的不同方法

可以使用各种方法以编程方式缓存 ASP.NET 应用程序中的信息。 我们已经了解了如何在前面的教程中使用数据缓存。 或者,可以使用 静态成员应用程序状态以编程方式缓存对象。

使用类时,通常必须先实例化该类,然后才能访问其成员。 例如,若要从业务逻辑层中的某个类调用方法,必须先创建类的实例:

ProductsBLL productsAPI = new ProductsBLL();
productsAPI.SomeMethod();
productsAPI.SomeProperty = "Hello, World!";

在调用 SomeMethod 或使用 SomeProperty 之前,必须先使用 new 关键字创建类的实例。 SomeMethodSomeProperty 与特定实例相关联。 这些成员的生存期与其关联的对象的生存期相关联。 另一方面,静态成员是类的所有实例之间共享的变量、属性和方法,因此,它们会在类的整个生命周期中存在。 静态成员由关键字 static表示。

除了静态成员,还可以使用应用程序状态缓存数据。 每个 ASP.NET 应用程序都维护一个名称/值集合,该集合在应用程序的所有用户和页面之间共享。 可以通过HttpContext类的Application属性访问此集合,并在 ASP.NET 页面的后台代码类中使用,如下所示:

Application["key"] = value;
object value = Application["key"];

数据缓存为缓存数据提供了更丰富的 API,为基于时间和依赖项的过期、缓存项优先级等提供机制。 使用静态成员和应用程序状态时,页面开发人员必须手动添加此类功能。 但是,当在应用程序启动时缓存数据用于应用程序的整个生命周期时,数据缓存的优点就变得没有意义。 在本教程中,我们将介绍使用所有三种用于缓存静态数据的技术的代码。

步骤 3:缓存Suppliers表数据

我们迄今实现的 Northwind 数据库表不包括任何传统的查阅表。 在 DAL 中实现的四个 DataTable 都是非静态值的所有模型表。 与其花时间将新的 DataTable 添加到 DAL,然后再为 BLL 添加新的类和方法,在本教程中,我们不妨假设 Suppliers 表的数据是静态的。 因此,我们可以在应用程序启动时缓存此数据。

若要开始,请在文件夹中创建一StaticCache.csCL个名为的新类。

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

该代码使用静态成员变量suppliers来保存SuppliersBLL类的GetSuppliers()方法的结果, 该方法是在LoadStaticCache()方法中调用的。 该方法 LoadStaticCache() 应在应用程序启动时调用。 在应用程序启动时加载此数据后,需要使用供应商数据的任何页面都可以调用 StaticCacheGetSuppliers() 的方法。 因此,在应用程序启动时,仅调用数据库以获取供应商一次。

我们可以选择使用应用程序状态或数据缓存,而不是使用静态成员变量作为缓存存储。 以下代码显示重新改造的类以使用应用程序状态:

[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()中,供应商信息存储到应用程序变量 。 它作为适当的类型 (Northwind.SuppliersDataTable) 从 GetSuppliers()中返回。 虽然可以在使用 Application["key"]ASP.NET 页面的代码隐藏类中访问应用程序状态,但在体系结构中,我们必须使用 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.NoAbsoluteExpirationSystem.Web.Caching.Cache.NoSlidingExpiration 值作为输入参数。 选择了此数据缓存 Insert 方法的特定重载,以便我们可以指定缓存项的 优先级 。 优先级用于确定在可用内存不足时要从缓存中除名的项。 在这里,我们使用优先级 NotRemovable,以确保不会清理此缓存项。

注释

本教程的下载使用静态成员变量方法实现 StaticCache 类。 应用程序状态和数据缓存技术的代码在类文件中的注释中可用。

步骤 4:在应用程序启动时执行代码

若要在 Web 应用程序首次启动时执行代码,需要创建名为 Global.asax 的特殊文件。 此文件可以包含应用程序、会话和请求级事件的事件处理程序,在这里我们可以添加每当应用程序启动时将执行的代码。

Global.asax通过在 Visual Studio 的解决方案资源管理器中右键单击网站项目名称并选择“添加新项”,将文件添加到 Web 应用程序的根目录。 在“添加新项”对话框中,选择“全局应用程序类”项类型,然后单击“添加”按钮。

注释

如果项目中已有文件 Global.asax ,全局应用程序类项类型将不会在“添加新项”对话框中列出。

将 Global.asax 文件添加到 Web 应用程序的根目录

图 3:将 Global.asax 文件添加到 Web 应用程序的根目录(单击以查看全尺寸图像

默认 Global.asax 文件模板在服务器端 <script> 标记中包含五种方法:

  • Application_Start 首次启动 Web 应用程序时执行
  • Application_End 在应用程序关闭时运行
  • Application_Error 每当未经处理的异常到达应用程序时执行
  • Session_Start 创建新会话时执行
  • Session_End 在会话过期或被放弃时运行

Application_Start事件处理程序在应用程序的生命周期内仅调用一次。 应用程序在第一次请求 ASP.NET 资源时启动,并持续运行,直到应用程序重新启动。这可以通过修改/Bin文件夹的内容、修改Global.asax、编辑App_Code文件夹中的内容,或修改Web.config文件等方式实现。 有关应用程序生命周期的更详细讨论,请参阅 ASP.NET 应用程序生命周期概述

对于这些教程,我们只需要将代码添加到 Application_Start 方法,因此可以随意删除其他代码。 在 Application_Start 中,只需调用 StaticCacheLoadStaticCache() 的方法,即可加载和缓存供应商信息:

<%@ Application Language="C#" %>
<script runat="server">
    void Application_Start(object sender, EventArgs e) 
    {
        StaticCache.LoadStaticCache();
    }
</script>

就是这么简单! 在应用程序启动时,该方法 LoadStaticCache() 将从 BLL 中获取供应商信息,并将其存储在静态成员变量中(或者你最终在类中使用 StaticCache 的任何缓存存储)。 若要验证此行为,请在 Application_Start 方法中设置断点并运行应用程序。 请注意,断点在应用程序启动时触发。 但是,后续请求不会导致 Application_Start 该方法执行。

使用断点验证是否正在执行Application_Start事件处理程序

图 4:使用断点验证 Application_Start 事件处理程序是否正在执行(单击以查看全尺寸图像

注释

如果在首次开始调试时未命中 Application_Start 断点,这是因为应用程序已启动。 通过修改Global.asax文件或Web.config文件来强制应用程序重启,然后重试。 只需在其中一个文件末尾添加一个空白行即可快速重启应用程序。

步骤 5:显示缓存的数据

此时, StaticCache 该类具有一个在应用程序启动时缓存的供应商数据版本,可通过其 GetSuppliers() 方法访问这些数据。 若要从呈现层处理此数据,可以使用 ObjectDataSource 或以编程方式从 ASP.NET 页的代码隐藏类调用 StaticCacheGetSuppliers() 的方法。 让我们看看如何使用 ObjectDataSource 和 GridView 控件显示缓存的供应商信息。

首先打开 AtApplicationStartup.aspx 文件夹中的页面 Caching 。 将 GridView 从工具箱拖到设计器上,将其 ID 属性设置为 Suppliers。 接下来,从 GridView 的智能标记选择创建名为 SuppliersCachedDataSource 的新 ObjectDataSource。 将 ObjectDataSource 配置为使用 StaticCacheGetSuppliers() 的方法。

将 ObjectDataSource 配置为使用 StaticCache 类

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

使用 GetSuppliers() 方法检索缓存的供应商数据

图 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()方法中设置断点来验证此行为。

供应商缓存的数据展示在 GridView 网格视图中

图 7:缓存的供应商数据显示在 GridView 中(单击以查看全尺寸图像

概要

大多数数据模型都包含相当数量的静态数据,通常以查阅表格的形式实现。 由于此信息是静态的,因此每次需要显示此信息时,都无法持续访问数据库。 此外,由于静态性质,缓存数据时不需要过期。 本教程介绍了如何获取此类数据,并将其缓存在数据缓存、应用程序状态以及通过静态成员变量进行缓存。 此信息在应用程序启动时缓存,并在整个应用程序的生存期内保留在缓存中。

在本教程以及之前的两个教程中,我们已了解如何缓存应用程序生存期内的数据以及使用基于时间的过期策略。 不过,缓存数据库数据时,基于时间的到期时间可能不太理想。 最好仅在修改基础数据库数据时逐出缓存项,而不是定期刷新缓存。 此理想是可以使用 SQL 缓存依赖项实现的,我们将在下一教程中对此进行探讨。

快乐编程!

关于作者

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

特别致谢

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