淺談C#中的延遲加載(1)——善用委托
很久以前就聽過延遲加載這個東西,不過沒有理解是什么意思,現在算是了解一二了,寫點文章作為讀書筆記,把自己的想法記錄一下,希望對初學者幫助,不管是初學者或者高手如果發現文章那里寫得不好或者有更好的思路和做法記得告訴我哦^^。文章打算寫成兩三篇,這個是第一篇。
在三層結構中我們通常會使用多一個叫做模型層的東西,這一層中最主要做的事情是把數據庫中的表 (或者其他數據源,例如xml或者自己定義的一種數據格式)轉成對應的類,例如有一個文章表,這時候在這一層就會有一個文章類;文章類的屬性對應著文章表的列,例如文章標題屬性對應文章標題列。 實體類和數據表一一對應是最簡單的情況,這時候實體類和實體類是各自獨立存在的,沒有出現相互引用的關系。 但是,幾乎每一個數據庫中的表都是存在關聯關系的(關系型數據庫),例如除了文章表之外,還會有一個文章分類表,假如說每一篇文章都必須屬于一個分類,那么在數據庫中表現出來的就是文章表中有一個外鍵字段指向文章分類表的主鍵 ,在C#代碼中表示出來的是文章類中有一個屬性(文章分類ID),通過這個屬性我們就可以知道文章所屬的分類、并且可以準確地通過代碼查詢數據庫,獲取一個文章分類實體類的對象,讀取到文章所屬分類的相關信息。
以上的過程看起來一點問題都沒有,整理一下思路,就是讀取數據庫,獲取一個文章類對象,通過文字類對象中的文章分類ID的值,以這個值為查詢條件去數據庫中讀取數據,獲取一個文章分類對象,當然對數據庫的操作我們通常 是封裝在數據訪問層中。然而從面向對象的角度考慮,我們會希望從文章類包含有文章分類的信息,用代碼表示文章分類和文章兩個實體類如下:
從上面的代碼可以看到,在文章實體類中出現了一個Model.ArticleCategory類型的屬性Category,我們想要的就是通過這個屬性直接讀取文章所屬分類的詳細信息。問題出現了,在數據庫訪問層中我們從數據庫中讀取數據去實例化一個文章實體類對象之后, 要選擇在什么時候去給Category賦值。
- 選擇一:立刻給通過分類ID(CategoryID屬性)去獲取所屬文章分類的對象,然后塞給文章所屬分類屬性(Category) ,然后再返回文章對象。這種方法在有一點不好,就是萬一得到文章對象之后根本不用去使用到Category屬性顯然這種做法不佳。
- 選擇二:在需要的時候再去讀取文章分類,然后給文章類對象的Category屬性賦值,但這和沒有這個屬性其實也沒有什么區別。
- 選擇三:在Category屬性的get訪問器中實現讀取數據庫獲取文章分類的代碼,這樣如果沒有使用到Category屬性的 時候是不會調用到這些代碼的,也就不會去訪問數據庫拿東西了,為了避免每次訪問Category屬性都去讀取數據庫, 我們給他增加一個所有字段,得到的代碼如下:
c#代碼
protected Model.ArticleCategory _category;
public Model.ArticleCategory Category
{
get
{
if(_category == null)
{
// 創建文章分類數據訪問層對象
Dal.ArticleCategory articleCategoryDal = new Dal.ArticleCategory();
// 獲取文章分類
_category = articleCategoryDal.GetArticleCategoryByCategoryID(CategoryID);
}
return _category;
}
// set訪問器就不需要了
}
乍看起來似乎沒問題,但要考慮一點,在三層結構中數據的傳輸靠的就是模型層,模型層處于三層之下, 換句話說,模型層不會去引用三層中的任何一層,而上面代碼中的GetArticleCategoryByCategoryID很顯然是在三層之中,也許是在業務邏輯層或者數據訪問層,所以...循環引用了,這種做法也不佳。如何實現對在文章類中對Category屬性的數據進行延時加載呢?整理思路,根據需求一步步分析:
- 首先:獲取到一個文章類對象的時候,只有在讀取了Category屬性才去訪問數據庫,不讀取是不訪問的
- 其次:讀取同一個文章類對象的Category屬性的時候只訪問一次數據庫
- 最后:在Category屬性的get訪問器中我們不能調用三層中的方法(嚴格說是不直接顯示調用)
換個角度思考,我們能不能在數據訪問層中讀取數據、初始化一個文章類對象之后給它一個方法,告訴它如果你要 獲取自己所屬分類信息(文章分類對象)的時候就調用這個方法來拿,不用的時候就不去調用了,免得多鏈讀取一次數據庫。 給它一個方法,也就是說把方法傳給它咯! 于是想到委托,我們可以在文章類中添加一個委托,這個委托的簽名和通過文章分類ID獲取文章分類對象 方法的簽名一致,在Category屬性的get訪問器中調用這個委托,這樣便解決了可以在get訪問器中調用到方法去訪問數據庫, 也自然實現了延時加載!于是修改實體類代碼如下:
namespace Model
{
// 文章分類實體類
public class ArticleCategory
{
public int CategoryID { get; set; }
public string CategoryName { get; set; }
}
// 文章實體類
public class Article
{
public int ArticleID { get; set; }
public string Title { get; set; }
public string Cotnent{ get; set; }
public DateTime CreateTime { get; set; }
public int CategoryID { get; set; }
// 文章所屬分類
protected Model.ArticleCategory _category;
public Model.ArticleCategory Category
{
get
{
if (_category == null)
{
if (CategoryLazyLoader != null)
{
_category = CategoryLazyLoader(CategoryID);
}
else
{
_category = null;
}
}
return _category ;
}
}
// 文章分類延時加載器(委托)
public Func<int, Model.ArticleCategory> CategoryLazyLoader { get; set; }
}
}
在文章讀取數據庫得到數據然后創建一個文章類對象之后,我們對CategoryLazyLoader進行賦值就OK了! 文章數據訪問類中獲取文章的方法大致如下:
// 根據文章ID獲取文章實體類對象
public Model.Article GetArticleById(int articleId)
{
// 從數據庫中取出數據,得到一個DateRow或者DateRader之類的東東然后初始化一個文章實體類對象
Model.Article article = ... // ...是代碼 - -!
// 創建文章分類數據訪問對象
Dal.ArticleCategory articleCategory = new Dal.ArticleCategory();
// 指定延時加載委托
article.CategoryLazyLoader = articleCategory.GetArticleCategoryById;
// 返回文章對象
return article;
}
通過上面方法得到的文章實體類對象中的Category屬性就是實現了延時加載的了!
文章寫得不短,不過說的東西很簡單,細想起來幾乎沒什么內容,一句話就是使用委托預先得到一個用于獲取文章分類的方法,在文章分類屬性的get選擇器中調用委托返回結果。好了,告訴負責編寫UI層代碼的同事,調用了業務邏輯層的方法去獲取文章實體類對象吧,! 已經幫你把文章分類給封裝加進去了,而且使用了延遲加載,怎么實現你就不用管,用就行了!于是這個人用的時候囧了,文章實體類對象里面有個委托...... 委托啊!!!干嘛用的!!!???啥意思!
先寫到這里了,下一篇文章再說一些怎么隱藏這個委托,了解延遲加載的同學應該也想到方法了...