這裡的實體類更傾向於數據傳輸對象(既DTO)。無論是編碼風格采用 事務腳本 還是 領域模型,我們都會遇到各種各樣的數據傳輸對象,尤其是傳統事務腳本三層架構的編碼中,更會遇到各類實體對象,一般來說,這些實體對象產生的原因如下:
1:為各類報表和查詢服務的聯表查詢,會導致字段變多,帶來的實體的屬性增多。怎麼辦,創造新的實體類或者為原有實體類新增字段?
2:前台不同的需求場合,會構建不同的 DTO 對象(如JSON對象)傳遞到後台,新的對象產生就創造新的實體類或者為原有實體類新增字段?
我們大多會遇到以上這類問題。以下是避免創建新的實體類或者為原有實體類新增字段的一些措施。
先來看第一種情況。
一:聯表查詢中的字段增多
這是從裡(數據層)發散到外(UI層)的過程。
以往我們像這樣編碼:
public override IList<User> GetList()
{
string sql = "select * from [EL_Organization].[User] where state=1";
var ds = SqlHelper.ExecuteDataset(
SqlHelper.ConnectionString,
CommandType.Text,
sql);
return DataTableHelper.ToList<User>(ds.Tables[0]);
}
現在,我們利用匿名類型:
public IEnumerable GetList2()
{
string sql = "select * from [EL_Organization].[User] where state=1";
var ds = SqlHelper.ExecuteDataset(
SqlHelper.ConnectionString,
CommandType.Text,
sql);var oblist = new List<object>();
foreach (DataRow row in ds.Tables[0].Rows)
{
oblist.Add(new { Id = row["Id"], Name = row["Name"] });
}return oblist;
}
你可能注意到兩點變化,
第一:我們原先使用泛型+反射的方法(通用方法,不贅述),直接返回了 IList<User>,而現在,我們沒有這樣的通用方法了,只能通過一個 foreach 循環自己來構建這樣的匿名類型及其列表。你可能會首先擔心循環中的那些代碼繁瑣而機械,但是這一定比創建一個新類型來的簡單。其次,你可能會想到改造原先的泛型+反射方法,使其支持匿名類型,但當我寫完這個函數的時候,我有點擔心其效率,所以我仍舊推薦上面的寫法。
第二點變化:返回類型是一個 IEnumerable 了,替代了原先的 IList<User>。這仿佛犧牲了一些原先作為強類型的特性,但這些特性是可克服的。有一種做法是,將返回類型修改為 IList<dynamic>,不過這仍舊帶來一點點性能損耗,因此我仍舊建議,如無特殊必要(如:在業務層需要對返回結果進行賦值),返回 IEnumerable 已足夠。
現在,Dal 層已經是這樣,上次調用者該如何用,以 MVC 中的控制器為例,我們應該如下使用(備注,下面這個方法針對是上層返回類型是一個 IEnumerable ,如果返回 List<dynamic>,則參考“ExpandoObject對象的JSON序列化” ):
public JsonResult xTest()
{
UserDal dal = new UserDal();
var list = dal.GetList2();
return this.Json(list, JsonRequestBehavior.AllowGet);
}
你可能會覺得,這太簡單,直接從 DAL 層取出來,就丟給前台了。沒錯,在事務腳本編碼時,很多時候就是這麼簡單,即便仍舊要對 DAL 返回值做特殊處理,采用匿名類型也不會阻止我們什麼事情。
二:構建不同的對象(如JSON對象)傳遞到後台
我們知道,MVC 中的控制器,如果參數中帶了強類型的參數,則 MVC 引擎會自動前台 post 過來的 JSON 對象轉換為該強類型。如,我們後台是這樣的(即,在參數中聲明了強類型):
public class xTemp
{
public string XId { get; set; }
public string XName { get; set; }
}public JsonResult xTest(xTemp SomeData)
{
。。。
return this.Json(easyUiPages, JsonRequestBehavior.AllowGet);
}
以及前台是這樣的:
var SomeData = {
"xId": "xxx",
"xName": "zzz"
};
$.ajax({
type: "post",
data: SomeData,
url: "@ViewBag.Domain/home/xTest",
success: function (data) {
;
}
});
則我們的後台一定會解析得到這個對象。但是,如果我們將控制器中的 SomeData的聲明換成了 object 或者 dynamic,則我們什麼也不會得到。所以,如果不想創建新的類型,則有集中做法:
1:一一獲得對象的屬性
直接傳遞或再創建 dynamic 對象,然後將其傳遞到 bll 或者 dal 層進行處理;一般要處理的對象屬性少於3個,可這樣處理。
2:或者,還是老老實實創建實體吧
傳統我們至少有三種選擇:創建 ViewModel 或 領域實體 或 數據實體。
2.1ViewModel 是指在 UI 層的實體,默認在 UI 層創建一個 ViewModel 的文件夾進行放置,這些實體不發散到其它層。當然,如果一定要發散到下層,可以傳遞 dynamic;
2.2 領域實體,則指在業務邏輯層的實體,或者我們也稱之為業務實體。業務實體和業務類並行放置一起,上層(如 UI 層)可訪問,下層不可訪問;
2.3 數據實體,則指最底層的,連 DAL 層都能訪問的實體,一般用於映射數據庫表(聯表則使用導航屬性,如 EntityFramework 等使用的),但一些通用的實體類,也可放置在此層。備注:也可以將領域實體作為數據實體;
在一些小型應用中,往往 ViewModel 、領域實體、數據實體,統一歸並到實體層。總之,實體層的創建和放置必須非常明確,隨意創建並且隨意放置的實體類,會導致代碼膨脹並給重構帶來災難。
如果我們發現數據實體已經不夠用,這裡建議的路線圖是:
1:修改數據實體或創建新的數據實體;
2:如果數據實體不能動,或不想動,則創建 ViewModel;
3:如果發現業務類需要用到該實體,則把該 ViewModel 重構為 領域實體;
4:如果發現該實體要傳遞到 DAL 層,那麼我們有四種選擇,
4.1 手動轉為數據實體;
4.2 回到第一步,將實體重構為數據實體;
4.3 使用 dynamic 傳遞到 Dal 層;
4.4 將屬性作為參數傳遞;
創建多余的實體的做法,可以看到我們啰啰嗦嗦講了這麼多,如果你已經覺得很煩了,或者覺得自己根本掌握不了這個度,那麼我們的終極做法是下面的這個做法。
3:接受 JSON 字符串,反序列化為 dynamic
上面我們說到:將控制器中的 SomeData的聲明換成了 object 或者 dynamic,則我們什麼也不會得到。但是,我們可以這麼做,
前台的 data: SomeData ,換為:
data: "someData=" + JSON.stringify(SomeData)
注意,因為我們這裡實際傳遞的是字符串,所以不能聲明:
contentType: "application/json; charset=utf-8",
後台則為:
[SessionFilter]
public JsonResult XTest(string someData)
{
var jsSerializer = new JavaScriptSerializer();
var model = jsSerializer.Deserialize(somData, typeof(dynamic)) as dynamic;
//如果是列表,則像如下處理
//var models = jsSerializer.Deserialize(somData, typeof(List<dynamic>)) as List<dynamic>;省略
return this.Json(result, JsonRequestBehavior.DenyGet);
}
這是建議的、減少實體類型的行之有效的方法。
三:另一種思路:構建通用的實體類
該思路的中心思想就是:消滅所有的實體類,任何傳統的實體無非就是:類型名+屬性名+屬性值,於是,我們就構建這樣的一個通用實體類,類似:
[Serializable]
public sealed class DynamicDalObject : DynamicObject, IDictionary<string, object>
{
public DynamicDalObject()
{
this._inner = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
}public DynamicDalObject(IEnumerable<KeyValuePair<string, object>> keyValuePairs) : this()
{
foreach (var keyValuePair in keyValuePairs)
{
this._inner.Add(keyValuePair);
}
}……
}
然後,發散到 DAL 層,就可以進行這樣的操作:
public static int Update(bool isOnline, int viewdCount, string Id)
{
using (var manger = new DynamicDalSqlManager())
{
return
manger.DataAccess.Update(
new DynamicDalObject(
new[]
{
new KeyValuePair<string, object>("id", Id),
new KeyValuePair<string, object>("IsOnline", isOnline),
new KeyValuePair<string, object>("ViewdCount", viewdCount),
}),
"EL_Course.CourseHistory",
new[] { "id" });
}
}
優點:
1:只要不想用實體類,就用它解決;
2:可構建通用的 Dal,即,DAl 只要一個也就夠了;
缺點:
1:失去了面向對象結構,實際上變成了面向 SQL 編碼;
2:不停的拆箱、裝箱;
這種通用實體類的做法,在一些業務相對單一,可嘗試使用,能帶來短平快的效果。
四:總結
總之,應充分利用 匿名類型 和 dynamic 類型來減少無必要的或者模稜兩可的實體類的創建,最後,再明確一下建議的架構思路:
1:首先我們有數據實體層,該實體層可拓展;
2:若現有實體不能滿足需要,則從 DAL 到 UI,可使用匿名類型;
3:若現有實體不能滿足需要,則從 客戶端 到 控制器,可 JSON 字符串(非對象),在控制器中反序列化為 dynamic 類型;
4:最後,如發現某個業務方法被多個場景使用,可再將其 dynamic 對象重構成一個 數據實體(優先) 或 領域實體。為什麼數據實體優先呢,因為從業務層傳遞到 DAL 層,我們可以直接傳遞該強類型對象。
5:在領域模型中,DTO 對象適用於本建議。