Share via


在架構中快取資料 (C#)

作者 :Scott Mitchell

下載 PDF

在上一個教學課程中,我們已瞭解如何在呈現層套用快取。 在本教學課程中,我們將瞭解如何利用分層架構,在商業規則層快取數據。 我們藉由擴充架構以包含快取層來執行此動作。

簡介

如我們在上一個教學課程中所見,快取 ObjectDataSource 的數據就像設定幾個屬性一樣簡單。 可惜的是,ObjectDataSource 會在呈現層套用快取,這會將快取原則與 ASP.NET 頁面緊密結合。 建立分層架構的其中一個原因是允許這類結合中斷。 例如,商業規則層會將商業規則與 ASP.NET 頁面分離,而數據存取層會將數據存取詳細數據分離。 這項商業規則和數據存取詳細數據的分離是慣用的,因為它可讓系統更容易閱讀、更容易維護,以及更有彈性地變更。 它也允許開發人員在簡報層上工作的領域知識與部門,不需要熟悉資料庫的詳細數據,才能執行其工作。 將快取原則與呈現層分離可提供類似的優點。

在本教學課程中,我們將增強我們的架構,以包含採用快取原則的簡短) 快取 (或 CL。 快取層將包含類別ProductsCL,該類別會使用、 GetProducts()GetProductsByCategoryID(categoryID)等方法提供產品資訊的存取權,因此叫用時,會先嘗試從快取擷取數據。 如果快取是空的,這些方法會在 BLL 中叫用適當的 ProductsBLL 方法,進而從 DAL 取得數據。 方法 ProductsCL 會在傳回數據之前,先快取從 BLL 擷取的數據。

如圖 1 所示,CL 位於簡報和商業規則層之間。

快取層 (CL) 在我們的架構中是另一層

圖 1:快取層 (CL) 在我們的架構中是另一層

步驟 1:建立快取層類別

在本教學課程中,我們將使用只有少數方法的單一類別 ProductsCL 建立非常簡單的CL。 為整個應用程式建置完整的快取層需要建立 CategoriesCLEmployeesCLSuppliersCL 類別,並在 BLL 中為每個資料存取或修改方法提供這些快取層類別中的方法。 如同 BLL 和 DAL,快取層最好實作為個別的類別庫專案;不過,我們會將它實作為資料夾中的 App_Code 類別。

若要更清楚地分隔 CL 類別與 DAL 和 BLL 類別,讓我們在 App_Code 資料夾中建立新的子資料夾。 以滑鼠右鍵按下 App_Code 方案總管 中的資料夾,選擇[新增資料夾],然後將新資料夾CL命名為 。 建立此資料夾之後,將 新增至該資料夾名為 ProductsCL.cs的新類別。

新增名為 CL 的新資料夾和名為 ProductsCL.cs 的類別

圖 2:新增名為 CL 的資料夾和名為 的類別 ProductsCL.cs

類別 ProductsCL 應該包含與其對應商業規則層類別相同的一組數據存取和修改方法, (ProductsBLL) 。 讓我們在這裡建立一些方法,而不是建立所有這些方法,以瞭解CL所使用的模式。 特別是,我們會在步驟 3 中新增 和 GetProductsByCategoryID(categoryID) 方法,並在步驟 4 中新增 GetProducts()UpdateProduct載。 您可以在您的家中新增其餘 ProductsCL 的方法和 CategoriesCLEmployeesCLSuppliersCL 類別。

步驟 2:讀取和寫入數據快取

在內部教學課程中探索的 ObjectDataSource 快取功能會使用 ASP.NET 數據快取來儲存從 BLL 擷取的數據。 數據快取也可以透過程序設計方式,從 ASP.NET 頁程式代碼後置類別或 Web 應用程式架構中的類別存取。 若要從 ASP.NET 頁的程式代碼後置類別讀取和寫入數據快取,請使用下列模式:

// Read from the cache
object value = Cache["key"];
// Add a new item to the cache
Cache["key"] = value;
Cache.Insert(key, value);
Cache.Insert(key, value, CacheDependency);
Cache.Insert(key, value, CacheDependency, DateTime, TimeSpan);

類別Cache s Insert 方法具有數個多載。 Cache["key"] = valueCache.Insert(key, value) 是同義字,而且兩者都會使用指定的索引鍵將專案新增至快取,而不會有定義的到期日。 一般而言,我們想要在將專案新增至快取時指定到期日,可能是相依性、以時間為基礎的到期,或兩者。 使用其中一個其他 Insert 方法的多載來提供相依性或以時間為基礎的到期資訊。

快取層的方法必須先檢查要求的數據是否位於快取中,如果是的話,請從該處傳回它。 如果要求的數據不在快取中,則必須叫用適當的 BLL 方法。 應該快取其傳回值,然後傳回,如下列循序圖所示。

如果快取層可用,則快取層的方法會從快取傳回數據

圖 3:如果快取層可用,則快取層方法會從快取傳回數據

圖 3 中所述的順序是在 CL 類別中使用下列模式來完成:

Type instance = Cache["key"] as Type;
if (instance == null)
{
    instance = BllMethodToGetInstance();
    Cache.Insert(key, instance, ...);
}
return instance;

在這裡, Type 是儲存在快取 Northwind.ProductsDataTable中的數據類型,例如,索引鍵是可唯一識別快取專案的 索引鍵 。 如果具有指定 索引鍵 的專案不在快取中,則 實例 會是 null ,而且會從適當的 BLL 方法擷取數據,並新增至快取。 到達時 return instance實例 會包含數據參考,不論是從快取或從 BLL 提取。

從快取存取數據時,請務必使用上述模式。 下列模式一目了然,看起來相等,包含引進競爭條件的細微差異。 競爭條件很難偵錯,因為它們會偶爾顯示自己,而且難以重現。

if (Cache["key"] == null)
{
    Cache.Insert(key, BllMethodToGetInstance(), ...);
}
return Cache["key"];

第二個,不正確的代碼段差異在於,而不是將快取專案的參考儲存在局部變數中,而是直接在條件語句 return存取數據快取。 想像一下,當達到此程式碼時, Cache["key"] 不是null,但在到達 語句之前 return ,系統會從快取收回 索引鍵 。 在此罕見的情況下,程式代碼會傳 null 回值,而不是預期的型別物件。

注意

數據快取是安全線程,因此您不需要同步處理線程存取,以進行簡單的讀取或寫入。 不過,如果您需要對快取中需要不可部分完成的數據執行多個作業,您必須負責實作鎖定或其他一些機制,以確保線程安全。 如需詳細資訊 ,請參閱同步處理 ASP.NET 快取的存取

您可以使用 如下所示的方法,以程式設計方式從數據Remove快取收回專案:

Cache.Remove(key);

步驟 3:從ProductsCL類別傳回產品資訊

在本教學課程中,讓我們實作兩種方法,以從 ProductsCL 類別傳回產品資訊: GetProducts()GetProductsByCategoryID(categoryID)ProductsBL如同商業規則層中的 類別,CL 中的方法會GetProducts()以 物件的形式傳回所有產品Northwind.ProductsDataTable的相關信息,同時GetProductsByCategoryID(categoryID)從指定的類別傳回所有產品。

下列程式代碼顯示 類別中 ProductsCL 方法的一部分:

[System.ComponentModel.DataObject]
public class ProductsCL
{
    private ProductsBLL _productsAPI = null;
    protected ProductsBLL API
    {
        get
        {
            if (_productsAPI == null)
                _productsAPI = new ProductsBLL();
            return _productsAPI;
        }
    }
    
   [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, true)]
    public Northwind.ProductsDataTable GetProducts()
    {
        const string rawKey = "Products";
        // See if the item is in the cache
        Northwind.ProductsDataTable products = _
            GetCacheItem(rawKey) as Northwind.ProductsDataTable;
        if (products == null)
        {
            // Item not found in cache - retrieve it and insert it into the cache
            products = API.GetProducts();
            AddCacheItem(rawKey, products);
        }
        return products;
    }
    
    [System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Select, false)]
    public Northwind.ProductsDataTable GetProductsByCategoryID(int categoryID)
    {
        if (categoryID < 0)
            return GetProducts();
        else
        {
            string rawKey = string.Concat("ProductsByCategory-", categoryID);
            // See if the item is in the cache
            Northwind.ProductsDataTable products = _
                GetCacheItem(rawKey) as Northwind.ProductsDataTable;
            if (products == null)
            {
                // Item not found in cache - retrieve it and insert it into the cache
                products = API.GetProductsByCategoryID(categoryID);
                AddCacheItem(rawKey, products);
            }
            return products;
        }
    }
}

首先,記下 DataObject 套用至 類別和方法的 和 DataObjectMethodAttribute 屬性。 這些屬性會將資訊提供給 ObjectDataSource 精靈,指出精靈步驟中應該顯示哪些類別和方法。 由於CL類別和方法將從簡報層中的 ObjectDataSource 存取,因此我新增了這些屬性來增強設計時間體驗。 請參閱 建立商業規則層 教學課程,以取得這些屬性及其效果的更完整描述。

GetProducts()GetProductsByCategoryID(categoryID) 方法中 GetCacheItem(key) ,從方法傳回的數據會指派給局部變數。 GetCacheItem(key)我們很快就會檢查的方法會根據指定的索引鍵,從快取傳回特定專案。 如果在快取中找不到這類數據,則會從對應的 ProductsBLL 類別方法擷取,然後使用方法新增至快取 AddCacheItem(key, value)

GetCacheItem(key)AddCacheItem(key, value) 方法會分別與數據快取、讀取和寫入值介面。 方法是 GetCacheItem(key) 兩者中更簡單的方法。 它只會使用傳入的 索引鍵,從 Cache 類別傳回值:

private object GetCacheItem(string rawKey)
{
    return HttpRuntime.Cache[GetCacheKey(rawKey)];
}
private readonly string[] MasterCacheKeyArray = {"ProductsCache"};
private string GetCacheKey(string cacheKey)
{
    return string.Concat(MasterCacheKeyArray[0], "-", cacheKey);
}

GetCacheItem(key) 不會使用提供的 索引鍵 值,而是會呼叫 GetCacheKey(key) 方法,其會傳回前面加上 ProductsCache-的 密鑰 。 保存 MasterCacheKeyArrayProductsCache 字串的 ,也會由 AddCacheItem(key, value) 方法使用,因為我們會立即看到。

從 ASP.NET 頁的程式代碼後置類別中,可以使用 類別 s Cache 屬性來存取Page數據快取,並允許類似 的Cache["key"] = value語法,如步驟 2 中所述。 從架構內的類別,可以使用 或 HttpContext.Current.Cache來存取HttpRuntime.Cache數據快取。 Peter Peter 的部落格文章 HttpRuntime.Cache 與 HttpContext.Current.Cache 會記下使用 HttpRuntimeHttpContext.Current而非 的稍微效能優勢,因此會 ProductsCL 使用 HttpRuntime

注意

如果您的架構是使用類別庫項目來實作,則您必須新增元件的參考 System.Web ,才能使用 HttpRuntimeHttpContext 類別。

如果快取中找不到項目,類別 ProductsCL s 方法會從 BLL 取得數據,並使用 AddCacheItem(key, value) 方法將其新增至快取。 若要將 新增至快取,我們可以使用下列程序代碼,其使用 60 秒的到期時間:

const double CacheDuration = 60.0;
private void AddCacheItem(string rawKey, object value)
{
    HttpRuntime.Cache.Insert(GetCacheKey(rawKey), value, null, 
        DateTime.Now.AddSeconds(CacheDuration), Caching.Cache.NoSlidingExpiration);
}

DateTime.Now.AddSeconds(CacheDuration) 指定未來 60 秒的時間型到期時間,而 System.Web.Caching.Cache.NoSlidingExpiration 表示沒有滑動到期。 雖然這個 Insert 方法多載同時具有絕對和滑動到期的輸入參數,但您只能提供兩者的其中一個。 如果您嘗試同時指定絕對時間和時間範圍,方法 Insert 會擲回 ArgumentException 例外狀況。

注意

此方法的這個實作 AddCacheItem(key, value) 目前有一些缺點。 我們將在步驟 4 中解決並克服這些問題。

步驟 4:透過架構修改資料時使快取失效

除了數據擷取方法之外,快取層還需要提供與 BLL 相同的方法來插入、更新和刪除數據。 CL 的數據修改方法不會修改快取的數據,而是呼叫 BLL 對應的數據修改方法,然後使快取失效。 如上一個教學課程中所見,這是 ObjectDataSource 在啟用InsertUpdate快取功能且叫用、 或 Delete 方法時所套用的相同行為。

下列 UpdateProduct 多載說明如何在CL中實作資料修改方法:

[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
    bool result = API.UpdateProduct(productName, unitPrice, productID);
    // TODO: Invalidate the cache
    return result;
}

會叫用適當的數據修改商業規則層方法,但在傳回其回應之前,我們需要使快取失效。 可惜的是,使快取失效並不簡單,因為 ProductsCL 類別 和 GetProducts()GetProductsByCategoryID(categoryID) 方法會以不同的索引鍵將專案新增至快取,而 GetProductsByCategoryID(categoryID) 方法會為每個唯一 的 categoryID 新增不同的快取專案。

使快取失效時,我們需要移除 類別可能加入ProductsCL的所有專案。 這可以藉由將 快取相依性 與方法中 AddCacheItem(key, value) 新增至快取的每個專案產生關聯來完成。 一般而言,快取相依性可以是快取中的另一個專案、文件系統上的檔案,或來自 Microsoft SQL Server 資料庫的數據。 當相依性變更或從快取中移除時,其相關聯的快取專案會自動從快取收回。 在本教學課程中,我們想要在快取中建立其他專案,以作為透過 ProductsCL 類別新增之所有專案的快取相依性。 如此一來,只要移除快取相依性即可從快取中移除所有這些專案。

讓我們更新 AddCacheItem(key, value) 方法,讓透過此方法新增至快取的每個專案都與單一快取相依性相關聯:

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Make sure MasterCacheKeyArray[0] is in the cache - if not, add it
    if (DataCache[MasterCacheKeyArray[0]] == null)
        DataCache[MasterCacheKeyArray[0]] = DateTime.Now;
    // Add a CacheDependency
    System.Web.Caching.CacheDependency dependency = 
        new CacheDependency(null, MasterCacheKeyArray);
    DataCache.Insert(GetCacheKey(rawKey), value, dependency, 
        DateTime.Now.AddSeconds(CacheDuration), 
        System.Web.Caching.Cache.NoSlidingExpiration);
}

MasterCacheKeyArray 是保存單一值 ProductsCache 的字串數位。 首先,快取專案會新增至快取,並指派目前的日期和時間。 如果快取專案已經存在,則會更新它。 接下來,會建立快取相依性。 類別CacheDependency的建構函式有一些多載,但此處所使用的多載需要兩string個陣列輸入。 第一個檔案會指定要作為相依性的一組檔案。 因為我們不想使用任何以檔案為基礎的相依性,所以的值 null 會用於第一個輸入參數。 第二個輸入參數會指定要作為相依性的快取索引鍵集合。 在這裡,我們會指定我們的單一相依性 。 MasterCacheKeyArray 接著會 CacheDependency 傳遞至 Insert 方法。

對進行這項修改 AddCacheItem(key, value)後,讓快取失效就如同移除相依性一樣簡單。

[System.ComponentModel.DataObjectMethodAttribute(DataObjectMethodType.Update, false)]
public bool UpdateProduct(string productName, decimal? unitPrice, int productID)
{
    bool result = API.UpdateProduct(productName, unitPrice, productID);
    // Invalidate the cache
    InvalidateCache();
    return result;
}
public void InvalidateCache()
{
    // Remove the cache dependency
    HttpRuntime.Cache.Remove(MasterCacheKeyArray[0]);
}

步驟 5:從呈現層呼叫快取層

快取層的類別和方法可用來使用我們在這些教學課程中檢查的技術來處理數據。 為了說明使用快取的數據,請將變更儲存至 ProductsCL 類別,然後在資料夾中開啟 FromTheArchitecture.aspx 頁面 Caching ,然後新增 GridView。 從 GridView 的智慧標記中,建立新的 ObjectDataSource。 在精靈的第一個步驟中,您應該會看到 ProductsCL 類別作為下拉式清單中的其中一個選項。

ProductsCL 類別包含在商務物件 Drop-Down 清單中

圖 4:類別 ProductsCL 包含在商務物件 Drop-Down 清單 (按兩下以檢視完整大小的影像)

選取 ProductsCL之後,按 [下一步]。 SELECT 索引標籤中的下拉式清單有兩個專案, GetProducts()GetProductsByCategoryID(categoryID) UPDATE 索引標籤則具有唯一 UpdateProduct 的多載。 GetProducts()從 SELECT 索引標籤選擇 方法,並從 UpdateProducts [更新] 索引標籤選擇 方法,然後按兩下 [完成]。

ProductsCL 類別的 方法列於 Drop-Down 清單

圖 5:類別 ProductsCL s 方法列在 Drop-Down 清單 (按兩下以檢視大小完整的影像)

完成精靈之後,Visual Studio 會將 ObjectDataSource s OldValuesParameterFormatString 屬性設定為 original_{0} ,並將適當的字段新增至 GridView。 將 OldValuesParameterFormatString 屬性變更回其預設值 , {0}並將 GridView 設定為支援分頁、排序和編輯。 UploadProducts由於 CL 所使用的多載只接受已編輯的產品名稱和價格,因此限制 GridView,以便只編輯這些欄位。

在上述教學課程中,我們定義了 GridView 以包含 、 CategoryNameUnitPrice 欄位的ProductName欄位。 請隨意復寫此格式和結構,在此情況下,GridView 和 ObjectDataSource 的宣告式標記看起來應該如下所示:

<asp:GridView ID="Products" runat="server" AutoGenerateColumns="False" 
    DataKeyNames="ProductID" DataSourceID="ProductsDataSource" 
    AllowPaging="True" AllowSorting="True">
    <Columns>
        <asp:CommandField ShowEditButton="True" />
        <asp:TemplateField HeaderText="Product" SortExpression="ProductName">
            <EditItemTemplate>
                <asp:TextBox ID="ProductName" runat="server" 
                    Text='<%# Bind("ProductName") %>' />
                <asp:RequiredFieldValidator ID="RequiredFieldValidator1"
                    ControlToValidate="ProductName" Display="Dynamic" 
                    ErrorMessage="You must provide a name for the product." 
                    SetFocusOnError="True"
                    runat="server">*</asp:RequiredFieldValidator>
            </EditItemTemplate>
            <ItemTemplate>
                <asp:Label ID="Label2" runat="server" 
                    Text='<%# Bind("ProductName") %>'></asp:Label>
            </ItemTemplate>
        </asp:TemplateField>
        <asp:BoundField DataField="CategoryName" HeaderText="Category" 
            ReadOnly="True" SortExpression="CategoryName" />
        <asp:TemplateField HeaderText="Price" SortExpression="UnitPrice">
            <EditItemTemplate>
                $<asp:TextBox ID="UnitPrice" runat="server" Columns="8" 
                    Text='<%# Bind("UnitPrice", "{0:N2}") %>'></asp:TextBox>
                <asp:CompareValidator ID="CompareValidator1" runat="server" 
                    ControlToValidate="UnitPrice" Display="Dynamic" 
                    ErrorMessage="You must enter a valid currency value with 
                        no currency symbols. Also, the value must be greater than 
                        or equal to zero."
                    Operator="GreaterThanEqual" SetFocusOnError="True" 
                    Type="Currency" ValueToCompare="0">*</asp:CompareValidator>
            </EditItemTemplate>
            <ItemStyle HorizontalAlign="Right" />
            <ItemTemplate>
                <asp:Label ID="Label1" runat="server" 
                    Text='<%# Bind("UnitPrice", "{0:c}") %>' />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>
<asp:ObjectDataSource ID="ProductsDataSource" runat="server" 
    OldValuesParameterFormatString="{0}" SelectMethod="GetProducts" 
    TypeName="ProductsCL" UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

此時,我們有使用快取層的頁面。 若要查看作用中的快取,請在 類別和 GetProducts()UpdateProduct 方法中ProductsCL設定斷點。 瀏覽瀏覽器中的頁面,並在排序和分頁時逐步執行程式碼,以查看從快取提取的數據。 然後更新記錄並注意快取已失效,因此,當數據重新系結至 GridView 時,會從 BLL 擷取它。

注意

本文隨附的下載中提供的快取層尚未完成。 它只包含一個類別, ProductsCL它只會運動少數方法。 此外,只有單一 ASP.NET 頁面會使用CL () ~/Caching/FromTheArchitecture.aspx 所有其他頁面仍直接參考 BLL。 如果您打算在應用程式中使用CL,則表示層的所有呼叫都應該移至CL,這需要CL類別和方法涵蓋目前由簡報層使用的BLL中使用的類別和方法。

摘要

雖然快取可以在具有 ASP.NET 2.0 s SqlDataSource 和 ObjectDataSource 控件的表示層套用快取,但理想情況下,快取責任會委派給架構中的個別層。 在本教學課程中,我們建立了位於呈現層與商業規則層之間的快取層。 快取層必須提供存在於 BLL 中的相同類別和方法集,並從表示層呼叫。

我們在本文中探索的快取層範例,以及先前的教學課程示範 了回應式載入。 使用回應式載入時,只有在提出數據要求且快取中遺漏該數據時,才會將數據載入快取中。 數據也可以 主動載入 快取,這是一種技術,可將數據載入快取,然後才實際需要。 在下一個教學課程中,我們將會在查看如何在應用程式啟動時將靜態值儲存到快取時,看到主動式載入的範例。

快樂的程序設計!

關於作者

Scott Mitchell 是七份 ASP/ASP.NET 書籍的作者,以及 1998 年以來與 Microsoft Web 技術合作的 4GuysFromRolla.com 作者。 Scott 是獨立顧問、訓練員和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格來連線到 ,您可以在 找到http://ScottOnWriting.NET

特別感謝

本教學課程系列是由許多實用的檢閱者檢閱。 本教學課程的首席檢閱者是 Teresa Murph。 有興趣檢閱即將推出的 MSDN 文章嗎? 如果是,請將一行 mitchell@4GuysFromRolla.com放在 。