咱們在前一篇教程中看到 , 要緩存ObjectDataSource 的數據 , 只須要設置幾個屬性便可。遺憾的是 ,ObjectDataSource 在 表示層進行 數據緩存 , 這將緩存策略與 ASP.NET 頁面緊密地結合在一塊兒。建立分層架構的緣由之一就是爲了打破這種結合。例如 ,業務邏輯層將 業務邏輯從ASP.NET 頁面中分離出來 ,而 數據訪問層將數據訪問細節分離出來。將業務邏輯細節與數據訪問細節分離出來是咱們的首選,其部分緣由是這樣使得系統更爲易讀,易於維護,能夠更爲靈活地修改。這也考慮到了知識領域與勞動分工的狀況— 表示層 的開發人員不須要熟悉數據庫的細節就 能夠進行開發工做 。而將緩存策略從 表示層 分離出來也有相似的好處。javascript
在本教程中 , 咱們將擴展咱們的架構 , 使其包括一個緩存層(Caching Layer , 簡稱 CL ), 用該層來實施咱們的緩存策略。 緩存層 包括一個 ProductsCL 類 ,該類 經過諸如GetProducts() 、GetProductsByCategoryID(categoryID) 等方法 來訪問 產品信息。當調用這些方法時 ,這些方法 首先嚐試從緩存中取得數據。若是緩存爲空 , 這些方法會調用 BLL 中 ProductsBLL 類的相應 方法 , 這進而會從 DAL 中獲取數據。 ProductsCL 類的方法將從 BLL 中獲取的數據緩存後再返回。html
如圖 1 所示, CL 位於表示層與業務邏輯層之間。java
圖1 : 在咱們的架構中加入了另一層— 緩存層(CL)web
在本教程中 , 咱們將建立一個很是簡單的 CL ,它 只有一個類 —ProductsCL ,該類 只有幾個方法。要爲整個應用程序構建一個完整的 緩存層, 則須要建立 CategoriesCL 、EmployeesCL 以及 SuppliersCL 類 , 並在這些 緩存層的 類中 , 爲BLL 中的每一個數據訪問或修改方法提供一個相應的方法。與BLL 和 DAL 同樣 , 理想狀況下 緩存層 應該實現爲一個單獨的 Class Library 項目 ; 然而 , 咱們要將它實現爲 App_Code 文件夾中的一個類。數據庫
爲了將 CL 類與 DAL 和 BLL 類更好地區分開,咱們在 App_Code 文件夾中建立一個新的子文件夾。在 Solution Explorer 中右鍵單擊 App_Code 文件夾 , 選擇 New Folder , 將新文件夾命名爲 CL 。建立這個文件夾以後 , 在其中添加一個名爲 ProductsCL.cs 的新類。編程
圖2 : 添加名爲 CL 的新文件夾和名爲 ProductsCL.cs 的類數組
與對應的業務邏輯層的類 (ProductsBLL) 同樣 ,ProductsCL 應包含相同的一組數據訪問與修改方法。不過在這裏咱們不會建立全部這些方法 ,而 只是建立幾個來感覺一下 CL 所 使用的模式。具體說來,咱們將在步驟 3 中添加 GetProducts() 與 GetProductsByCategoryID(categoryID) 方法,在步驟 4 中添加 UpdateProduct 重載方法。您能夠在空閒時添加上其它的 ProductsCL 方法以及 CategoriesCL 、 EmployeesCL 和 SuppliersCL 類。瀏覽器
在前面的教程中探討過 ObjectDataSource 緩存功能 , 該功能在內部使用 ASP.NET 數據緩存來存儲從 BLL 中獲取的數據。咱們還能夠經過編碼從ASP.NET 頁面的code-behind 類或從 web 應用架構中的類來訪問該數據緩存。要從ASP.NET 頁面的code-behind 類讀寫該數據緩存 , 請使用下面的模式 :緩存
// 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 類 的 Insert 方法 有許多重載。Cache("key") = value 和 Cache.Insert(key, value) 是 相同的 , 都是用指定的鍵值向緩存添加一個條目 ,但 沒有指定有效期。典型地 , 咱們想在向緩存添加條目時指定有效期 ,該 有效期或者是基於依賴項的 , 或者是基於時間的 ,又 或者二者兼而有之。使用 Insert 方法的其它重載 , 就能夠提供基於依賴項或基於時間的有效期信息。安全
緩存層 的方法首先要檢查請求的數據是否在緩存中,若是在,從那裏 將其返回。若是請求的數據不在緩存中 , 則須要調用 BLL 中的 相應方法。而後應將該方法返回的值緩存後再返回,以下面的流程圖所示。
圖3 : 若是數據存在於緩存中 ,緩存層 的方法會將其返回
在 CL 的 類中可使用下面的模式來完成圖 3 描述的流程 :
Type instance = Cache["key"] as Type; if (instance == null) { instance = BllMethodToGetInstance(); Cache.Insert(key, instance, ...); } return instance;
其中 ,Type 是在緩存中存儲的數據的類型 — 例如 ,Northwind.ProductsDataTable ,而 key 是惟一標識緩存條目的鍵值。若是指定 key 的條目不在緩存中,那麼 instance 就爲空值,因而經過相應的 BLL 方法獲取數據,而後緩存該數據。當執行到 Return instance 時, instance 已包含了對數據的一個引用,它要麼是從緩存得到,要麼是從 BLL 得到的。
當訪問緩存中的數據時 , 請務必使用上述模式。下面的模式,乍一看好象和上面的模式同樣,但實際上卻有一個細微的差異,這個差異會產生競爭狀態。競爭狀態很難調試,由於它們只是偶爾出現,很難重現出來。
if (Cache["key"] == null) { Cache.Insert(key, BllMethodToGetInstance(), ...); } return Cache["key"];
這第二個不正確代碼段的不一樣之處是 , 它並無將緩存條目的引用存儲在一個局部變量中 , 而是在條件語句以及 Return 語句中直接訪問數據緩存。設想這種狀況,執行到這段代碼時, Cache["key"] 是非空的,可是當執行到 Return 語句以前時,系統從緩存中刪除了這個 key。在這種罕見的狀況下,代碼會返回空值,而不是返回期待類型的對象。參見Scott Cate 的博客文章 , 裏面舉例描述了使用這個不正確的緩存模式怎樣偶爾致使非預期的行爲。
注意 : 該數據緩存是線程安全的,因此對於簡單的讀寫,您不須要對線程訪問進行同步。然而,若是您須要對緩存中的數據進行原子級的多重操做,那麼您就要負責實現鎖定或其它機制以確保線程安全。詳情參見 對 ASP.NET 緩存訪問進行同步 。
能夠經過編碼用以下的 Remove 方法 從數據緩存中刪除一個條目 :
Cache.Remove(key)
對於本教程 , 咱們來實現以下兩個方法 , 它們從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 的嚮導提供信息 , 指示哪些類和方法應出如今嚮導的步驟中。由於要從 表示層 中的ObjectDataSource 訪問 CL 的 類和方法 , 因此我添加了這些屬性來加強設計時體驗。有關這些屬性及其做用的更爲詳盡的描述,請參閱建立業務邏輯層 教程。
在GetProducts() 和 GetProductsByCategoryID(categoryID) 方法中 ,GetCacheItem(key) 方法返回的數據賦值給了一個局部變量。咱們稍後探討GetCacheItem(key) 方法 , 該方法會根據指定的 key , 從緩存中返回一個特定的條目。若是在緩存中沒有找到這樣的數據 , 則經過ProductsBLL 類的相應方法獲取該數據 , 而後用 AddCacheItem(key, value) 方法緩存該數據。
GetCacheItem(key) 和 AddCacheItem(key, value) 方法是對數據緩存的接口 , 分別負責讀與寫。GetCacheItem(key) 方法是二者中相對簡單的。它只是根據傳入的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) 並無直接使用咱們提供的 key 值 , 而是調用了GetCacheKey(key) 方法 , 這個方法在 key 前面加上 "ProductsCache-" 而後返回之 。MasterCacheKeyArray 用於保存字符串 "ProductsCache" ,咱們稍後會看到,AddCacheItem(key, value) 方法也使用這個變量。
從ASP.NET 頁面的 code-behind 類 , 咱們可使用 Page 類的 Cache 屬性 來訪問數據緩存 , 並容許相似Cache["key"] = value 的語法 , 如步驟 2 中所述。從架構內的類中,可使用 HttpRuntime.Cache 或 HttpContext.Current.Cache 來訪問數據緩存。Peter Johnson 在其博客文章 HttpRuntime.Cache vs. HttpContext.Current.Cache 中提到了使用 HttpRuntime 比使用 HttpContext.Current 稍有性能優點;所以, ProductsCL 類使用 HttpRuntime 。
注意 : 若是您的架構是使用 Class Library 項目實現的 ,則 須要添加一個對 System.Web 程序集的引用才能使用HttpRuntime 和HttpContext 類。
若是在緩存中沒有找到這個條目 ,ProductsCL 類的方法會從 BLL 中獲取數據 , 而後用 AddCacheItem(key, value) 方法緩存該數據。咱們能夠用下面的代碼將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 指示不存在滑動有效期 (sliding expiration) 。雖然這個 Insert 重載方法既有絕對有效期又有滑動有效期的輸入參數 , 可是您只能提供其中一種。若是您試圖同時指定絕對時間和時間範圍 ,Insert 方法會拋出一個 ArgumentException 異常。
注意 : 這個 AddCacheItem(key, value) 方法的實現目前有些缺點。咱們將在步驟4 中討論並解決這些問題。
除了檢索數據的方法以外 , 和BLL 同樣 , 緩存層還須要提供插入、更新、刪除數據的方法。 CL 的數據修改方法並不修改緩存數據,而是調用 BLL 的相應數據修改方法,而後使緩存數據失效。咱們在前面的教程中看到 , 這與 ObjectDataSource 的行爲是同樣的 , 當啓用了 ObjectDataSource 的緩存功能 ,並調用 它的 Insert 、Update 、Delete 方法時 ,ObjectDataSource 會產生這些行爲。
下面的 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 類 的構造函數有多個重載,但這裏使用的重載接受兩個字符串數組做爲輸入參數。第一個參數指定用做依賴項的一組文件。由於咱們不打算使用任何基於文件的依賴項,因此對第一個輸入參數使用空值。第二個輸入參數指定用做依賴項的一組緩存鍵值。在這裏咱們指定惟一的依賴項 ,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]); }
用這些教程中介紹的技巧 , 可使用 緩存層 的類和方法來對數據進行操做。爲了演示怎樣操做緩存數據 , 先保存對ProductsCL 類的更改 , 而後打開 Caching 文件夾中的 FromTheArchitecture.aspx 頁面 , 在其中添加一個 GridView 控件 。從該 GridView 控件的智能標記中,建立一個新的 ObjectDataSource 。在嚮導的第一步, ProductsCL 類做爲一個選項出現於下拉列表中。
圖4 :ProductsCL 類包含在 Business Object 下拉列表中
選擇ProductsCL , 而後 單 擊Next 。SELECT 選項卡中的下拉列表具備兩項 — GetProducts() 和 GetProductsByCategoryID(categoryID) ,而 UPDATE 選項卡只有一個 UpdateProduct 重載方法。從 SELECT 選項卡中選擇 GetProducts() 方法,從 UPDATE 選項卡中選擇 UpdateProducts 方法,而後單擊 Finish 。
圖5 :下拉列表中列出了 ProductsCL 類的方法
完成嚮導以後 ,Visual Studio 會將 ObjectDataSource 的OldValuesParameterFormatString 屬性設置爲 original_{0} 並向 GridView 添加相應的字段。將 OldValuesParameterFormatString 屬性改回默認值 {0} , 配置 GridView 使其支持分頁、排序和編輯。由於 CL 使用的 UploadProducts 重載只接受所編輯產品的名稱與價格 , 因此要限制 GridView 使其只有這兩個字段是可編輯的。
在前面的教程中 , 咱們定義了一個包含有 ProductName 、CategoryName 和 UnitPrice 字段的 GridView 控件 。可放心地複製這一格式與結構 , 這樣,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>
此時 , 咱們有了一個使用 緩存層 的頁面。爲了看到緩存的運行狀況 , 在ProductsCL 類的 GetProducts() 和UpdateProduct 方法中設置斷點。在瀏覽器中訪問該頁面 , 在排序與翻頁時 , 單步執行代碼 , 以便看到從緩存中獲取數據。而後更新一條記錄,注意因爲緩存失效,當數據被從新綁定到 GridView 時,它是從 BLL 中得到的。
注意 : 本文附帶的下載中提供的 緩存層 並不完整。它只包含了一個類 ,ProductsCL ,該類 只有少數幾個方法。此外 , 只有一個ASP.NET 頁面使用了 CL (~/Caching/FromTheArchitecture.aspx) , 全部其它頁面都仍是直接調用 BLL 。若是打算在您的應用程序中使用 CL , 那麼 表示層 的全部調用都應該是對 CL 的調用, 這就須要 CL 的類和方法要涵蓋 表示層 當前使用的 BLL 中的類和方法。
雖然使用ASP.NET 2.0 的 SqlDataSource 和ObjectDataSource 控件 , 能夠在 表示層進行 緩存 , 但理想的作法是由架構中的單獨一層來承擔緩存任務。在本教程中 , 咱們建立了一個 緩存層,該層位 於 表示層 與 業務邏輯層 之間。 對於BLL 中已有的由 表示層 調用的類和方法 ,緩存層應該 提供與之相同的一組類與方法。
咱們在本教程與前面教程中探討的 緩存層的 例子都展現了應激裝載。對於應激裝載 , 僅當請求了數據 , 而且緩存中沒有這個數據時 , 纔會將數據裝載進緩存中。數據也能夠預裝載進緩存,該技術會在實際須要數據以前就將數據裝載進緩存。在下一篇教程中,咱們將看到預裝載的例子,在那時咱們將看到怎樣在應用程序啓動時將靜態值存儲到緩存中。
快樂編程!
出處:http://www.cnblogs.com/codecrazy/archive/2010/10/14/1851934.html