ABP理论学习之数据传输对象(DTO)

本文涉及的产品
数据传输服务 DTS,数据迁移 small 3个月
推荐场景:
MySQL数据库上云
数据传输服务 DTS,数据同步 small 3个月
推荐场景:
数据库上云
数据传输服务 DTS,数据同步 1个月
简介:

本篇目录

DTO用于应用层和 展现层间的数据传输。

展现层调用具有DTO参数的应用服务方法,然后应用服务使用领域对象来执行一些特定的业务逻辑,最后返回给展现层一个DTO。因此,展现层完全独立于领域层。在一个理想的分层应用中,展现层不直接和领域对象打交道(仓储,实体...)。

为何需要DTO

为每个应用服务方法创建一个DTO起初可能被看作是一项乏味而又耗时的事情。但如果正确地使用它,那么DTOs可能会拯救你应用。为啥呢?

领域层抽象

DTO为展现层抽象领域对象提供了一种有效方式。这样,层与层之间就正确分离了。即使你想完全分离展现层,仍然可以使用已存在的应用层和领域层。相反,只要领域服务的契约(方法签名和DTOs)保持不变,即使重写领域层,完全改变数据库模式,实体和ORM框架,也不需要在展现层做任何改变。

数据隐藏

试想你有一个User实体,包含Id,Name,EmailAddress和Password字段。如果UserAppService的GetAllUsers()方法返回一个List,即使你没有在屏幕上显示它,那么任何人也都能看到所有user的密码。它不是涉及安全的,而是与数据隐藏相关的。应用服务都应该返回给展现层需要的,不要更多,也不很少,要的是恰到好处。

序列化和懒加载问题

当返回给展现层一个对象时,它很可能在某个地方序列化。比如,一个MVC方法返回JSON,一个对象会被序列化成JSON,然后发送到客户端。在那种情况,将一个实体返回到展现层是有问题的。这是怎么回事呢?

在一个真实应用中,实体之间是相互引用的。User实体可能有一个Role的引用。因此,如果你想序列化User,那么Role也会序列化。而且,如果Role有一个List且Permission类有一个PermissionGroup类的引用等等。你能想象所有的对象都会被序列化的那种场景吗?你可能会意外地序列化整个数据库。那么解决方案是什么呢?把属性标记为NonSerilized吗?不,你可能不知道它何时应该序列化,何时不应该。它可能在一个应用方法中需要,可能在另一个就不需要了。因此,在这种情景中,设计一个可安全序列化的,特别设计的DTOs是一种好的选择。

几乎所有的ORM框架都支持懒加载。它的特征是当需要时才从数据库中加载实体。假如说User类有一个Role类的引用。当从数据库中获得一个User时,此时Role属性还没有填充,当第一次读该Role属性时,它才从数据库中加载。因此,不要将这样的一个实体直接返回给展现层,它可能会轻易造成从数据库检索额外的实体。如果序列化工具读到了该实体,它会递归地读取所有属性,最终整个数据库可能会被检索(如果实体间有合适的关系)。

在展现层使用实体还会有更多的问题。最好压根不要在将包含领域(业务)层的程序集引用到展现层上。

DTO惯例和验证

ABP高度支持DTOs,它提供了一些符合惯例的类和接口,并且对于DTO的命名和用法提出了一些建议。当按照下面描述的那样编写代码时,ABP会轻易地自动处理一些事情。

举个例子

让我们看一个完整的例子。假如我们想要开发一个应用服务方法,作用是使用一个名字来搜索人,并返回一个人的集合。这种情况下,我们可能会有一个如下的Person实体:

public class Person : Entity
{
    public virtual string Name { get; set; }
    public virtual string EmailAddress { get; set; }
    public virtual string Password { get; set; }
}

首先,我们定义一个应用服务的接口:

public interface IPersonAppService : IApplicationService
{
    SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ABP建议将input/output参数命名为MethodNameInput和 MethodNameOutput,并为每个应用服务方法定义一个单独的input和output DTO。即使你的方法只需要或返回一个参数,最好也创建一个DTO类。这样,你的代码回更具有扩展性。以后你可以添加更多的属性而不用改变方法的签名,而且也不用使已存在的客户端应用发生重大变化。

当然,如果你的方法没有返回值,那么方法可以返回void。如果以后添加了一个返回值,也不会打破已存在的应用。如果你的方法不需要任何参数,那么你也不必定义一个输入DTO。但是如果未来很可能添加参数,那么也许最好还是编写一个输入DTO。这取决于你。

让我们看一下为这个例子定义的输入和输出的DTO:

public class SearchPeopleInput : IInputDto
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }
}

public class SearchPeopleOutput : IOutputDto
{
    public List<PersonDto> People { get; set; }
}

public class PersonDto : EntityDto
{
    public string Name { get; set; }
    public string EmailAddress { get; set; }
}

验证:按照惯例,输入DTO实现了 IInputDto接口,输出DTO实现了 IOutputDto接口。当实现了IInputDto时,ABP会在方法执行前自动验证输入。这和ASP.NET MVC的验证很相似,但是注意应用服务不是控制器,它是纯粹的C#类。ABP使用拦截来自动检查输入。关于更多的验证,请看下篇DTO验证。

EntityDto是一个声明了Id属性的简单类。因为这对于所有的实体都是公用的。如果你的实体的主键不是int的,那么还有一个泛型版本。PersonDto不包含Password属性,因为表现层不需要。甚至将所有人的密码都发送到展现层可能是很危险的。想象一下,如果Javascript客户端发送请求,任何人就会轻易地抓取到所有的密码。

接下来进一步实现之前的IPersonAppService

public class PersonAppService : IPersonAppService
{
    private readonly IPersonRepository _personRepository;

    public PersonAppService(IPersonRepository personRepository)
    {
        _personRepository = personRepository;
    }

    public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
    {
        //Get entities
        var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

        //Convert to DTOs
        var peopleDtoList = peopleEntityList
            .Select(person => new PersonDto
                                {
                                    Id = person.Id,
                                    Name = person.Name,
                                    EmailAddress = person.EmailAddress
                                }).ToList();

        return new SearchPeopleOutput { People = peopleDtoList };
    }
}

我们从数据库中获得实体,再将它们转成DTOs,然后返回到输出。注意我们没有验证输入,因为ABP会自动验证。ABP甚至会检查输入参数是否为null,如果为null,就会抛出异常。

但是很可能你不喜欢从一个Person实体到一个PersonDto对象的转换代码。这是相当无聊的事情,而且,Person实体可能会有更多的属性。

DTO和实体的自动映射

幸好,我们有工具可以让这个变得很简单。AutoMapper就是之一(要学习AutoMapper,请看我的AutoMapper系列教程。它已经发布到Nuget上了,你可以轻松地将它添加到项目中。让我们再次写一下SearchPeople方法,但是这次是用AutoMapper:

public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
    var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));
    return new SearchPeopleOutput { People = Mapper.Map<List<PersonDto>>(peopleEntityList) };
}

这样就ok了。你可以给实体和DTO添加更多的属性而不需要转换代码做任何改变。唯一要做的事情就是在使用前定义一个映射:

Mapper.CreateMap<Person, PersonDto>();

AutoMapper创建了映射代码。这样,动态映射就不会成为性能问题了。它既快速又容易。AutoMapper为Person实体创建了PersonDto,并使用命名规范赋予DTO属性。命名规范可能是复杂的且可配置的。此外,你还可以定义自定义映射以及更多。

使用特性和扩展方法进行映射

ABP提供了若干特性和扩展方法来定义映射。首先,要将Abp.AutoMapper nuget包添加到项目中。然后,AutoMap特性是双向映射方式, AutoMapFrom和 AutoMapTo是单向映射方式。最后,使用MapTo扩展方法将一个对象映射到另一个对象。映射定义的例子如下:

[AutoMap(typeof(MyClass2))] //定义双向映射
public class MyClass1
{
    public string TestProp { get; set; }
}

public class MyClass2
{
    public string TestProp { get; set; }
}

定义了上面的代码之后,就可以使用MapTo扩展方法映射它们了:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = obj1.MapTo<MyClass2>(); //从obj1的副本创建一个新的MyClass2对象

上面的代码从MyClass1的对象创建了MyClass2一个新的对象。此外,你可以像下面那样,映射到一个已存在的对象:

var obj1 = new MyClass1 { TestProp = "Test value" };
var obj2 = new MyClass2();
obj1.MapTo(obj2);

帮助接口

ASP.NET 提供了一些实现标准化公共DTO属性名称的帮助接口。

ILimitedResultRequest定义了 MaxResultCount属性。这样你就可以在你的输入DTO中实现它来标准化有限的结果集。

IPagedResultRequest通过添加了 SkipCount扩展了 ILimitedResultRequest。这样,我们可以在SearchPeopleInput中为分页显示实现这个接口:

public class SearchPeopleInput : IInputDto, IPagedResultRequest
{
    [StringLength(40, MinimumLength = 1)]
    public string SearchedName { get; set; }

    public int MaxResultCount { get; set; }
    public int SkipCount { get; set; }
}

对于一个分页请求的结果,你可以返回一个实现了IHasTotalCount的输出DTO。命名标准化帮助我们创建可重复使用的代码和惯例。你也可以在 Abp.Application.Services.Dto命名空间下看到其他的接口和类。






本文转自tkbSimplest博客园博客,原文链接:http://www.cnblogs.com/farb/p/ABPDTO.html,如需转载请自行联系原作者

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
Sqoop 企业级大数据迁移方案实战
Sqoop是一个用于在Hadoop和关系数据库服务器之间传输数据的工具。它用于从关系数据库(如MySQL,Oracle)导入数据到Hadoop HDFS,并从Hadoop文件系统导出到关系数据库。 本课程主要讲解了Sqoop的设计思想及原理、部署安装及配置、详细具体的使用方法技巧与实操案例、企业级任务管理等。结合日常工作实践,培养解决实际问题的能力。本课程由黑马程序员提供。
目录
相关文章
|
5月前
|
存储 网络协议 网络安全
逆向学习网络篇:数据传输和交换过程
逆向学习网络篇:数据传输和交换过程
52 0
|
缓存 开发工具 数据安全/隐私保护
【软考学习17】数据传输控制方式、虚设备和微内核操作系统
【软考学习17】数据传输控制方式、虚设备和微内核操作系统
167 0
|
存储 设计模式 开发框架
Dto数据传输对象
Dto数据传输对象
Dto数据传输对象
|
JSON 前端开发 数据格式
地图集web项目_技术学习(一)_前后端数据传输(ajax)
地图集web项目_技术学习(一)_前后端数据传输(ajax)
147 0
|
Web App开发 前端开发 JavaScript
从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之十二 || 三种跨域方式比较,DTOs(数据传输对象)初探
更新反馈 1、博友@落幕残情童鞋说到了,Nginx反向代理实现跨域,因为我目前还没有使用到,给忽略了,这次记录下,为下次补充。 代码已上传Github+Gitee,文末有地址   今天忙着给小伙伴们提出的问题解答,时间上没把握好,都快下班了,赶紧发布:书说上文《从壹开始前后端分离【 .
1636 0
|
API 数据库 .NET
【Web API系列教程】3.5 — 实战:处理数据(创建数据传输对象)
现在,我们的Web API暴露数据库实体给客户端,而客户端接收直接映射到你的数据库表的数据。然而,这不永远都是个好办法。有时候你可以想要改变发送到客户端的数据的形式。
1027 0
|
网络协议 JavaScript
动手学习TCP:数据传输(转)
前面的文章介绍了TCP状态变迁,以及TCP状态变迁图中的一些特殊状态。 本文主要看看TCP数据传输过程中需要了解的一些重要点: MSS(Maximum Segment Size) Seq号和Ack号的计算 TCP半连接 TCP数据传输实验 在开始介绍上面列出的内容之前,先看看实验程序的运行。
1291 0
|
6月前
|
SQL 分布式计算 监控
在数据传输服务(DTS)中,要查看每个小时源端产生了多少条数据
【2月更文挑战第32天】在数据传输服务(DTS)中,要查看每个小时源端产生了多少条数据
67 6
下一篇
无影云桌面