首页 > 代码库 > 减少到处衍生的实体类

减少到处衍生的实体类

这里的实体类更倾向于数据传输对象(既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 = http://www.mamicode.com/{
    "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="http://www.mamicode.com/+ 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 对象适用于本建议。