由 斯科特·米切爾
最簡單的快取策略是允許快取的資料在指定的時間段後過期。 但是,這種簡單的方法意味著快取的資料與其基礎資料來源沒有任何關聯,導致資料過時或過早失效。 更好的方法是使用 SqlCacheDependency 類別,以便資料保持快取狀態,直到其基礎資料在 SQL 資料庫中修改為止。 本教學課程會指導您方法。
簡介
教學課程使用 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 的表,該表具有三列tableName、notificationCreated 和 changeId。 此表包含每個表的一行,其中包含可能需要在 Web 應用程式的 SQL 快取依賴項中使用的資料。 此 tableName 列指定資料表的名稱,而 notificationCreated 指示將資料列新增至資料表的日期和時間。
changeId 列是類型 int 且初始值為 0。 它的值隨著對資料表的每次修改而增加。
除了 AspNet_SqlCacheTablesForChangeNotification 表格之外,資料庫還需要在可能會出現在 SQL 快取依賴項中的每一個表格上包含觸發器。 每當插入、更新或刪除資料列時,這些觸發器會執行並增加 changeId 中的 AspNet_SqlCacheTablesForChangeNotification 值。
ASP.NET 執行時會追蹤使用 SqlCacheDependency 物件快取資料時的表格目前 changeId。 定期檢查資料庫,並擠出任何 SqlCacheDependency 物件,其 changeId 值與資料庫中的值不同,因為不同的 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_securityadmin 和 db_ddladmin 角色中。
例如,若要將輪詢基礎架構新增至名為 pubs 的 Microsoft SQL Server 資料庫,並使用 Windows 驗證,在名為 ScottsServer 的資料庫伺服器上執行,請瀏覽到相應的目錄,然後在命令提示字元中輸入:
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
若要將觸發器新增至 authors 和 titles 表到 pubs 資料庫在 ScottsServer 上,請使用:
aspnet_regsql.exe -S ScottsServer -E -d pubs -t authors -et
aspnet_regsql.exe -S ScottsServer -E -d pubs -t titles -et
對於本教學課程,將觸發器新增至 Products、Categories 和 Suppliers 表格中。 我們將在步驟 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 版本,您可以下載免費的 sql Server Management Studio Microsoft。
首先關閉 Visual Studio。 接下來,開啟 SQL Server Management Studio 並選擇使用 Windows 驗證連線到 localhost\SQLExpress 伺服器。
連接至 localhost\SQLExpress Server
圖 1:localhost\SQLExpress連接至伺服器
連接到伺服器後,Management Studio 將顯示伺服器並包含資料庫、安全性等的子資料夾。 右鍵點擊資料庫資料夾並選擇附加選項。 這將開啟「附加資料庫」對話框 (請參閱圖 2)。 按一下 "新增" 按鈕,然後選擇位於您網頁應用程式 NORTHWND.MDF 資料夾中的 App_Data 資料庫資料夾。
從 App_Data 資料夾附加 NORTHWND.MDF 資料庫
圖 2:從 資料夾附加 資料庫 (按一下查看全尺寸影像)
這會將資料庫新增至資料庫資料夾。 資料庫名稱可能是資料庫檔案的完整路徑,或者是前面帶有GUID的完整路徑。 為了避免在使用 aspnet_regsql.exe 命令列工具時必須輸入冗長的資料庫名稱,請右鍵點擊剛剛附加的資料庫並選擇重新命名,將資料庫重新命名為更人性化的名稱。 我已將資料庫重新命名為 DataTutorials 。
將附加的資料庫重新命名為較易理解的名稱。
圖 3:將附加資料庫重新命名為更人性化的名稱
步驟 3:將輪詢基礎結構新增至 Northwind 資料庫
現在我們已經掛載了NORTHWND.MDF資料庫,從App_Data資料夾,我們可以添加輪詢基礎結構。 假設您已將資料庫重新命名為 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)、新預存程序以及 Products、Categories 和 Suppliers 表上的觸發器。
資料庫現在包括了必要的輪詢基礎設施
圖 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>
name 元素 ( NorthwindDB ) 中的 <add> 值將易於閱讀的名稱與特定資料庫相關聯。 當使用 SQL 快取依賴項時,我們需要引用此處定義的資料庫名稱以及快取資料所基於的表。 我們將在步驟 6 中了解如何使用 SqlCacheDependency 類別以程式碼方式將 SQL 快取相依性與快取資料關聯起來。
一旦建立了 SQL 快取依賴關係,輪詢系統將每 pollTime 毫秒連接到 <databases> 元素中定義的資料庫,並執行 AspNet_SqlCachePollingStoredProcedure 預存程序。 此預存程序 (在步驟 3 中使用 aspnet_regsql.exe 命令列工具新增回來) 傳回 tableName 和 changeId 值,對於 AspNet_SqlCacheTablesForChangeNotification 中的每筆記錄。 過時的 SQL 快取相依性將從快取中逐出。
pollTime 設定引入了效能和數據陳舊之間的權衡。
pollTime 值較小會增加對資料庫的請求數量,但能更快地把過時的資料逐出快取。 較大的pollTime值會減少資料庫請求的數量,但會增加後端資料變更與相關快取項目被清除之間的延遲。 幸運的是,資料庫請求正在執行一個簡單的預存程序,這個程序僅從一個簡單的輕量級表格中返回少量資料列。 但請嘗試不同的 pollTime 值,以便在應用程式的資料庫存取和資料陳舊性之間找到理想的平衡。 允許的最小 pollTime 值為 500。
備註
上面的範例在 <sqlCacheDependency> 元素中提供了單一 pollTime 值,但您可以選擇在 <add> 元素中指定 pollTime 值。 如果您指定了多個資料庫並且想要自訂每個資料庫的輪詢頻率,這非常有用。
第 5 步:以聲明方式使用 SQL 快取依賴項
在步驟 1 到 4 中,我們了解如何設定必要的資料庫基礎結構並設定輪詢系統。 有了這個基礎結構,我們現在可以使用程式化或宣告性技術將項目新增到具有關聯 SQL 快取相依性的資料快取中。 在此步驟中,我們將研究如何以聲明方式使用 SQL 快取相依性。 在第 6 步中,我們將了解程式設計方法。
教學教程「使用 ObjectDataSource 快取資料」探討了 ObjectDataSource 的宣告式快取功能。 簡單地將 EnableCaching 屬性設為 True 並將 CacheDuration 屬性設為某個時間間隔,ObjectDataSource 將自動快取從其底層物件傳回的資料,並維持此快取資料達到所設定的時間間隔。 ObjectDataSource 也可以使用一個或多個 SQL 快取相依性。
若要以宣告式示範如何使用 SQL 快取相依性,請開啟 ID 設為 ProductsDeclarative,並從其智慧標籤中選擇將其繫結到名為 ProductsDataSourceDeclarative 的新 ObjectDataSource。
建立一個名為 ProductsDataSourceDeclarative 的新 ObjectDataSource
圖 5:建立一個新名為ProductsDataSourceDeclarative的 ObjectDataSource (按一下以檢視全尺寸影像)
設定 ObjectDataSource 來使用 ProductsBLL 類別,並將 SELECT 標籤中的下拉選單設為 GetProducts()。 在 UPDATE 標籤中,選擇具有三個輸入參數的 UpdateProduct 重載 productName、unitPrice 和 productID。 在「插入」和「刪除」標籤中將下拉清單設為 (無)。
圖 6:使用具有三個輸入參數的 UpdateProduct 多載 (按一下查看全尺寸影像)
將 INSERT 和 DELETE 標籤的下拉清單設為 (無)
圖 7:將 INSERT 和 DELETE 標籤的下拉清單設為 (無) (按一下查看全尺寸影像)
完成設定資料來源精靈後,Visual Studio 將在 GridView 中為每個資料欄位建立 BoundFields 和 CheckBoxFields。 刪除 ProductName、CategoryName 和 UnitPrice 以外的所有欄位,並根據您認為合適的方式設定這些欄位的格式。 從 GridView 的智慧標記中,選取「啟用分頁」、「啟用排序」和「啟用編輯」複選框。 Visual Studio 會將 ObjectDataSource 的 OldValuesParameterFormatString 屬性設為 original_{0}. 為了使 GridView 的編輯功能正常運作,請從聲明性語法中完全刪除此屬性,或將其設定回預設值 {0}。
最後,在 GridView 上方新增 Label Web 控件,並將
<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 的事件建立一個事件處理程序,並在其中加入下列程式碼:
Protected Sub ProductsDataSourceDeclarative_Selecting _
(sender As Object, e As ObjectDataSourceSelectingEventArgs) _
Handles ProductsDataSourceDeclarative.Selecting
ODSEvents.Text = "-- Selecting event fired"
End Sub
請記住,ObjectDataSource 的 Selecting 事件僅在從其基礎物件擷取資料時才會觸發。 如果 ObjectDataSource 從其自己的快取存取資料,則不會觸發此事件。
現在,透過瀏覽器造訪此頁面。 由於我們尚未實現任何快取,因此每次對網格進行分頁、排序或編輯時,頁面都應顯示文字「選擇事件已觸發」,如圖 8 所示。
每次進行 GridView 分頁、編輯或排序時,ObjectDataSource 的 Selecting 事件都會觸發。
圖 8:每次對 GridView 進行分頁、編輯或排序時,都會觸發 ObjectDataSource 的 事件 (按一下以檢視全尺寸影像)
正如我們在 ObjectDataSource 快取資料 教學中所看到的,將 EnableCaching 屬性設定為 True 將使 ObjectDataSource 在 CacheDuration 屬性指定的時間內快取其資料。 ObjectDataSource 還有一個屬性,可以使用下列模式為快取資料新增一個或多個 SQL 快取相依性:
databaseName1:tableName1;databaseName2:tableName2;...
其中,databaseName 是在 name 中 <add> 元素的 Web.config 屬性中指定的資料庫名稱,tableName 是資料庫表的名稱。 例如,要建立一個基於 Northwind 的 Products 表的 SQL 快取依賴關係,無限期快取資料的 ObjectDataSource,請將 ObjectDataSource 的 EnableCaching 屬性設為 True ,並將其 SqlCacheDependency 屬性設為 NorthwindDB:Products。
備註
您可以透過設定EnableCaching為True, 設定CacheDuration為時間間隔, 以及設定資料庫和表格名稱為SqlCacheDependency,來使用 SQL 快取依賴項和基於時間的過期。 當達到基於時間的到期時間或當輪詢系統注意到底層資料庫資料已變更時 (以先發生者為準),ObjectDataSource 將清除其資料。
GridView 顯示來自兩個表的資料 Products 和 Categories(透過 JOIN 在 Categories 擷取產品的 CategoryName 欄位)。 因此,我們要指定兩個 SQL 快取依賴項: NorthwindDB:Products;NorthwindDB:Categories 。
設定 ObjectDataSource 以支援使用產品和類別上的 SQL 快取依賴關係進行快取
圖九:將 ObjectDataSource 設定為支援使用 SQL 快取依賴性在 Products 和 Categories 上進行快取 (按一下查看全尺寸圖像)
將 ObjectDataSource 設定為支援快取後,透過瀏覽器重新造訪該頁面。 同樣,文字「選擇觸發的事件」應該在首次訪問首頁時顯示,但在進行分頁、排序或點擊「編輯」或「取消」按鈕時應該消失。 這是因為當資料載入到 ObjectDataSource 的快取中後,它會一直保留在那裡,直到 Products 或 Categories 表格被修改,或是透過 GridView 更新資料。
翻閱資料網格並注意到缺少「選擇事件觸發」文字後,開啟一個新的瀏覽器視窗並導航至「編輯、插入和刪除」部分中的基礎教學 (~/EditInsertDelete/Basics.aspx)。 更新產品的名稱或價格。 然後,從第一個瀏覽器窗口,查看不同的資料頁,對網格進行排序,或點擊行的「編輯」按鈕。 這次,由於底層資料庫資料已被修改,「選擇事件觸發」應該會重新啟動 (參見圖 10)。 如果文字未出現,請稍等片刻,然後重試。 請記住,輪詢服務每隔 pollTime 毫秒檢查一次 Products 表的更改,因此在基礎資料更新與快取資料清除之間會存在延遲。
修改產品表會驅逐快取的產品資料
圖 10:修改產品表會清除快取的產品資料 (點擊查看大圖)
第 6 步:以程式設計方式使用 SqlCacheDependency類別
架構中的快取資料教學課程探討在架構中使用單獨的快取層,而不是將快取功能與 ObjectDataSource 緊密相耦合的好處。 在該教學課程中,我們建立了一個 ProductsCL 類別來演示程式化操作資料快取。 若要利用快取層中的 SQL 快取依賴項,請使用 SqlCacheDependency 類別。
對於輪詢系統,SqlCacheDependency 物件必須與特定的資料庫和資料表對相關聯。 例如,以下程式碼根據 Northwind 資料庫的 Products 表建立一個 SqlCacheDependency 物件:
Dim productsTableDependency As _
New Caching.SqlCacheDependency("NorthwindDB", "Products")
SqlCacheDependency 建構子的兩個輸入參數分別是資料庫名稱和資料表名稱。 與 ObjectDataSource 的 SqlCacheDependency 屬性一樣,使用的資料庫名稱與 Web.config 中 <add> 元素的 name 屬性中指定的值相同。 表名是資料庫表的實際名稱。
若要將 SqlCacheDependency 與新增至資料快取中的項目相關聯,請使用接受相依性的 Insert 方法重載之一。 以下程式碼無限期地為資料快取添加值,但將其與Products表自快取以來已發生變更而被逐出。
Dim productsTableDependency As _
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 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), _
Caching.Cache.NoSlidingExpiration)
End Sub
更新此程式碼以使用 SqlCacheDependency 物件而不是 MasterCacheKeyArray 快取依賴項:
Private Sub AddCacheItem(ByVal rawKey As String, ByVal value As Object)
Dim DataCache As System.Web.Caching.Cache = HttpRuntime.Cache
' Add the SqlCacheDependency objects for Products
Dim productsTableDependency As New _
Caching.SqlCacheDependency("NorthwindDB", "Products")
DataCache.Insert(GetCacheKey(rawKey), value, productsTableDependency, _
Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub
若要測試此功能,請將 GridView 新增至現有的 ProductsDeclarative GridView 下方的頁面。 將此新的 GridView 的ID設為ProductsProgrammatic,並透過其智慧標籤將其綁定到名為ProductsDataSourceProgrammatic的新 ObjectDataSource。 設定 ObjectDataSource 以使用 ProductsCL 類別,將 SELECT 和 UPDATE 索引標籤中的下拉清單分別設為 GetProducts 和 UpdateProduct。
設定 ObjectDataSource 以使用 ProductsCL 類別
圖 11:將 ObjectDataSource 設定為使用 ProductsCL 類別 (按一下即可檢視完整大小的影像)
從 SELECT 標籤的下拉清單中選擇 GetProducts 方法
圖 12:從「SELECT」標籤的下拉清單中選擇方法 (點擊查看完整圖片)
從 UPDATE 標籤的下拉清單中選擇 UpdateProduct 方法
圖 13:從 UPDATE 頁籤的下拉清單中選擇 UpdateProduct 方法 (點擊看大圖)
完成設定資料來源精靈後,Visual Studio 將在 GridView 中為每個資料欄位建立 BoundFields 和 CheckBoxFields。 與新增至此頁面的第一個 GridView 一樣,刪除除ProductName、CategoryName和UnitPrice以外的所有欄位,然後根據需要設定這些欄位的格式。 從 GridView 的智慧標記中,選取「啟用分頁」、「啟用排序」和「啟用編輯」複選框。 與 ProductsDataSourceDeclarativeObjectDataSource 一樣,Visual Studio 會將 ProductsDataSourceProgrammaticObjectDataSource 的 OldValuesParameterFormatString 屬性設為 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 表的更改,因此在基礎資料更新與快取資料清除之間會存在延遲。
備註
當在SqlCacheDependencies.aspx的 GridView 中編輯某個產品時,更容易出現這種延遲。 在架構中快取資料教學課程中,我們新增了MasterCacheKeyArray快取相依性,以確保透過類別的UpdateProduct方法所編輯的資料被從快取中逐出。 但是,我們在此步驟稍早修改 AddCacheItem 方法時替換了此快取依賴項,因此 ProductsCL 類別將繼續顯示快取的資料,直到輪詢系統注意到 Products 表的變更。 我們將在步驟 7 中了解如何重新引入 MasterCacheKeyArray 快取相依性。
第 7 步驟:將多個相依性與快取項目關聯
請記住,MasterCacheKeyArray 快取相依性用於確保當更新其中關聯的任何單一項目時,所有與產品相關的資料都會從快取中逐出。 例如,GetProductsByCategoryID(categoryID)方法會快取每個唯一的categoryID值的ProductsDataTables實例。 如果這些物件之一被移除,MasterCacheKeyArray 快取相依性會確保其他物件也隨之移除。 如果沒有這種快取依賴性,當快取的資料被修改時,其他快取的產品資料可能會過期。 因此,在使用 SQL 快取相依性時,維護 MasterCacheKeyArray 快取相依性非常重要。 然而,資料快取的Insert方法只允許單一依賴物件。
此外,在處理 SQL 快取依賴項時,我們可能需要將多個資料庫表關聯為依賴項。 例如,ProductsDataTable 類別中快取的內容包含每個產品的類別名稱和供應商名稱,但 AddCacheItem 方法僅依賴於 Products 的依賴性。 在這種情況下,如果使用者更新類別或供應商的名稱,則快取的產品資料將保留在快取中並過期。 因此,我們希望快取的商品資料不僅依賴 Products 表,還依賴 Categories 和 Suppliers 表。
AggregateCacheDependency類別提供了一種將多個依賴項與快取項目關聯起來的方法。 首先建立一個 AggregateCacheDependency 實例。 接下來,使用 AggregateCacheDependency s Add 方法新增相依性集。 此後將項目插入資料快取時,傳入 AggregateCacheDependency 實例。 當任何AggregateCacheDependency實例的依賴項發生變化時,快取的項目將被移除。
下面顯示了 ProductsCL 類別 AddCacheItem 方法的更新的程式碼。 此方法會建立 MasterCacheKeyArray 快取依賴關係以及 SqlCacheDependency、Products 和 Categories、Suppliers 表的物件。 這些全部組合成一個名為 aggregateDependencies 的 AggregateCacheDependency 物件,然後將其傳遞到 Insert 方法中。
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
'Create the CacheDependency
Dim masterCacheKeyDependency As _
New Caching.CacheDependency(Nothing, MasterCacheKeyArray)
' Add the SqlCacheDependency objects for Products, Categories, and Suppliers
Dim productsTableDependency As _
New Caching.SqlCacheDependency("NorthwindDB", "Products")
Dim categoriesTableDependency As _
New Caching.SqlCacheDependency("NorthwindDB", "Categories")
Dim suppliersTableDependency As _
New Caching.SqlCacheDependency("NorthwindDB", "Suppliers")
' Create an AggregateCacheDependency
Dim aggregateDependencies As New Caching.AggregateCacheDependency()
aggregateDependencies.Add(masterCacheKeyDependency, productsTableDependency, _
categoriesTableDependency, suppliersTableDependency)
DataCache.Insert(GetCacheKey(rawKey), value, aggregateDependencies, _
Caching.Cache.NoAbsoluteExpiration, Caching.Cache.NoSlidingExpiration)
End Sub
測試這個新程式碼。現在,對 Products、Categories 或 Suppliers 表格的變更會導致快取的資料被逐出。 此外,透過 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,必須改用輪詢系統。 幸運的是,建立必要的輪詢基礎結構相當簡單。
快樂的程序設計!
進一步閱讀
如需本教學課程中所討論主題的詳細資訊,請參閱下列資源:
- 在 Microsoft SQL Server 2005 中使用查詢通知
- 建立查詢通知
-
在 ASP.NET 中使用
SqlCacheDependency類別進行快取 - ASP.NET SQL Server 註冊工具 (aspnet_regsql.exe)
- 概述
SqlCacheDependency
關於作者
斯科特·米切爾,七本 ASP/ASP.NET 書籍和 4GuysFromRolla.com 創始人的作者,自1998年以來一直與Microsoft Web 技術合作。 斯科特擔任獨立顧問、教練和作家。 他的最新書是 Sams Teach Yourself ASP.NET 2.0 in 24 Hours。 可以透過 mitchell@4GuysFromRolla.com 聯絡他。
特別感謝
本教學系列已由許多熱心的評論者審閱。 本教學的主要審閱者是 Marko Rangel、Teresa Murphy 和 Hilton Giesenow。 有興趣檢閱我即將推出的 MSDN 文章嗎? 如果是,請在 mitchell@4GuysFromRolla.com給我留言。