本篇的主要議題如下:
1.設計DAL的基本操作
2.對基本的操作的進一步的思考
3.查詢對象的一些思考
1.設計DAL的基本操作
Richard認為:在設計一個架構或者Framework的時候,有幾點很重要:
a.總體把握的能力。
b.抽象的能力。
c.分析的能力
首先,從總體上來看,Richard認為DAL中最基本,而且最容易想到的方法就是CRUD(Create, Read, Update, Delete)四個操作。
於是Richard在草紙寫出了基本操作的名稱:
AddSingleDataEntity;
AddDataEntityList;
UpdateSingleDataEntity;
UpdateDataEntityList;
DeleteSingleDataEntity;
DeleteDataEntityList;
GetSingleDataEntiry;
GetDataEntityList;
上面列出的方法名字很長,其實Richard在思考這些方法的名稱的時候也參考了.NET設計規范中的一些建議:方法名稱要具有“自解釋性”,因為架構的設計最後還是給開發人員用的,所以方法的定義要一眼就看出它是干什麼的,而且規范的命名也可以大大的減少維護的成本。(可能這些名字的命名有點對規范的 “生搬硬套”,但是之後會慢慢的重構的)
從總體出發,已經定義出了基本的操作,那麼現在就開始一步步的分析,如何實現這些方法。
Richard開始思考第一個方法的實現,其實Richard心裡也清楚:其實到底哪個方法作為第一個來考慮也許很重要,但是在一切都不清楚之前起碼要拿一個來入手,而且隨著思考的深入,很多的問題都會慢慢的浮現,到時候一切就會明晰起來。
對於AddSingleDataEntity這個方法,首先就要考慮這個方法到底要把什麼添加到數據庫中,也就是說要考慮這個方法的參數,而且這個參數要足夠的“兼容”,因為之前Richard就是想設計出一個“以不變應萬變”的DAL。在考慮這個參數問題之前,首先Richard很清楚:在.NET數據訪問技術中,Linq,Entity Framework等ORM技術已經廣泛的應用,所以在設計DAL的時候要充分的考慮到現有的一些技術(盡量避免重新造輪子)。
而且在數據是如何被存入到數據庫中的以及數據是如何從數據庫中取出的,這個工作是完全可以交給這些ORM的,最後的結果就是:在DAL中只是操作這些 ORM的那些映射實體。基於這個想法, Richard就確定了:AddSingleDataEntity參數是一個數據實體。(本系列文章約定:數據實體,即DataEntity,就是ORM 對一個數據庫表進行映射後產生的實體和數據庫中的表一一對應,如在數據庫中有一張Employee表,Linq to Sql將會把它映射成為Employee的一個類,這個類就稱為數據實體)。因為這些方法最終是操作數據實體的,所以包含這些方法的接口名字就定義為IDataContext。
因為不同的表產生不同的數據實體,但是Richard還想使得AddSingleDataEntity這個方法可以接受任何的數據實體,所以此時很有必要對數據實體進行抽象。所以Richard想到了定義一個接口:IDataEntity,打算讓所有通過ORM生成的數據實體都繼承這個接口。而且 Richard還想到:
1.如果BLL直接引用DAL使用的,那麼IDataEntity可能會在BLL中出現的。
2.如果BLL通過repository去DAL中獲取數據,那麼到時候BLL可能都不會直接引用DAL,但是BLL最終還是得使用數據做事情,所以IDataEntity還是會在BLL中出現,所以,IDataEntity接口最好定義在一個公共的地方。
Richard決定新建一個Common的類庫,加入IDataEntity接口的定義,現在這個接口裡面什麼都沒有,只是一個標記而已,表明繼承這個接口的類就是數據實體類。
AddSingleDataEntity(IDataEntity dataEntity);
還有一點就是盡量的使用類型安全的方法,於是Richard把方法改成了范型方法:
AddSingleDataEntity<T>(T dataEntity) where T:IDataEntity,class,new();
至於T 的那些約束:T:IDataEntity,class,new(),是考慮到了Linq和EF中對數據實體的一些要求。
一般的Add方法都是返回添加是否成功,true或者false,方法再次改造:
bool AddSingleDataEntity<T>(T dataEntity) where T:IDataEntity,class,new();
然後Richard就寫出了上面列出的一些方法的定義:
bool AddSingleDataEntity<T>(T dataEntity) where T : class,IDataEntity, new();
bool AddDataEntityList<T>(List<T> dataEntityList) where T : class,IDataEntity, new();
bool DeleteDataEntityList<T>(List<T> dataEntityList) where T : class,IDataEntity, new();
bool DeleteSingleDataEntity<T>(T dataEntity) where T : class,IDataEntity, new();
bool UpdateSingleDataEntity<T>(T dataEntity) where T : class,IDataEntity, new();
bool UpdateDataEntityList<T>(List<T> dataEntityList) where T : class,IDataEntity, new();
至於GetDataEntityList,按照之前的查詢對象的想法,傳入一個IQuery的接口:
List<T> GetDataEntityList<T>(IQuery query)where T : class,IDataEntity, new();
2.對基本的操作的進一步的思考
確實,上面那些基本操作是沒有什麼問題的,現在Richard又考慮到了另外的一些問題,還是以AddSingleDataEntity方法為例:
a.有些時候,不僅僅要知道插入數據是否成功,而且還想返回新加入數據在數據庫中的主鍵信息來做其他的用途。怎麼辦?再來查詢一次?
b.如果插入失敗了,僅僅只是返回一個false嗎?可能其他的調用模塊想知道到底是發生了什麼異常而導致的插入失敗,而且其他的模塊對於發生的異常有自己的處理方法,所以AddSingleDataEntity要提供足夠的信息。
基於上面的思考,所以這個基本的操作方法不能只是簡單的返回一些簡單的值就完了。也就是說,這些方法要返回一個數據包:裡面包含很多信息,以便其他的調用模塊來使用這些信息,感覺有點像是C#事件中的eventArgs.
所以Richard在Common的那個類庫中加入一個對象,定義如下:
public class DataResult<T> where T : IDataEntity
{
public List<T> EntityList { get; set; }
public bool IsSuccess { get; set; }
public Exception Exception { get; set; }
public object CustomData { get; set; }
}
這個類最後將會作為方法的返回結果。
Richard發覺,上面的方法確實是很有”自解釋性”,但是很多的開發人員對這些CRUD操作的名字都很熟悉了,而且最後開發的出來的架構的使用者還是開發人員,應該符合他們的習慣,所以Richard改變了方法的名字,改變後的版本如下:
public interface IDataContext
{
IList<T> Query<T>(IQuery queryCondition) where T : class,IDataEntity, new();
IList<T> Query<T>(IQuery queryCondition, int pageIndex, int pageCount) where T : class,IDataEntity, new();
// CRUD services (well, mostly CUD)
T Add<T>(T item) where T : class,IDataEntity, new();
IList<T> Add<T>(List<T> itemList) where T : class,IDataEntity, new();
bool Delete<T>(List<T> itemList) where T : class,IDataEntity, new();
bool Delete<T>(T item) where T : class,IDataEntity, new();
T Update<T>(T item) where T : class,IDataEntity, new();
List<T> Update<T>(List<T> itemList) where T : class,IDataEntity, new();
}
3.查詢對象的一些思考
在上面的基本的操作中,在Query 方法中傳入的是查詢對象,因為查詢對象是基於解釋器的,可以在解釋完查詢對象之後就緩存起來。以後再次查詢的時候,如果兩個查詢的對象一樣(例如在構造查詢對象時候,可以為其定義一個唯一的標示),那麼就不用再去對查詢對象進行解釋。如果根據這個查詢對象的查出的數據都是只讀的,那就更好了,就可以把查詢對象和對應的結果一起緩存起來。