这里的实体类更倾向于数据传输对象(既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 对象适用于本建议。