在架構中快取資料 (VB)

作者: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.vb的新類別。

新增名為 CL 的新資料夾,以及名為 ProductsCL.vb 的類別

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

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

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

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

' Read from the cache
Dim value as Object = 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 類別中完成:

Dim instance As Type = TryCast(Cache("key"), Type)
If instance Is Nothing Then
    instance = BllMethodToGetInstance()
    Cache.Insert(key, instance, ...)
End If
Return instance

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

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

If Cache("key") Is Nothing Then
    Cache.Insert(key, BllMethodToGetInstance(), ...)
End If
Return Cache("key")

第二個代碼段的差異是,數據快取會直接在條件語句 中存取,而不是將快取專案的參考儲存在局部變數中 Return。 假設達到此程式代碼時,不是 NothingCache("key")但在到達 語句之前Return,系統會從快取收回索引鍵。 在這個罕見的情況下,程式代碼會傳回 Nothing ,而不是預期的型別物件。

注意

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

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

Cache.Remove(key)

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

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

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

<System.ComponentModel.DataObject()> _
Public Class ProductsCL
    Private _productsAPI As ProductsBLL = Nothing
    Protected ReadOnly Property API() As ProductsBLL
        Get
            If _productsAPI Is Nothing Then
                _productsAPI = New ProductsBLL()
            End If
            Return _productsAPI
        End Get
    End Property
    <System.ComponentModel.DataObjectMethodAttribute _
    (DataObjectMethodType.Select, True)> _
    Public Function GetProducts() As Northwind.ProductsDataTable
        Const rawKey As String = "Products"
        ' See if the item is in the cache
        Dim products As Northwind.ProductsDataTable = _
            TryCast(GetCacheItem(rawKey), Northwind.ProductsDataTable)
        If products Is Nothing Then
            ' Item not found in cache - retrieve it and insert it into the cache
            products = API.GetProducts()
            AddCacheItem(rawKey, products)
        End If
        Return products
    End Function
    <System.ComponentModel.DataObjectMethodAttribute _
        (DataObjectMethodType.Select, False)> _
    Public Function GetProductsByCategoryID(ByVal categoryID As Integer) _
        As Northwind.ProductsDataTable
        If (categoryID < 0) Then
            Return GetProducts()
        Else
            Dim rawKey As String = String.Concat("ProductsByCategory-", categoryID)
            ' See if the item is in the cache
            Dim products As Northwind.ProductsDataTable = _
                TryCast(GetCacheItem(rawKey), Northwind.ProductsDataTable)
            If products Is Nothing Then
                ' Item not found in cache - retrieve it and insert it into the cache
                products = API.GetProductsByCategoryID(categoryID)
                AddCacheItem(rawKey, products)
            End If
            Return products
        End If
    End Function
End Class

首先,請注意 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 Function GetCacheItem(ByVal rawKey As String) As Object
    Return HttpRuntime.Cache(GetCacheKey(rawKey))
End Function
Private ReadOnly MasterCacheKeyArray() As String = {"ProductsCache"}
Private Function GetCacheKey(ByVal cacheKey As String) As String
    Return String.Concat(MasterCacheKeyArray(0), "-", cacheKey)
End Function

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

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

注意

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

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

Const CacheDuration As Double = 60.0
Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    DataCache.Insert(GetCacheKey(rawKey), value, Nothing, _
        DateTime.Now.AddSeconds(CacheDuration), _
        System.Web.Caching.Cache.NoSlidingExpiration)
End Sub

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

注意

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

步驟 4:透過架構修改數據時,使快取失效

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

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

<DataObjectMethodAttribute(DataObjectMethodType.Update, False)> _
Public Function UpdateProduct(productName As String, _
    unitPrice As Nullable(Of Decimal), productID As Integer) _
    As Boolean
    Dim result As Boolean = API.UpdateProduct(productName, unitPrice, productID)
    ' TODO: Invalidate the cache
    Return result
End Function

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

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

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

Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
    Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
    ' Make sure MasterCacheKeyArray[0] is in the cache - if not, add it
    If DataCache(MasterCacheKeyArray(0)) Is Nothing Then
        DataCache(MasterCacheKeyArray(0)) = DateTime.Now
    End If
    ' Add a CacheDependency
    Dim dependency As New Caching.CacheDependency(Nothing, MasterCacheKeyArray) _
        DataCache.Insert(GetCacheKey(rawKey), value, dependency, _
        DateTime.Now.AddSeconds(CacheDuration), _
        System.Web.Caching.Cache.NoSlidingExpiration)
End Sub

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

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

<DataObjectMethodAttribute(DataObjectMethodType.Update, False)> _
Public Function UpdateProduct(ByVal productName As String, _
    ByVal unitPrice As Nullable(Of Decimal), ByVal productID As Integer) _
    As Boolean
    Dim result As Boolean = API.UpdateProduct(productName, unitPrice, productID)
    ' Invalidate the cache
    InvalidateCache()
    Return result
End Function
Public Sub InvalidateCache()
    ' Remove the cache dependency
    HttpRuntime.Cache.Remove(MasterCacheKeyArray(0))
End Sub

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

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

ProductsCL 類別包含在 Business Object Drop-Down List 中

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

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

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

圖 5:類別ProductsCL的 方法列在 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>

此時,我們有一個使用快取層的頁面。 若要查看作用中的快取,請在 類別 s 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 是 1998 年以來,1998 年與 Microsoft Web 技術合作的 篇 ASP/ASP.NET 書籍和 4GuysFromRolla.com 作者。 Scott 是獨立的顧問、訓練者和作者。 他的最新書籍是 Sams 在 24 小時內自行 ASP.NET 2.0。 您可以透過mitchell@4GuysFromRolla.com部落格連到,也可以透過其部落格來存取,網址為 http://ScottOnWriting.NET

特別感謝

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