1 寫在前面
此文主要參考了園子里以下兩篇文章:
黃聰,Microsoft Enterprise Library 5.0 系列(一) : Caching Application Block (初級)
顧磊,[EntLib]微軟企業庫5.0 學習之路——第四步、使用緩存提高網站的性能(EntLib Caching)
2 前面兩篇博文寫的很好,很全面,為何還需要本文?
大家可以點進去看下前面的文章,黃聰寫的是企業庫Cache的基本用法,顧磊的文章比較深入,而且自定義了CacheHelper類,實用性更強,我也抄襲了這個類(^_^ )。
我寫此文的目的主要是記錄下如果在項目中引入操作Cache、緩存哪些內容及最后的單元測試等~
主要是在緩存哪些數據的問題上,可能和顧磊的文章有些不同。
3 項目中引入Cache
首先從微軟網站下載並安裝Enterprise Library 5.0, 我這里Cache主要用在DataAccess這個項目中,是一個類庫項目,所以Config的東西先不用配置,直接添加Microsoft.Practices.EnterpriseLibrary.Caching.dll的引用,
然后添加CacheHelper類,代碼:

using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using Microsoft.Practices.EnterpriseLibrary.Caching.Expirations;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using System.Reflection;
namespace DataAccess
{
public static class CacheHelper
{
private static ICacheManager cache = CacheFactory.GetCacheManager();
/// <summary>
/// Add Cache
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="isRefresh"> if true, should reload data every 5 mins; default value = false </param>
public static void Add( string key, object value, bool isRefresh)
{
if (isRefresh)
cache.Add(key, value, CacheItemPriority.Normal, new ATSCacheItemRefreshAction(), new AbsoluteTime(TimeSpan.FromMinutes( 5)));
else
cache.Add(key, value);
}
// Summary:
// Adds new CacheItem to cache. If another item already exists with the same
// key, that item is removed before the new item is added. If any failure occurs
// during this process, the cache will not contain the item being added. Items
// added with this method will be not expire, and will have a Normal Microsoft.Practices.EnterpriseLibrary.Caching.CacheItemPriority
// priority.
//
// Parameters:
// key:
// Identifier for this CacheItem
//
// value:
// Value to be stored in cache. May be null.
//
// Exceptions:
// System.ArgumentNullException:
// Provided key is null
//
// System.ArgumentException:
// Provided key is an empty string
//
// Remarks:
// The CacheManager can be configured to use different storage mechanisms in
// which to store the CacheItems. Each of these storage mechanisms can throw
// exceptions particular to their own implementations.
public static void Add( string key, object value)
{
Add(key, value, false);
}
public static object Get( string key)
{
return cache.GetData(key);
}
public static void Remove( string key)
{
cache.Remove(key);
}
}
[Serializable]
public class ATSCacheItemRefreshAction : ICacheItemRefreshAction
{
#region ICacheItemRefreshAction Members
public void Refresh( string removedKey, object expiredValue, CacheItemRemovedReason removalReason)
{
// when expired, reload it.
if (removalReason == CacheItemRemovedReason.Expired)
{
ICacheManager c = CacheFactory.GetCacheManager();
c.Add(removedKey, expiredValue);
}
}
#endregion
}
4 緩存數據
在顧磊的文章中,他主要緩存一些Class,如ClassInfoService、StudentService等,代碼如下:

/// 通用對象反射(包含緩存)
/// </summary>
/// <param name="className"> 要反射的類名 </param>
/// <returns></returns>
public static T CreateObject( string className)
{
var typeName = assemblyString + " . " + className;
// 判斷對象是否被緩存,如果已經緩存則直接從緩存中讀取,反之則直接反射並緩存
var obj = (T)CacheHelper.GetCache(typeName);
if (obj == null)
{
obj = (T)Assembly.Load(assemblyString).CreateInstance(typeName, true);
CacheHelper.Add(typeName, obj, true);
}
return obj;
}
public static IStudentService CreateStudent()
{
string typeName = assemblyString + " .StudentService ";
if (CacheHelper.GetCache(typeName) != null)
{
return (IStudentService)CacheHelper.GetCache(typeName);
}
else
{
IStudentService service = (IStudentService)Assembly.Load(assemblyString).CreateInstance(typeName, true);
CacheHelper.Add(typeName, service, true);
return service;
}
而像StudentService這種Class,如果New StudentService()這樣一個實例的話,所占的CPU和內存都是很小的,我覺得更有必要的是緩存數據,從數據庫中查詢回來的數據。
我們看下顧磊代碼中如何操作數據的:
class StudentManage:

public Student SelectById( int id)
{
return studentService.SelectById(id);
class StudentService:

/// 根據學生ID查詢學生對象
/// </summary>
/// <param name="id"> 學生ID </param>
/// <returns></returns>
public Student SelectById( int id)
{
Student student = null;
Database db = DBHelper.CreateDataBase();
StringBuilder sb = new StringBuilder();
sb.Append( " select * from Student ");
sb.Append( " where ID=@ID ");
DbCommand cmd = db.GetSqlStringCommand(sb.ToString());
db.AddInParameter(cmd, " @ID ", DbType.Int32, id);
using (IDataReader reader = db.ExecuteReader(cmd))
{
if (reader.Read())
{
student = new Student()
{
Id = reader.GetInt32( 0),
ClassId = reader.GetInt32( 1),
Sid = reader.GetString( 2),
Password = reader.GetString( 3),
Name = reader.GetString( 4),
Sex = reader.GetInt32( 5),
Birthday = reader.GetDateTime( 6),
IsAdmin = reader.GetInt32( 7)
};
}
}
return student;
大家從上面的代碼可以看出,緩存中存放了StudentService這個類,但是在SelectById(int id)這個函數中,並沒有緩存任何東西,還是每次從數據庫中查詢數據,這樣設計緩存,對程序性能的提升是十分有限的。
5 我程序中如何緩存數據
我還是用上面顧磊的代碼吧,那個好理解。然后我將每次查詢到的數據緩存起來,如果下次查詢,先從緩存中取數據,有則自動返回數據;沒有,則從數據庫中查詢,然后添加到緩存中,緩存有自動的數據過期機制,過期的數據會自動刪除。

{
Student student = (Student)CacheHelper.GetCache(id.ToString());
if (student == null)
{
student = SelectById(id);
CacheHelper.Add(id.ToString(), student);
}
return student;
這里可能上面的代碼不是十分恰當,但是為了讓大家更清楚我的意圖,及上面的代碼十分簡潔、易懂。
主要是我項目程序中有這樣的特情況,查詢參數是一個TableParameters,里面有很多字段:

{
public Guid? tableGuid { get; set; }
public string tableName { get; set; }
public string tableDesc { get; set; }
public Guid? tableTypeGuid { get; set; }
public Guid? schemeGuid { get; set; }
public Guid index1TypeGuid { get; set; }
public Guid? latestTableVersionGuid { get; set; }
public Guid? tableFormatGuid { get; set; }
public Guid? index2TypeGuid { get; set; }
在第一次會通過一個條件,查詢得到一個List,之后會直接傳輸一個Guid過來,而這個guid往往會包含在上面的結果中,所以第一件會將List緩存,詳細代碼:

{
TableParameters param = (TableParameters)t;
List<tblTable> query = new List<tblTable>();
if (param.tableGuid != null)
{
tblTable result = (tblTable)CacheHelper.Get(GuidTable + param.tableGuid.Value.ToString());
if (result != null)
{
query.Add(result);
return query;
}
}
var _tableQuery = from c in db.tblTable.Expand( " TableType ").Expand( " TableFormat ").Expand( " Scheme ").Expand( " Index1Type ").Expand( " Index2Type ").Expand( " LatestTableVersionGUID ")
select c;
if (param.tableGuid != null)
{
_tableQuery = _tableQuery.Where(n => n.TableGUID == param.tableGuid.Value);
}
if (! string.IsNullOrEmpty(param.tableName))
{
_tableQuery = _tableQuery.Where(n => n.TableName.Contains(param.tableName));
}
if (param.tableTypeGuid != null)
{
_tableQuery = _tableQuery.Where(n => n.TableType.TableTypeGUID == param.tableTypeGuid.Value);
}
if (param.tableFormatGuid != null)
{
_tableQuery = _tableQuery.Where(n => n.TableFormat.TableFormatGUID == param.tableFormatGuid.Value);
}
if (param.schemeGuid != null)
_tableQuery = _tableQuery.Where(n => n.Scheme.SchemeGUID == param.schemeGuid.Value);
query = _tableQuery.ToList<tblTable>();
foreach ( var tb in query)
{
CacheHelper.Add(GuidTable + tb.TableGUID.ToString(), tb);
}
return query;
6 單元測試
這個單元測試不是測試顧磊代碼的,是我項目中測試TableManager的,因為顧磊的代碼更簡明,所以上面我還是貼出了他的代碼,至於單元測試,還是隨便貼個我項目的,因為顧磊那個我沒數據,沒法寫,呵呵~

/// A test for GetQueryList
/// </summary>
[TestMethod()]
public void GetQueryListTest()
{
TableParameters t = new TableParameters();
t.schemeGuid = new Guid( " 9C962C55-A598-40B8-A39B-11788161A9D8 ");
List<tblTable> actual;
actual = ManagerFactory.TableManager.GetQueryList(t);
Assert.AreEqual( " Sedgwick Marsh- B & pre2000-NRA65 ", actual[ 0].TableName);
// Assert.Inconclusive("Verify the correctness of this test method.");
7 總結
我認為緩存數據不僅僅要緩存Class類,更要緩存從數據庫中查詢過來的數據,這樣才能最大限度的提示程序性能。
8 聲明
以上純為技術交流,對顧磊、黃聰等技術牛人十分敬佩,文中也引用了他們的文中和大量代碼,再次感謝。