ASP.NET 2.0 ObjectDataSource 內建快取功能雖方便,但將快取邏輯耦合在表現層,違反分層架構原則。本文示範建立獨立 快取層 (Caching Layer, CL),位於表現層與業務邏輯層 (BLL) 之間,統一管理快取策略、相依性、過期政策,實現關注點分離。
| 層級 | 傳統 ObjectDataSource 快取 | 獨立快取層 (CL) |
|---|---|---|
| 快取位置 | 表現層 (ASPX/ObjectDataSource) | 獨立 CL 類別庫 / App_Code/CL |
| 耦合度 | 緊密耦合頁面生命週期 | 鬆散耦合,可單元測試、可複用 |
| 快取策略 | 分散在各頁面宣告 | 集中在 CL 方法內,統一管理 |
| 相依性管理 | 較難協調跨頁面相依性 | 集中處理 SqlCacheDependency、檔案相依性 |
| 測試性 | 需啟動 Web 伺服器測試 | 純類別庫,易於單元測試 |
MySolution/
├── DAL/ # Data Access Layer (Typed DataSet / TableAdapter)
├── BLL/ # Business Logic Layer (ProductsBLL, CategoriesBLL)
├── CL/ # Caching Layer (新增)
│ ├── ProductsCL.cs
│ ├── CategoriesCL.cs
│ └── SuppliersCL.cs
├── WebApp/ # Presentation Layer (ASP.NET WebForms / MVC)
│ ├── App_Code/CL/ # 或編譯為獨立 DLL 參考
│ └── ...
└── Tests/ # Unit Tests (可直接測試 CL)
using System;
using System.Data;
using System.Web.Caching;
using BLL; // 參考業務邏輯層
namespace CL
{
public class ProductsCL
{
private readonly ProductsBLL _bll = new ProductsBLL();
private const string CacheKeyPrefix = "ProductsCL_";
///
/// 取得所有產品:優先快取,遺漏則查詢 BLL 並建立快取相依性
///
public DataTable GetProducts()
{
string cacheKey = CacheKeyPrefix + "AllProducts";
object cached = HttpRuntime.Cache[cacheKey];
if (cached != null)
return (DataTable)cached;
// 快取遺漏:呼叫 BLL 取得資料
DataTable dt = _bll.GetProducts();
// 建立快取相依性:資料庫變更自動失效 (需 SQL Server 2005+ 啟用 Service Broker)
// 此處示範檔案相依性,實務建議使用 SqlCacheDependency
CacheDependency dep = new CacheDependency(null); // 無實體相依性,僅時間過期
HttpRuntime.Cache.Insert(cacheKey, dt, dep,
DateTime.Now.AddMinutes(30), Cache.NoSlidingExpiration);
return dt;
}
///
/// 依類別編號取得產品
///
public DataTable GetProductsByCategoryID(int categoryID)
{
string cacheKey = $"{CacheKeyPrefix}Cat_{categoryID}";
object cached = HttpRuntime.Cache[cacheKey];
if (cached != null) return (DataTable)cached;
DataTable dt = _bll.GetProductsByCategoryID(categoryID);
HttpRuntime.Cache.Insert(cacheKey, dt, null,
DateTime.Now.AddMinutes(20), Cache.NoSlidingExpiration);
return dt;
}
///
/// 新增產品後使相關快取失效
///
public void InvalidateProductsCache()
{
// 簡單做法:移除所有前綴快取 (實務建議精確鍵值)
foreach (DictionaryEntry entry in HttpRuntime.Cache)
{
if (entry.Key.ToString().StartsWith(CacheKeyPrefix))
HttpRuntime.Cache.Remove(entry.Key.ToString());
}
}
}
}
<asp:ObjectDataSource ID="odsProducts" runat="server"
TypeName="CL.ProductsCL"
SelectMethod="GetProducts"
OldValuesParameterFormatString="original_{0}">
</asp:ObjectDataSource>
<asp:GridView ID="gvProducts" runat="server" DataSourceID="odsProducts" />
或程式碼後置直接呼叫:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
var cl = new CL.ProductsCL();
gvProducts.DataSource = cl.GetProducts();
gvProducts.DataBind();
}
}
// Global.asax Application_Start 啟用輪詢
SqlCacheDependencyAdmin.EnableNotifications("NorthwindConnectionString");
SqlCacheDependencyAdmin.EnableTableForNotifications("NorthwindConnectionString", "Products");
// CL 中使用
CacheDependency sqlDep = new SqlCacheDependency("NorthwindConnectionString", "Products");
HttpRuntime.Cache.Insert(cacheKey, dt, sqlDep,
Cache.NoAbsoluteExpiration, Cache.NoSlidingExpiration);
資料表異動時 SQL Server 通知 ASP.NET,快取自動失效,無需輪詢間隔延遲。
ICacheProvider 介面,實作 HttpRuntimeCacheProvider、RedisCacheProvider、MemoryCacheProvider,支援未來遷移分散式快取。{Entity}_{Method}_{ParametersHash} 格式,如 Products_GetByCategory_5,便於精確失效。CacheTag 機制,InvalidateByTag("Products") 一次清除相關所有快取。獨立快取層是分層架構成熟的標誌,將横切關注點從表現層剝離,讓 BLL 專注業務規則,CL 專注快取策略,表現層專注呈現邏輯。此模式平滑遷移至 ASP.NET Core IDistributedCache、Redis、MemoryCache 等現代基礎設施。