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

by Scott Mitchell

下載 PDF

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

簡介

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

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

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

注意

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

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

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

雖然資料庫有許多動態的、經常變化的值,但大多數資料庫也有相當數量的靜態資料。 例如,幾乎所有資料模型都有一個或多個列,其中包含一組固定選擇中的特定值。 一個 Patients 資料庫表可能有一個 PrimaryLanguage 列,其值集可以是英語、西班牙語、法語、俄語、日語等。 通常,這些類型的欄位是使用查找表來實現的。 與其在Patients表中儲存字串「英語」或「法語」,不如建立第二個表,該表通常具有兩個欄位 - 唯一識別碼和字串描述 - 並為每個可能值建立一筆記錄。 PrimaryLanguage 列在 Patients 表中儲存查找表內的對應唯一識別碼。 在圖 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 關鍵字建立該類別的實例。 SomeMethod 和 SomeProperty 與特定實例相關聯。 這些成員的生命週期與其關聯物件的生命週期有關。 另一方面,靜態成員是在類別的所有實例之間共享的變數、屬性和方法,因此其生命週期與類別一樣長。 靜態成員由關鍵字 static 表示。

除了靜態成員之外,還可以使用應用程式狀態來快取資料。 每個 ASP.NET 應用程式都維護一個在應用程式的所有使用者和頁面之間共享的名稱/值集合。 可以使用 HttpContext 類別 的 Application 屬性來存取此集合 ,並從 ASP.NET 頁面的程式碼後置類別中使用該集合,如下所示:

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

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

第三步:快取Suppliers表資料

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

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

在 CL 資料夾中建立 StaticCache.cs 類別

圖 2:在 資料夾中建立 類別

我們需要添加一個在啟動時將資料載入到適當的快取儲存中的方法,以及從該快取返回資料的方法。

[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() 中,供應商資訊儲存到應用程式變數key中。 它作為適當的型別 (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.NoAbsoluteExpiration 和 System.Web.Caching.Cache.NoSlidingExpiration 值作為輸入參數。 選擇資料快取的 Insert 方法的這個特定重載是為了讓我們可以指定快取項目的 。 優先權用於確定當可用記憶體不足時從快取中清除哪些項目。 這裡我們使用優先級 NotRemovable,它確保該快取項目不會被清除。

注意

本教程的下載實作StaticCache類別,採用靜態成員變數方法。 應用程式狀態和資料快取技術的程式碼可在類別文件的註解中找到。

第 4 步:在應用程式啟動時執行程式碼

為了在 Web 應用程式首次啟動時執行程式碼,我們需要建立一個名為 Global.asax 的特殊檔案。 該檔案可以包含應用程式層級、會話層級和請求級事件的事件處理程序,我們可以在其中新增在應用程式啟動時執行的程式碼。

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

注意

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

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

圖 3:將 檔案新增至 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 斷點,那是因為您的應用程式已經啟動。 強制應用程式重新啟動,方式是修改您的 Global.asax 或 Web.config 檔案,然後重試。 您只需在這些文件之一的末尾添加 (或刪除) 一個空白行即可快速重新啟動應用程式。

步驟5:顯示快取資料

此時,StaticCache 類別具有在應用程式啟動時快取的供應商資料版本,可以透過其 GetSuppliers() 方法進行存取。 要在表示層中使用這些資料,我們可以使用 ObjectDataSource,或以程式設計方式從 ASP.NET 頁面的程式碼後置類別中呼叫 StaticCache 類別的 GetSuppliers() 方法。 讓我們看看如何使用 ObjectDataSource 和 GridView 控制項來顯示快取的供應商資訊。

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

設定 ObjectDataSource 以使用 StaticCache 類別

圖 5:設定 ObjectDataSource 以使用 類別 (按一下以查看完整尺寸的圖像)

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

圖 6:使用 方法擷取快取的供應商資料 (點擊以查看完整圖像)

完成精靈後,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() 方法中設定斷點來驗證此行為。

The Cached Supplier Data is Displayed in a GridView快取的供應商資料顯示在 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給我留言。

上一個 下一個