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

作者 :Scott Mitchell

下载 PDF

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

简介

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

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

主动加载的另一种风格(即本教程将探讨的类型)是在应用程序启动时将数据加载到缓存中。 此方法对于缓存静态数据(例如数据库查找表中的记录)特别有用。

注意

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

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

我们在前两篇教程中介绍的使用反应式加载的缓存示例适用于可能会定期更改且生成时间过长的数据。 但是,如果缓存的数据永远不会更改,则被动加载使用的过期时间是多余的。 同样,如果正在缓存的数据需要很长时间才能生成,则那些请求发现缓存为空的用户在检索基础数据时必须经历长时间的等待。 请考虑缓存静态数据,以及应用程序启动时需要很长时间才能生成的数据。

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

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

图 1:表 Languages 是表使用的 Patients 查阅表格

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

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

在本教程中,我们将了解如何缓存查找表数据和其他静态信息。

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

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

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

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

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

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

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

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

步骤 3:缓存Suppliers表数据

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

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

在 CL 文件夹中创建 StaticCache.cs 类

图 2:在StaticCache.cs文件夹中创建类CL

我们需要添加一个方法,用于在启动时将数据加载到相应的缓存存储中,以及从此缓存返回数据的方法。

[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来保存类的 GetSuppliers() 方法的结果SuppliersBLL,该方法从 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()) 。 虽然可以使用 在 ASP.NET 页 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.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,只需调用 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 执行 方法。

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

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

注意

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

步骤 5:显示缓存的数据

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

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

将 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 类返回在应用程序启动时缓存的供应商数据。 可以在 类的 GetSuppliers() 方法中StaticCache设置断点来验证此行为。

缓存的供应商数据显示在 GridView 中

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

总结

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

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

编程快乐!

关于作者

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

特别感谢

本教程系列由许多有用的审阅者审阅。 本教程的主要审阅者是特蕾莎·墨菲和扎克·琼斯。 有兴趣查看我即将发布的 MSDN 文章? 如果是,请在 处mitchell@4GuysFromRolla.com放置一行。