【五分钟的dotnet】是一个利用您的碎片化时间来学习和丰富.net知识的博文系列。它所包含了.net体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.net知识等等。 5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。
一谈到如何在.Net中进行对象映射,可能大部分同学都会脱口而出:“使用AutoMapper!”。 是的,AutoMapper 是一个非常成熟的对象映射器。截至到写这篇文章,您能在Nuget上下载到的AutoMapper包的版本为:v9.0.0,而对应的 Github 的 star 已经高达7K。
对了,谈到AutoMapper就不得不谈起它的作者(之一):“JIMMY BOGARD”。也许您没有听过这个名字,但是您一定听过他的另一个作品:MediatR(在微软的官方示例EShop中也使用了MediatR)。同时,“JIMMY BOGARD” 也是提出“将领域事件附加在聚合根”上的人,为领域驱动设计(DDD)做出了很大的贡献。在微软官方文档中,您可以看到该处提及到了“JIMMY BOGARD”:
好吧,优秀的人总是优秀?。还是回到今天的正文,对象映射工具。当然,对于AutoMapper大家可能再熟悉不过了,而且它的知名度和热度也居高不下,看一看百度搜索结果就知道了:
然后再来看一看,咱们今天要介绍的主角:Mapster。 不知道有多少同学听过它?应该很少吧,这一点从百度搜索也可以看出来:
额………………好像差距有点大哈。而且在这些搜索结果中,有用的信息只有那么几条,其中能看的文章就只有一条,而且还是出自于博客园。 来自 “dudu” 大佬去年的一篇文章: EF Core 相关的千倍性能之差: AutoMapper ProjectTo VS Mapster ProjectToType。 再来看一看Github的情况,距离我写这一篇稿子的时候,Star数只有 518 个。
咱们先来回顾一下AutoMapper是怎么使用的:
现在有两个类,一个叫做MyEntity ,一个叫做 MyDto。 在咱们书写应用层代码的时候,将数据转换为Dto是很常见的一种操作,所以这也是我们需要对象映射器的原因。 假设,这两个类的结构是酱紫的:
public class MyEntity
{
public string Name { get; set; }
public int No { get; set; }
}
public class MyDto
{
public string Name { get; set; }
public int No { get; set; }
}
很普遍,也是很常见的类型。那么如果我们要用AutoMapper来完成两者之间的转换呢?
var configuration = new MapperConfiguration(cfg =>
{
cfg.CreateMap<MyEntity, MyDto>();
});
var mapper = configuration.CreateMapper();
var r = mapper.Map<MyDto>(new MyEntity() { Name = "xxx", No = 111 });
这是9.0的版本,如果您用过以前的版本可能会有点差异,比如老版本会使用Initialize方法来配置。但是思路都是一样的,也就是说,咱们需要先配置对象与对象之间的相互关系,然后创建一个Mapper,在.NET core中咱们一般会在Configura配置好之后,将mapper注册为一个单例,以后使用的话通过依赖注入就可以使用了。
是的,这种写法逻辑清晰没有一点问题。那么是什么契机让我选择放弃AutoMapper呢? 可能您会认为是性能问题,毕竟在上面 dudu 的那篇文章的标题真的很有吸引力。 但这只是很小的一部分原因。
当我在写一些库的时候,我需要用到对象转换的功能,如果自己造轮子写一个的话也不现实(可以看看AutoMapper的源码,里面有多少的表达式树写法?),所以我尝试引入第三方的映射工具,和大家一样我第一反应就是AutoMapper。但是在评估的时候,我发现:一般来说,mapper对象全局只需要一个,那么这个mapper对象是在我写的库中使用,还是交由用户来创建呢? 如果在库中创建,那么用户必须在使用库的时候进行配置,比如库公开一个委托来配置:
service.AddMyLibary(config=>
{
//config wrap automapper
})
就好比上面的写法。可能您现在正在使用的框架中就是使用了这种方式。 当然也不是说这样不好,但是我个人感觉很奇怪。
还有一点就是,AutoMapper必须要在进行了配置之后才能完成映射,如果我不提供配置的话,就是抛出一个异常。
所以,基于这两点,我就想有没有 1:简单的映射不需要配置 2:可以在任何地方进行配置 的对象映射工具。
是的,后来我采用了Mapster,很早之前就已听闻该工具,但是一直没有对比着使用过它。
如果将上面AutoMapper进行映射的代码修改一下,转换为Mapster的版本,是这样的:
var entity = new MyEntity() { Name = "xxx", No = 111 };
var r = entity.Adapt<MyDto>();
是的,没有看错,只有一句代码。(虽然我写了两句?)。 当我们安装了Mapster之后,object对象就会拥有一个 Adapt() 的扩展方法。只需要调用该方法就可以直接完成转换。对于简单的关系,我们根本都不需要进行配置。
那么对于复杂的映射呢? Mapster 提供了一个 TypeAdapterConfig<T,T> 的静态泛型类型来进行配置,所以我们可以在任何地方书写配置:
TypeAdapterConfig<MyEntity, MyDto>
.NewConfig()
.Map(s => s.Name, d => d.Name + "_Mapster");
就像这样,我们就完成了这组对象的复杂映射关系。(Name里面加上"_Mapster"后缀)。
当然,上面的例子只是一个很基础的类型,但是我们经常会遇到类型里面拥有另外的类型,这种嵌套关系能行吗? 所以我们把上面的实体进行更改:
public class MyDto
{
public string Name { get; set; }
public int No { get; set; }
public string UoyoName { get; set; }
}
public class MyEntity
{
public string Name { get; set; }
public int No { get; set; }
public ChildEntity UoyoCSharp { get; set; }
}
public class ChildEntity
{
public string Name { get; set; }
}
在MyEntity里面拥有一个ChildEntity的类型。 “什么?您问我为什么不好好命名,比如ChildEntity就命名为Child呀,为什么要命名成读不懂的东西。” 因为……您命名规范了,根本都不用写配置,Mapster会自动完成映射。 所以基于这个不规则的命名,我们只需要进行下面的配置就行了:
TypeAdapterConfig<MyEntity, MyDto>
.NewConfig()
.Map(s => s.UoyoName, d => d.UoyoCSharp.Name);
就是这么简单。那么其它的高级映射呢??? 请自行跳转自文档页查询。 因为本文不是教程篇所以就偷懒了哈。当然官方的文档也很少,只需要半个小时,可能您就学完了?。
最后,再来说一说大家很关心的一个问题吧:它和AutoMapper比较,性能有什么差距呢? 由于选型评估的时候我也并没有太考虑性能这个因素,所以就没有进行测试,但是在Github的说明页,官方给了一个测试比较:
好吧,差距相对来说还是挺大的。但是毕竟我没有进行确切的验证,也不会对它进行无脑吹。详细情况还请各位大佬自行测试。