共用方式為


使用 SQL 快取相依性 (C#)

作者:Scott Mitchell

下載 PDF

最簡單的快取策略是允許快取的資料在指定的時間段後過期。 雖然可以使用 ASP.NET 2.0 SqlDataSource 和 ObjectDataSource 控制項在表示層套用快取,但理想情況下,將快取職責委託給體系結構中的單獨層。 更好的方法是使用 SqlCacheDependency 類別,以便資料保持快取狀態,直到其基礎資料在 SQL 資料庫中修改為止。 本教學課程會完整說明。

簡介

資料和 AI使用 ObjectDataSource 快取資料和架構教學課程中的快取資料中介紹的快取技術使用基於時間的過期時間在指定時間段後從快取中逐出資料。 這種方法是平衡快取效能增益和資料陳舊性的最簡單方法。 透過選擇 x 秒的時間到期時間,頁面開發人員承認僅享受快取 x 秒的效能優勢,但可以放心,她的資料永遠不會過時超過最大 x 秒。 當然,對於靜態資料,x 可以擴展到 Web 應用程式的生命週期,如應用程式啟動時快取資料教學中所述。

當快取資料庫資料時,通常會選擇基於時間的到期時間,因為它易於使用,但這通常是一個不充分的解決方案。 理想情況下,資料庫資料將保持快取狀態,直到資料庫中的基礎資料被修改為止;只有這樣快取才會被驅逐。 這種方法最大限度地提高了快取的效能優勢,並最大限度地縮短了陳舊資料的持續時間。 然而,為了享受這些好處,必須有某種系統知道底層資料庫資料何時被修改並從快取中逐出相應的項目。 在 ASP.NET 2.0 之前,頁面開發人員負責實作此系統。

ASP.NET 2.0 提供了一個 SqlCacheDependency 類別 和必要的基礎結構來確定資料庫中何時發生更改,以便可以逐出相應的快取項目。 有兩種技術可用於確定基礎資料何時發生變更:通知和輪詢。 在討論了通知和輪詢之間的差異之後,我們將建立支援輪詢所需的基礎架構,然後探索如何在聲明性和程式設計方案中使用 SqlCacheDependency 類別。

了解通知和輪詢

有兩種技術可用於確定資料庫中的資料何時被修改:通知和輪詢。 透過通知,當自上次執行查詢以來特定查詢的結果發生變更時,資料庫會自動向 ASP.NET 執行時發出警報,此時與該查詢關聯的快取項目將會逐出。 透過輪詢,資料庫伺服器可以維護有關特定表上次更新時間的資訊。 ASP.NET 執行時會定期輪詢資料庫,以檢查哪些表在進入快取後發生了變更。 資料已被修改的那些表的關聯快取項目將會被逐出。

通知選項比輪詢需要更少的設置,並且更細粒度,因為它追蹤查詢級別而不是表級別的更改。 不幸的是,通知僅在 Microsoft SQL Server 2005 的完整版本 (即非 Express 版本) 中可用。 但是,輪詢選項可用於從 7.0 到 2005 的所有版本的 Microsoft SQL Server。 由於這些教學課程使用 SQL Server 2005 Express 版本,因此我們將重點介紹如何設定和使用輪詢選項。 有關 SQL Server 2005 通知功能的更多資源,請參閱本教學最後的進一步閱讀部分。

透過輪詢,資料庫必須設定為包含一個名為 AspNet_SqlCacheTablesForChangeNotification 的表,該表具有三列 tableNamenotificationCreatedchangeId。 此表包含每個表的一行,其中包含可能需要在 Web 應用程式的 SQL 快取依賴項中使用的資料。 此 tableName 列指定表的名稱,同時 notificationCreated 指示將行新增至表中的日期和時間。 changeId 列的類別型 int 為 0,初始值為 0。 它的值隨著對資料表的每次修改而增加。

除了 AspNet_SqlCacheTablesForChangeNotification 表格之外,資料庫還需要在 SQL 快取依賴項中可能出現的每個表上包含觸發器。 每當插入、更新或刪除資料列並增加 AspNet_SqlCacheTablesForChangeNotification 中的 changeId 值時,就會執行這些觸發器。

當使用 SqlCacheDependency 物件快取資料時,ASP.NET 執行時會追蹤表的目前 changeId 情況。 定期檢查資料庫,並逐出與資料庫中的值 changeId 不同的任何 SqlCacheDependency 物件,因為不同的 changeId 值表示自快取資料以來表已發生變更。

第 1 步:探索aspnet_regsql.exe命令列程序

使用輪詢方法時,資料庫必須設定為包含上述基礎結構:一個預定義表 (AspNet_SqlCacheTablesForChangeNotification)、一些預存程序以及每個表上可用於 Web 應用程式中 SQL 快取依賴項的觸發器。 這些資料表、預存程序 aspnet_regsql.exe 和觸發器可以透過 $WINDOWS$\Microsoft.NET\Framework\version 資料夾中的命令列程式建立。 若要建立 AspNet_SqlCacheTablesForChangeNotification 資料表和關聯的預存程序,請從命令列執行以下命令:

/* For SQL Server authentication... */
aspnet_regsql.exe -S server -U user -P password -d database -ed
/* For Windows Authentication... */
aspnet_regsql.exe -S server -E -d database -ed

注意

若要執行這些指令,指定的資料庫登入名稱必須處於 db_securityadmindb_ddladmin 角色中。

例如,若要將輪詢基礎架構新增至 pubs 使用 Windows 驗證命名的 ScottsServer 資料庫伺服器上指定的 Microsoft SQL Server 資料庫,請導覽至對應的目錄,然後從命令列輸入:

aspnet_regsql.exe -S ScottsServer -E -d pubs -ed

在新增資料庫層級基礎架構後,我們需要將觸發器新增到將在 SQL 快取依賴項中使用的表。 再次使用 aspnet_regsql.exe 命令列程序,但使用 -t 開關指定表名,而不是使用 -ed 開關使用 -et,如下所示:

/* For SQL Server authentication... */
aspnet_regsql.exe -S <i>server</i>
-U <i>user</i> -P <i>password</i> -d <i>database</i> -t <i>tableName</i> -et
/* For Windows Authentication... */
aspnet_regsql.exe -S <i>server</i>
-E -d <i>database</i> -t <i>tableName</i> -et

若要將觸發器新增至 ScottsServerpubs 資料庫的 authorstitles 表中,請使用:

aspnet_regsql.exe -S ScottsServer -E -d pubs -t authors -et
aspnet_regsql.exe -S ScottsServer -E -d pubs -t titles -et

對於本教學課程,將觸發器新增至 ProductsCategoriesSuppliers 表格中。 我們將在步驟 3 中查看特定的命令列語法。

步驟 2:引用 App_Data 中 Microsoft SQL Server 2005 Express Edition 資料庫

aspnet_regsql.exe 命令列程式需要資料庫和伺服器名稱才能新增必要的輪詢基礎架構。 但是,駐留在 App_Data 資料夾中的 Microsoft SQL Server 2005 Express 資料庫的資料庫和伺服器名稱是什麼? 我發現最簡單的方法是將 localhost\SQLExpress 資料庫附加到資料庫實例並使用 SQL Server Management Studio 重命名資料,而不必找出資料庫和伺服器名稱。 如果您的電腦上安裝了 SQL Server 2005 的完整版本之一,那麼您的電腦上可能已經安裝了 SQL Server Management Studio。 如果您只有 Express 版本,則可以下載免費的 Microsoft SQL Server Management Studio Express 版本。

首先關閉 Visual Studio。 接下來,開啟 SQL Server Management Studio 並選擇使用 Windows 驗證連線到 localhost\SQLExpress 伺服器。

連線到 localhost\SQLExpress Server

圖 1localhost\SQLExpress 連接到伺服器

連接到伺服器後,Management Studio 將顯示伺服器並包含資料庫、安全性等的子資料夾。 右鍵點擊資料庫資料夾並選擇附加選項。 這將開啟「附加資料庫」對話框 (請參閱圖 2)。 按一下新增按鈕並選擇 Web 應用程式 App_Data 資料夾中的 NORTHWND.MDF 資料庫資料夾。

從 App_Data 資料夾附加 NORTHWND.MDF 資料庫

圖 2:從 App_Data 資料夾附加 NORTHWND.MDF 資料庫 (按一下查看全尺寸影像)

這會將資料庫新增至資料庫資料夾。 資料庫名稱可能是資料庫檔案的完整路徑或前面帶有 GUID 的完整路徑。 為了避免在使用 aspnet_regsql.exe 命令列工具時必須輸入冗長的資料庫名稱,請右鍵點擊剛剛附加的資料庫並選擇重新命名,將資料庫重新命名為更人性化的名稱。 我已將資料庫重新命名為 DataTutorials 。

將附加資料庫重新命名為更人性化的名稱

圖 3:將附加資料庫重新命名為更人性化的名稱

步驟 3:將輪詢基礎結構新增至 Northwind 資料庫

現在我們已經從 App_Data 資料夾附加了 NORTHWND.MDF 資料庫,我們準備添加輪詢基礎結構。 假設您已將資料庫重新命名為 DataTutorials,請執行以下四個命令:

aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -ed
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Products -et
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Categories -et
aspnet_regsql.exe -S localhost\SQLExpress -E -d DataTutorials -t Suppliers -et

執行這四個命令後,右鍵點擊 Management Studio 中的資料庫名稱,轉到「任務」子選單,然後選擇「分離」。 然後關閉 Management Studio 並重新開啟 Visual Studio。

Visual Studio 重新開啟後,透過伺服器資源管理器深入了解資料庫。 請注意新表 (AspNet_SqlCacheTablesForChangeNotification)、新預存程序以及 ProductsCategoriesSuppliers 表上的觸發器。

資料庫現在包括必要的輪詢基礎結構

圖 4:資料庫現在包含必要的輪詢基礎結構

步驟 4:設定輪詢服務

在資料庫中建立所需的表、觸發器和預存程序後,最後一步是設定輪詢服務,這是透過指定要使用的資料庫和輪詢頻率 (以毫秒為單位) 來完成 Web.config。 以下標記每秒輪詢一次 Northwind 資料庫。

<?xml version="1.0"?>
<configuration>
   <connectionStrings>
      <add name="NORTHWNDConnectionString" connectionString=
          "Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\NORTHWND.MDF;
           Integrated Security=True;User Instance=True" 
           providerName="System.Data.SqlClient"/>
   </connectionStrings>
   <system.web>
      ...
      <!-- Configure the polling service used for SQL cache dependencies -->
      <caching>
         <sqlCacheDependency enabled="true" pollTime="1000" >
            <databases>
               <add name="NorthwindDB" 
                    connectionStringName="NORTHWNDConnectionString" />
            </databases>
         </sqlCacheDependency>
      </caching>
   </system.web>
</configuration>

<add> 元素 ( NorthwindDB ) 中的 name 值將人類別可讀的名稱與特定資料庫相關聯。 當使用 SQL 快取依賴項時,我們需要引用此處定義的資料庫名稱以及快取資料所基於的表。 我們將在步驟 6 中了解如何使用 SqlCacheDependency 類別以程式設計方式將 SQL 快取相依性與快取資料關聯起來。

一旦建立了 SQL 快取依賴關係,輪詢系統將 pollTime 每毫秒連接到 <databases> 元素中定義的資料庫並執行 AspNet_SqlCachePollingStoredProcedure 預存程序。 此預存程序 (使用 aspnet_regsql.exe 命令列工具在步驟 3 中新增回來) 傳回 AspNet_SqlCacheTablesForChangeNotification 中每筆記錄的 tableNamechangeId 值。 過時的 SQL 快取相依性將從快取中逐出。

pollTime 設定引入了效能和資料陳舊性之間的權衡。 較小的 pollTime 值會增加對資料庫的請求數量,但會更快地從快取中逐出過時的資料。 較大的 pollTime 值會減少資料庫請求的數量,但會增加後端資料變更與相關快取項目被逐出之間的延遲。 幸運的是,資料庫請求正在執行一個簡單的預存程序,該過程僅從一個簡單的輕量級表中傳回幾行。 但請嘗試使用不同的 pollTime 值,以便在應用程式的資料庫存取和資料陳舊性之間找到理想的平衡。 允許的最小 pollTime 值為 500。

注意

上面的範例在 <add> 元素中提供了單一 pollTime 值,但您可以選擇在 <sqlCacheDependency> 元素中指定 pollTime 值。 如果您指定了多個資料庫並且想要自訂每個資料庫的輪詢頻率,這非常有用。

第 5 步:以聲明方式使用 SQL 快取依賴項

在步驟 1 到 4 中,我們了解如何設定必要的資料庫基礎結構並設定輪詢系統。 有了這個基礎結構,我們現在可以使用程式設計或聲明性技術將專案新增到具有關聯 SQL 快取相依性的資料快取。 在此步驟中,我們將研究如何以聲明方式使用 SQL 快取相依性。 在第 6 步中,我們將了解程式設計方法。

使用 ObjectDataSource 快取資料 教學探討了 ObjectDataSource 的宣告式 快取功能。 透過簡單地將 CacheDuration 屬性設為 EnableCaching 並將 true 屬性設為某個時間間隔,ObjectDataSource 將自動快取從其底層物件傳回的資料指定的時間間隔。 ObjectDataSource 也可以使用一個或多個 SQL 快取相依性。

若要以聲明方式示範如何使用 SQL 快取依賴項,請開啟 Caching 資料夾中的 SqlCacheDependencies.aspx 頁面並將 GridView 從工具箱拖曳到設計器上。 將 GridView 設為 ProductsDeclarative,並從其智慧標記中選擇將其綁定到名為 ProductsDataSourceDeclarative 的新 ObjectDataSource ID

建立一個名為 ProductsDataSourceDeclarative 的新 ObjectDataSource

圖 5:建立一個名為的新 ObjectDataSource (ProductsDataSourceDeclarative按一下檢視全尺寸影像)

設定 ObjectDataSource 以使用 ProductsBLL 類別並將 SELECT 索引標籤中的下拉清單設為 GetProducts()。 在 UPDATE 標籤中,選擇具有三個輸入參數的 UpdateProduct 重載 productNameunitPriceproductID。 在「插入」和「刪除」標籤中將下拉清單設為 (無)。

使用具有三個輸入參數的 UpdateProduct 重載

圖 6:使用具有三個輸入參數的 UpdateProduct 重載 (按一下查看全尺寸影像)

將 INSERT 和 DELETE 標籤的下拉清單設為 (無)

圖 7:將 INSERT 和 DELETE 標籤的下拉清單設為 (無) (按一下查看全尺寸影像)

完成設定資料來源精靈後,Visual Studio 將在 GridView 中為每個資料欄位建立 BoundFields 和 CheckBoxFields。 刪除 ProductNameCategoryNameUnitPrice 以外的所有欄位,並根據您認為合適的方式設定這些欄位的格式。 從 GridView 的智慧標記中,選取「啟用分頁」、「啟用排序」和「啟用編輯」複選框。 Visual Studio 會將 ObjectDataSource 的 OldValuesParameterFormatString 屬性設為 original_{0}. 為了使 GridView 的編輯功能正常運作,請從聲明性語法中完全刪除此屬性,或將其設定回預設值 {0}

最後,在 GridView 上方新增一個 Label Web 控件,並將 ID 屬性設為 ODSEvents,並將 EnableViewState 屬性設為 false。 進行這些變更後,頁面的聲明性標記應類似於以下內容。 請注意,我對 GridView 欄位進行了許多美觀的自訂,這些自訂對於演示 SQL 快取依賴項功能來說並不是必需的。

<asp:Label ID="ODSEvents" runat="server" EnableViewState="False" />
<asp:GridView ID="ProductsDeclarative" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSourceDeclarative" 
    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") %>' />
            </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"
                    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" Display="Dynamic" 
                    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="ProductsDataSourceDeclarative" runat="server" 
    SelectMethod="GetProducts" TypeName="ProductsBLL" 
    UpdateMethod="UpdateProduct">
    <UpdateParameters>
        <asp:Parameter Name="productName" Type="String" />
        <asp:Parameter Name="unitPrice" Type="Decimal" />
        <asp:Parameter Name="productID" Type="Int32" />
    </UpdateParameters>
</asp:ObjectDataSource>

接下來,為 ObjectDataSource Selecting事件建立一個事件處理程序,並在其中新增以下程式碼:

protected void ProductsDataSourceDeclarative_Selecting
    (object sender, ObjectDataSourceSelectingEventArgs e)
{
    ODSEvents.Text = "-- Selecting event fired";
}

回想一下,ObjectDataSource Selecting事件僅在從其底層物件擷取資料時才會觸發。 如果 ObjectDataSource 從其自己的快取存取資料,則不會觸發此事件。

現在,透過瀏覽器造訪此頁面。 由於我們尚未實現任何快取,因此每次對網格進行分頁、排序或編輯時,頁面都應顯示文字選擇已觸發的事件,如圖 8 所示。

每次對 GridView 進行分頁、編輯或排序時,都會觸發 ObjectDataSource 的 Selecting 事件

圖 8:每次對 GridView 進行分頁、編輯或排序時都會觸發 ObjectDataSource Selecting事件 (按一下檢視全尺寸影像)

正如我們在使用 ObjectDataSource 快取資料教學中所看到的,設定 EnableCaching 屬性為 true 會導致 ObjectDataSource 在 CacheDuration 屬性指定的時間內快取其資料。 ObjectDataSource 還有一個SqlCacheDependency屬性,它使用下列模式為快取資料新增一個或多個 SQL 快取相依性:

databaseName1:tableName1;databaseName2:tableName2;...

其中,databaseName 是 中 <add> 元素的 name 屬性 Web.config 中指定的資料庫名稱,tableName 是資料庫表的名稱。 例如,要建立一個基於 Northwind Products表的 SQL 快取依賴關係無限期快取資料的 ObjectDataSource,請將 ObjectDataSource 的EnableCaching屬性設為true NorthwindDB:Products ,並將 SqlCacheDependency 屬性設為 NorthwindDB:Products 。

注意

您可以透過設定 EnableCaching true 時間間隔以及資料庫和表格名稱來使用 SQL 快取依賴項和基於時間的到期日。CacheDurationSqlCacheDependency 當達到基於時間的到期時間或當輪詢系統注意到底層資料庫資料已變更時 (以先發生者為準),ObjectDataSource 將逐出其資料。

中的 GridView SqlCacheDependencies.aspx 顯示來自兩個表的資料 ProductsCategories (透過 JOIN 擷取產品的 CategoryName 欄位Categories)。 因此,我們要指定兩個 SQL 快取依賴項: NorthwindDB:Products;NorthwindDB:Categories 。

設定 ObjectDataSource 以支援使用產品和類別上的 SQL 快取依賴關係進行快取

圖 9:設定 ObjectDataSource 以支援使用 SQL 快取依賴關係進行快取 ProductsCategories (按一下查看全尺寸映像)

將 ObjectDataSource 設定為支援快取後,透過瀏覽器重新造訪該頁面。 同樣,文字「選擇觸發的事件」應該出現在首頁訪問時,但在分頁、排序或按一下「編輯」或「取消」按鈕時應該會消失。 這是因為在資料載入到 ObjectDataSource 的快取中後,它會一直保留在那裡,直到修改表 ProductsCategories 透過 GridView 更新資料。

翻閱網格並注意到缺少「選擇事件觸發」文字後,開啟一個新的瀏覽器視窗並導航至「編輯、插入和刪除」部分中的基礎教學 (~/EditInsertDelete/Basics.aspx)。 更新產品的名稱或價格。 然後,從第一個瀏覽器窗口,查看不同的資料頁,對網格進行排序,或點擊行的「編輯」按鈕。 這次,由於底層資料庫資料已被修改,「選擇觸發的事件」應該會重新出現 (參見圖 10)。 如果文字未出現,請稍等片刻,然後重試。 請記住,輪詢服務 pollTime 每毫秒檢查一次 Products 表的更改,因此更新基礎資料和清除快取資料之間存在延遲。

修改產品表會逐出快取的產品資料

圖 10:修改產品表會清除快取的產品資料 (點選查看大圖)

第 6 步:以程式設計方式使用 SqlCacheDependency 類別

架構中的快取資料教學課程介紹了在架構中使用單獨的快取層而不是將快取資料與 ObjectDataSource 緊密耦合的好處。 在該教學課程中,我們建立了一個 ProductsCL 類別來演示以程式設計方式使用資料快取。 若要利用快取層中的 SQL 快取依賴項,請使用 SqlCacheDependency 類別。

對於輪詢系統,SqlCacheDependency 物件必須與特定的資料庫和表對相關聯。 例如,以下程式碼根據 Northwind Products 資料庫的表建立一個 SqlCacheDependency 物件:

Caching.SqlCacheDependency productsTableDependency = 
    new Caching.SqlCacheDependency("NorthwindDB", "Products");

SqlCacheDependency 建構子的兩個輸入參數分別是資料庫名稱和表格名稱。 與 ObjectDataSource 的 SqlCacheDependency 屬性一樣,使用的資料庫名稱與 Web.config<add> 元素的 name 屬性中指定的值相同。 表名是資料庫表的實際名稱。

若要將 SqlCacheDependency 與新增至資料快取中的項目相關聯,請使用接受相依性的 Insert 方法重載之一。 以下程式碼無限期地為資料快取添加,但將其與 Products 表上的 SqlCacheDependency 相關聯。 簡而言之,將保留在快取中,直到由於記憶體限製或輪詢系統偵測到 Products 表自快取以來已發生變更而被逐出。

Caching.SqlCacheDependency productsTableDependency = 
    new Caching.SqlCacheDependency("NorthwindDB", "Products");
Cache.Insert(key, 
             value, 
             productsTableDependency, 
             System.Web.Caching.Cache.NoAbsoluteExpiration, 
             System.Web.Caching.Cache.NoSlidingExpiration);

快取層 ProductsCL 類別目前使用基於時間的 60 秒過期時間來快取 Products 表中的資料。 讓我們更新此類別,以便它使用 SQL 快取依賴項。 ProductsCL 類別的 AddCacheItem 方法,負責將資料新增至快取中,目前包含以下程式碼:

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

更新此程式碼以使用 SqlCacheDependency 物件而不是 MasterCacheKeyArray 快取依賴項:

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Add the SqlCacheDependency objects for Products
    Caching.SqlCacheDependency productsTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Products");
    // Add the item to the data cache using productsTableDependency
    DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, 
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration);
}

若要測試此功能,請將 ProductsDeclarative GridView 新增至現有 GridView 下方的頁面。 將此新的 GridView ID 設為 ProductsProgrammatic,並透過其智慧標記將其綁定到名為 的新 ObjectDataSource ProductsDataSourceProgrammatic。 設定 ObjectDataSource 以使用該 ProductsCL 類別,將 SELECT 和 UPDATE 索引標籤中的下拉清單分別設為 GetProductsUpdateProduct

設定 ObjectDataSource 以使用 ProductsCL 類別

圖 11:設定 ObjectDataSource 以使用 ProductsCL 類別 (按一下檢視全尺寸影像)

從 SELECT 標籤的下拉清單中選擇 GetProducts 方法

圖 12:從「選擇」標籤的下拉清單中選擇 GetProducts 方法 (按一下查看大圖)

從更新標籤的下拉清單中選擇更新產品方法

圖 13:從 UPDATE 標籤的下拉清單中選擇 UpdateProduct Method (點擊看大圖)

完成設定資料來源精靈後,Visual Studio 將在 GridView 中為每個資料欄位建立 BoundFields 和 CheckBoxFields。 與新增至此頁面的第一個 GridView 一樣,刪除 ProductNameCategoryNameUnitPrice 以外的所有欄位,並根據需要設定這些欄位的格式。 從 GridView 的智慧標記中,選取「啟用分頁」、「啟用排序」和「啟用編輯」複選框。 與 ProductsDataSourceDeclarativeObjectDataSource 一樣,Visual Studio 會將 ProductsDataSourceProgrammaticObjectDataSourceOldValuesParameterFormatString 的屬性設為 original_{0}。 為了讓 GridView 的編輯功能正常運作,請將此屬性設定回 {0} (或從聲明性語法中完全刪除屬性指派)。

完成這些任務後,產生的 GridView 和 ObjectDataSource 宣告性標記應如下所示:

<asp:GridView ID="ProductsProgrammatic" runat="server" 
    AutoGenerateColumns="False" DataKeyNames="ProductID" 
    DataSourceID="ProductsDataSourceProgrammatic" 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") %>' />
            </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="ProductsDataSourceProgrammatic" 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>

若要測試快取層中的 SQL 快取依賴關係,請在 ProductCL 類別的 AddCacheItem 方法中設定斷點,然後開始偵錯。 當您第一次訪問 SqlCacheDependencies.aspx 時,應該會命中斷點,因為第一次請求資料並將其放入快取中。 接下來,移至 GridView 中的另一頁或對其中一列進行排序。 這會導致 GridView 重新查詢其資料,但由於 Products 資料庫表尚未修改,因此應該在快取中找到資料。 如果在快取中反覆找不到資料,請確保您的電腦上有足夠的可用記憶體,然後重試。

翻閱 GridView 的幾個頁面後,開啟第二個瀏覽器視窗並導航至「編輯、插入和刪除」部分 (~/EditInsertDelete/Basics.aspx) 中的基礎教學。 從「產品」表更新一筆記錄,然後從第一個瀏覽器視窗查看新頁面或按一下其中一個排序標題。

在這種情況下,您將看到以下兩種情況之一:要麼命中斷點,表明快取的資料由於資料庫中的更改而被逐出;要麼或者,斷點不會被命中,這代表 SqlCacheDependencies.aspx 現在顯示的是陳舊的資料。 如果未命中斷點,則可能是因為資料變更後輪詢服務尚未觸發。 請記住,輪詢服務 pollTime 每毫秒檢查一次 Products 表的更改,因此更新基礎資料和清除快取資料之間存在延遲。

注意

當透過 GridView 中的 GridView 編輯 SqlCacheDependencies.aspx 中一個產品時,更可能出現這種延遲。 在架構中快取資料教學課程中,我們新增了MasterCacheKeyArray快取依賴項ProductsCL,以確保透過類別 UpdateProduct 方法編輯的資料從快取中逐出。 但是,我們在本步驟前面修改 AddCacheItem 方法時替換了此快取依賴項,因此 ProductsCL 類別將繼續顯示快取的資料,直到輪詢系統注意到 Products 表的變更。 我們將在步驟 7 中了解如何重新引入 MasterCacheKeyArray 快取相依性。

第 7 步:將多個相依性與快取項目關聯

回想一下,MasterCacheKeyArray 快取相依性用於確保當更新其中關聯的任何單一項目時,所有與產品相關的資料都會從快取中逐出。 例如,GetProductsByCategoryID(categoryID) 方法會快取每個唯一的 categoryID 值的 ProductsDataTables 實例。 如果這些物件之一被逐出,MasterCacheKeyArray 快取相依性將確保其他物件也被刪除。 如果沒有這種快取依賴性,當快取的資料被修改時,其他快取的產品資料可能會過期。 因此,在使用 SQL 快取依賴項時維護 MasterCacheKeyArray 快取相依性非常重要。 然而,資料快取的 Insert 方法只允許單一依賴物件。

此外,在處理 SQL 快取依賴項時,我們可能需要將多個資料庫表關聯為依賴項。 例如,類別中 ProductsDataTable 快取的內容包含每個產品的 ProductsCL 類別和供應商名稱,但 AddCacheItem 方法僅使用對 Products。 在這種情況下,如果使用者更新類別或供應商的名稱,則快取的產品資料將保留在快取中並過期。 因此,我們希望快取的商品資料不僅依賴 Products 表,還依賴 CategoriesSuppliers 表。

AggregateCacheDependency 類別提供了一種將多個依賴項與快取項目關聯起來的方法。 首先建立一個 AggregateCacheDependency 實例。 接下來,使用 AggregateCacheDependencyAdd 方法新增相依性集。 此後將項目插入資料快取時,傳入 AggregateCacheDependency 實例。 當任何AggregateCacheDependency 實例的依賴項發生變化時,快取的項目將被驅逐。

下面顯示了 ProductsCL 類別 AddCacheItem 方法的更新程式碼。 此方法會建立 MasterCacheKeyArray 快取依賴關係以及 ProductsCategories Suppliers 表的 SqlCacheDependency 物件。 這些全部組合成一個名為 aggregateDependenciesAggregateCacheDependency 物件,然後將其傳遞到該 Insert 方法中。

private void AddCacheItem(string rawKey, object value)
{
    System.Web.Caching.Cache DataCache = HttpRuntime.Cache;
    // Make sure MasterCacheKeyArray[0] is in the cache and create a depedency
    DataCache[MasterCacheKeyArray[0]] = DateTime.Now;
    Caching.CacheDependency masterCacheKeyDependency = 
        new Caching.CacheDependency(null, MasterCacheKeyArray);
    // Add the SqlCacheDependency objects for Products, Categories, and Suppliers
    Caching.SqlCacheDependency productsTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Products");
    Caching.SqlCacheDependency categoriesTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Categories");
    Caching.SqlCacheDependency suppliersTableDependency = 
        new Caching.SqlCacheDependency("NorthwindDB", "Suppliers");
    // Create an AggregateCacheDependency
    Caching.AggregateCacheDependency aggregateDependencies = 
        new Caching.AggregateCacheDependency();
    aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, 
        categoriesTableDependency, suppliersTableDependency);
    DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, 
        Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration);
}

測試這個新程式碼。現在,對 ProductsCategoriesSuppliers 表的變更會導致快取的資料被逐出。 此外,透過 GridView 編輯產品時呼叫的 ProductsCL 類別 UpdateProduct 方法會逐出快取依賴項,這會導致 MasterCacheKeyArray ProductsDataTable 快取被逐出並在下一個請求時重新擷取資料。

注意

SQL 快取相依性也可以與輸出快取一起使用。 有關此功能的演示,請參閱:將 ASP.NET 輸出快取與 SQL Server 結合使用

摘要

快取資料庫資料時,理想情況下資料將保留在快取中,直到在資料庫中被修改。 使用 ASP.NET 2.0,可以在聲明性和程式設計方案中建立和使用 SQL 快取相依性。 這種方法的挑戰之一是發現資料何時被修改。 Microsoft SQL Server 2005 的完整版本提供了通知功能,可在查詢結果發生變更時向應用程式發出警報。 對於 SQL Server 2005 Express Edition 和舊版的 SQL Server,必須改用輪詢系統。 幸運的是,建立必要的輪詢基礎結構相當簡單。

快樂程式!

深入閱讀

有關本教學課程中討論的主題的更多信息,請參閱以下資源:

關於作者

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部落格可以在以下位置找到。

特別感謝

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