共用方式為


在應用程式啟動時快取資料 (C#)

作者:Scott Mitchell

下載 PDF

在任何 Web 應用程式中,有些資料會被頻繁使用,有些資料會不常使用。 我們可以透過提前載入常用資料 (一種稱為快取的技術) 來提高 ASP.NET 應用程式的效能。 本教學課程示範了一種主動載入的方法,即在應用程式啟動時將資料載入到快取中。

簡介

前兩個教學介紹了在表示層和快取層中快取資料。 在使用 ObjectDataSource 快取資料中,我們研究如何使用 ObjectDataSource 的快取功能在表示層中快取資料。 在架構中快取資料檢查了新的、單獨的快取層中的快取。 這兩個教學課程都在處理資料快取時使用了反應式載入。 透過反應式加載,每次請求資料時,系統首先檢查它是否在快取中。 如果沒有,它會從原始來源 (例如資料庫) 獲取資料,然後將其儲存在快取中。 反應式載入的主要優點是易於實現。 它的缺點之一是跨請求的效能不均。 想像一個頁面使用前面教學中的快取層來顯示產品資訊。 當第一次造訪該頁面時,或在因記憶體限制或達到指定的過期時間而清除快取資料後第一次存取該頁面時,必須從資料庫中擷取資料。 因此,這些使用者請求將比快取可以服務的使用者請求花費更長的時間。

主動載入提供了另一種快取管理策略,透過在需要之前載入快取資料來平滑請求之間的效能。 通常,主動載入使用一些流程來定期檢查或在底層資料有更新時收到通知。 然後,此過程會更新快取以保持最新狀態。 如果底層資料來自緩慢的資料庫連結、Web 服務或其他一些特別緩慢的資料來源,主動載入尤其有用。 但這種主動載入方法更難實現,因為它需要建立、管理和部署一個流程來檢查變更並更新快取。

主動加載的另一種風格,以及我們將在本教學課程中探討的類別型,是在應用程式啟動時將資料載入到快取中。 此方法對於快取靜態資料 (例如資料庫查找表中的記錄) 特別有用。

注意

若要更深入了解主動載入和被動式載入之間的差異,以及優缺點和實作建議列表,請參閱 .NET Framework 應用程式快取體系結構指南管理快取內容部分

步驟 1:確定應用程式啟動時要快取的資料

我們在前兩個教學課程中研究的使用反應式加載的快取範例可以很好地處理可能定期更改的資料,並且生成時間不會太長。 但如果快取的資料永遠不會改變,那麼反應式載入所使用的過期時間就是多餘的。 同樣,如果快取的資料需要很長時間才能生成,那麼那些請求發現快取為空的使用者將不得不在擷取底層資料時忍受漫長的等待。 考慮快取靜態資料和在應用程式啟動時需要很長時間才能產生的資料。

雖然資料庫有許多動態的、經常變化的值,但大多數資料庫也有相當數量的靜態資料。 例如,幾乎所有資料模型都有一個或多個列,其中包含一組固定選擇中的特定值。 Patients 資料庫表可能有一 PrimaryLanguage 列,其值集可以是英語、西班牙語、法語、俄語、日語等。 通常,這些類別型的列是使用查找表來實現的。 不是在表中儲存字串英語或法語,而是建立第二個 Patients 表,該表通常具有兩個欄位 - 唯一識別碼和字串描述 - 以及每個可能值的記錄。 PrimaryLanguage 表中的列儲存查找 Patients 表中對應的唯一識別碼。 在圖 1 中,患者 John Doe 的主要語言是英語,而 Ed Johnson 的主要語言是俄語。

語言表是患者表使用的查找表

圖 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 來快取資料,提供基於時間和依賴項的過期機制、快取項目優先權等等。 對於靜態成員和應用程式狀態,此類別功能必須由頁面開發人員手動新增。 然而,當在應用程式啟動時在應用程式的生命週期內快取資料時,資料快取的優勢就毫無意義了。 在本教學課程中,我們將研究使用所有三種技術來快取靜態資料的程式碼。

第三步:快取 Suppliers 表資料

我們迄今為止實作的 Northwind 資料庫表不包括任何傳統的查找表。 在我們的 DAL 中實作的四個 DataTable 都是模型表,其值都是非靜態的。 在本教學課程中,我們假設 Suppliers 表格的資料是靜態的,而不是花時間在 DAL 中新增新的 DataTable,然後在 BLL 中新增的類別和方法。 因此,我們可以在應用程式啟動時快取這些資料。

首先,建立一個在 CL 資料夾中命名 StaticCache.cs 的新類別。

在 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() 方法應在應用程式啟動期間調用。 一旦在應用程式啟動時載入了此資料,任何需要使用供應商資料的頁面都可以呼叫 StaticCache 類別的 GetSuppliers() 方法。 因此,調用資料庫來獲取供應商僅在應用程式啟動時發生一次。

我們可以選擇使用應用程式狀態或資料快取,而不是使用靜態成員變數作為快取儲存。 以下程式碼顯示了重新設計以使用應用程式狀態的類別:

[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。 該檔案可以包含應用程式層級、會話層級和請求級事件的事件處理程序,我們可以在其中新增在應用程式啟動時執行的程式碼。

透過右鍵點擊 Visual Studio 的解決方案資源管理器中的網站專案名稱並選擇新增項目,將 Global.asax 檔案新增至 Web 應用程式的根目錄。 從「新增項目」對話方塊中,選擇「全域應用程式類別」項目類別型,然後按一下「新增」按鈕。

注意

如果您的專案中已有 Global.asax 檔案,則「新增項目」對話方塊中將不會列出「全域應用程式類別」項目類別型。

將 Global.asax 檔案新增至 Web 應用程式的根目錄

圖 3:將 Global.asax 檔案新增至 Web 應用程式的根目錄 (按一下檢視全尺寸影像)

預設 Global.asax 檔案範本在伺服器端 <script> 標記中包含五個方法:

  • 在 Web 應用程式首次啟動時執行 Application_Start
  • 當應用程式關閉時執行 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 頁面的程式碼隱藏類別中呼叫 StaticCache 類別的 GetSuppliers() 方法。 讓我們看看如何使用 ObjectDataSource 和 GridView 控制項來顯示快取的供應商資訊。

首先打開 Caching 資料夾中的 AtApplicationStartup.aspx 頁面。 將 GridView 從工具箱拖曳到設計器上,將 ID 屬性設為 Suppliers。 接下來,從 GridView 的智慧標記中選擇建立一個名為 SuppliersCachedDataSource 的新 ObjectDataSource。 設定 ObjectDataSource 以使用 StaticCache 類別的 GetSuppliers() 方法。

設定 ObjectDataSource 以使用 StaticCache 類別

圖 5:設定 ObjectDataSource 以使用 StaticCache 類別 (按一下查看全尺寸影像)

使用 GetSuppliers() 方法擷取快取的供應商資料

圖 6:使用 GetSuppliers() 方法擷取快取的供應商資料 (點擊看大圖)

完成精靈後,Visual Studio 將自動為 SuppliersDataTable。 您的 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 快取依賴項可以實現這一理想,我們將在下一個教學課程中對此進行研究。

快樂程式!

關於作者

Scott Mitchell 是七本 ASP/ASP.NET 書籍的作者和 4GuysFromRolla.com 的創始人,自 1998 年以來一直致力於 Microsoft Web 技術。 史考特是一名獨立顧問、培訓師和作家。 他的最新著作是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 您可以撥打 mitchell@4GuysFromRolla.com 聯絡他。或者透過他的部落格, http://ScottOnWriting.NET部落格可以在以下位置找到。

特別感謝

本教學系列得到了許多有用的審閱者的審閱。 本教學的主要審閱者是 Teresa Murphy 和 Zack Jones。 有興趣查看我即將發表的 MSDN 文章嗎? 如果是這樣,請留言給我 mitchell@4GuysFromRolla.com