對於「幾乎不變更」的參考資料 (如縣市代碼、幣別、產品分類),每次請求都查詢資料庫是嚴重浪費。本文介紹利用 static readonly 欄位 + static constructor 在**應用程式域啟動時**一次性從資料庫載入到記憶體,後續存取零資料庫開銷。
static readonly:欄位只能在宣告或靜態建構函式指派,保證不可變性。static constructor:在**第一次存取該類別的任何靜態/實例成員前**自動執行**一次**,執行緒安全由 CLR 保證。
using System;
using System.Collections.Generic;
using System.Data;
public static class CertificateCache
{
// 公開靜態唯讀集合,供全系統直接存取
public static readonly IReadOnlyList CertificateTypes;
public static readonly IReadOnlyDictionary> CertificateDictionary;
// 資料存取介面 (可注入測試用 Mock)
private static readonly ICertificateRepository _repository = RepositoryFactory.CreateCertificateRepository();
// 靜態建構函式:CLR 保證只執行一次,執行緒安全
static CertificateCache()
{
// 1. 載入所有證書類型
var types = _repository.GetAllCertificateTypes();
CertificateTypes = types.AsReadOnly();
// 2. 依類型分組載入明細,建立字典
var dict = new Dictionary>();
foreach (var type in types)
{
var items = _repository.GetItemsByTypeId(type.Id);
dict[type.Id] = items.AsReadOnly() as List; // 簡化轉型
}
CertificateDictionary = dict.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList)kvp.Value);
}
// 供管理工具呼叫:重新載入 (需重新啟動應用程式域或重新部署)
public static void Refresh()
{
// 靜態欄位無法重新指派,實際上需重啟 AppDomain / IIS / 容器
// 此處僅示意,實際可改用非 readonly 靜態欄位 + 鎖定實作熱重載
throw new NotSupportedException("靜態快取需重啟應用程式才能更新。請參考熱重載模式。");
}
}
// 使用方式:零資料庫查詢
public class CertificateService
{
public IEnumerable GetItemsByType(int typeId)
{
if (CertificateCache.CertificateDictionary.TryGetValue(typeId, out var items))
return items;
return Enumerable.Empty();
}
}
| 優點 | 缺點 |
|---|---|
| 極致效能:後續存取純記憶體,無鎖、無 IO | 資料更新需**重啟應用程式** (IIS Reset / 容器重啟 / 部署新版) |
| 程式碼極簡:無快取過期、淘汰、同步複雜度 | 啟動時間變長 (資料量大時明顯) |
| CLR 保證執行緒安全、單次執行 | 不適合頻繁變動資料 (如庫存、即時價格) |
| 型別安全:強型別集合,編譯期檢查 | 單元測試難以隔離 (靜態相依),建議搭配 Repository 介面抽象 |
若以上全勾,此模式為最佳選擇;否則請考慮 MemoryCache、IDistributedCache (Redis) 等支援過期/更新機制的快取方案。
public static class HotReloadCache
{
private static volatile IReadOnlyDictionary _cache;
private static readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
private static readonly Func> _loader;
static HotReloadCache(Func> loader)
{
_loader = loader;
Reload();
}
public static IReadOnlyDictionary Data
{
get
{
_lock.EnterReadLock();
try { return _cache; }
finally { _lock.ExitReadLock(); }
}
}
public static void Reload()
{
var newData = _loader();
_lock.EnterWriteLock();
try { _cache = newData; }
finally { _lock.ExitWriteLock(); }
}
}
此版本支援運行時呼叫 Reload() 更新資料,無需重啟,適合「偶爾變動但需高效能讀取」的場景。