使用AutoMapper实现Dto和Model的*转换(下)

时间:2020-12-18 23:17:47

书接上文。在上一篇文章中我们讨论了使用AutoMapper实现类型间1-1映射的两种方式——Convention和Configuration,知道了如何进行简单的OO Mapping。在这个系列的最后一篇文章我想基于我们的需求讨论一些中级别的话题,包括:如何实现类型体型之间的映射,以及如何为两个类型实现多个映射规则。 
【四】将一个类型映射为类型体系 
先回顾一下我们的Dto和Model。我们有BookDto,我们有Author,每个Author有自己的ContactInfo。现在提一个问题:如何从BookDto得到第一个作者的Author对象呢?答案即简单,又不简单。 
最简单的做法是,使用前面提到的CountructUsing,指定BookDto到Author的全部字段及子类型字段的映射:

  1. var map = Mapper.CreateMap<BookDto,Author>();
  2. map.ConstructUsing(s => new Author
  3. {
  4. Name = s.FirstAuthorName,
  5. Description = s.FirstAuthorDescription,
  6. ContactInfo = new ContactInfo
  7. {
  8. Blog = s.FirstAuthorBlog,
  9. Email = s.FirstAuthorEmail,
  10. Twitter = s.FirstAuthorTwitter
  11. }
  12. });

这样的做法可以工作,但很不经济。因为我们是在从头做BookDto到Author的映射,而从BookDto到ContactInfo的映射是我们之前已经实现过的,实在没有必要重复再写一遍。设想一下,如果有一个别的什么Reader类型里面也包含有ContactInfo,在做BookDto到Reader映射的时候,我们是不是再写一遍这个BookDto -> ContactInfo逻辑呢?再设想一下如果我们在实现BookDto到Book的映射的时候,是不是又需要把BookDto到Author的映射规则再重复写一遍呢? 
所以我认为对于这种类型体系间的映射,比较理想的做法是为每个具体类型指定简单的映射,而后在映射复杂类型的时候再复用简单类型的映射。用简单点的语言描述: 
我们有A,B,C,D四个类型,其中B = [C, D]。已知A -> C, A -> D, 求A -> B。 
我的解法是使用AutoMapper提供的——IValueResolver。IValueResolver是AutoMapper为实现字段级别的特定映射逻辑而定义的类型,它的定义如下:

  1. public interface IValueResolver
  2. {
  3. ResolutionResult Resolve(ResolutionResult source);
  4. }

而在实际的应用中我们往往会使用它的泛型子类——ValueResolver,并实现它的抽象方法:

  1. protected abstract TDestination ResolveCore(TSource source);

其中TSource为源类型,TDestination为目标字段的类型。 
回到我们的例子,我们现在可以这样来映射BookDto -> Author:

  1. var map = Mapper.CreateMap<BookDto, Author>();
  2. map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.FirstAuthorName))
  3. .ForMember(d => d.Description, opt => opt.MapFrom(s => s.FirstAuthorDescription))
  4. .ForMember(d => d.ContactInfo,
  5. opt => opt.ResolveUsing<FirstAuthorContactInfoResolver>()));

在FirstAuthorContactInfoResolver中我们实现ValueResolver并复用BookDto -> ContactInfo的逻辑:

  1. public class FirstAuthorContactInfoResolver : ValueResolver<BookDto,ContactInfo>
  2. {
  3. protected override ContactInfo ResolveCore(BookDto source)
  4. {
  5. return Mapper.Map<BookDto, ContactInfo>(source);
  6. }
  7. }

一切就搞定了。 
类似的,我们现在也可以实现BookDto -> Book了吧?通过复用BookDto -> Author以及BookDto -> Publisher。 
真的可以吗?好像还有问题。是的,我们会发现需要从BookDto映射到两个不同的Author,它们的字段映射规则是不同的。怎么办?赶紧进入我们的最后一个议题。 
【五】为两个类型实现多套映射规则 
我们的问题是:对于类型A和B,需要定义2个不同的A -> B,并让它们可以同时使用。事实上目前的AutoMapper并没有提供现成的方式做到这一点。 
当然我们可以采用“曲线救国”的办法——为first author和second author分别定义Author的两个子类,比如说FirstAuthor和SecondAuthor,然后分别实现BookDto -> FirstAuthor和BookDto -> SecondAuthor映射。但是这种方法也不太经济。假如还有第三作者甚至第四作者呢?为每一个作者都定义一个Author的子类吗? 
另一方面,我们不妨假设一下,如果AutoMapper提供了这样的功能,那会是什么样子呢?CreateMap方法和Map方法应该这样定义:

  1. CreateMap<TSource, TDestination>(string tag)
  2. Map<TSource, TDestination>(TSource, string tag)

其中有一个额外的参数tag用于标识该映射的标签。 
而我们在使用的时候,就可以:

  1. var firstAuthorMap = Mapper.CreateMap<BookDto, Author>("first");
  2. // Define BookDto -> first Author rule
  3. var secondAuthorMap = Mapper.CreateMap<BookDto, Author>("second");
  4. // Define BookDto -> second Author rule
  5. var firstAuthor = Mapper.Map<BookDto, Author>(source, "first");
  6. var secondAuthor = Mapper.Map<BookDto, Author>(source, "second");

遗憾的是,这一切都是假如。但是没有关系,虽然AutoMapper关上了这扇门,却为我们留着另一扇门——MappingEngine。 
MappingEngine是AutoMapper的映射执行引擎,事实上在Mapper中有默认的MappingEngine,我们在调用Mapper.CreateMap的时候,是往与这个默认的MappingEngine对应的Configuration中写规则,在调用Mapper.Map获取对象的时候则是使用默认的MappingEngine执行其对应Configuration中的规则。 
简而言之一个MappingEngine就是一个AutoMapper的“虚拟机”,如果我们同时启动多个“虚拟机”,并且将针对同一对类型的不同映射规则放到不同的“虚拟机”上,就可以让它们各自相安无事的运行起来,使用的时候要用哪个规则就问相应的“虚拟机”去要好了。 
说做就做。首先我们定义一个MappingEngineProvider类,用它来获取不同的MappingEngine:

  1. public class MappingEngineProvider
  2. {
  3. private readonly MappingEngine _engine;
  4. public MappingEngine Get()
  5. {
  6. return _engine;
  7. }
  8. }

我们将不同类型的映射规则抽象为接口IMapping:

  1. public interface IMapping
  2. {
  3. void AddTo(Configuration config);
  4. }

然后在MappingEngineProvider的构造函数里将需要的规则放到对应的MappingEngine中:

  1. private static Dictionary<Engine,List<IMapping>> _rules=new Dictionary<Engine, List<IMapping>>();
  2. public MappingEngineProvider(Engine engine)
  3. {
  4. var config = new Configuration(new TypeMapFactory(), MapperRegistry.AllMappers());
  5. _rules[engine].ForEach(r=> r.AddTo(config));
  6. _mappingEngine = new MappingEngine(config);
  7. }

注意到这里我们用了一个枚举类型Engine用于标识可能的MappingEngine:

  1. public enum Engine
  2. {
  3. Basic = 0,
  4. First,
  5. Second
  6. }

我们用到了3个Engine,Basic用于放置所有基本的映射规则,First用于放置所有Dto -> FirstXXX的规则,Second则用于放置所有Dto -> SecondXXX的规则。 
我们还定义了一个放置所有映射规则的字典_rule,将规则分门别类放到不同的Engine中。 
剩下的事情就是往字典_rule里填充我们的mapping了。比如说我们把BookDtoToFirstAuthorMapping放到First engine里并把BookDtoToSecondAuthorMapping放到Second engine里:

  1. private static readonly Dictionary<Engine, List<IMapping>> _rules =
  2. new Dictionary<Engine, List<IMapping>>
  3. {
  4. {
  5. Engine.First, new List<IMapping>
  6. {
  7. new BookDtoToFirstAuthorMapping(),
  8. }
  9. },
  10. {
  11. Engine.Second, new List<IMapping>
  12. {
  13. new BookDtoToSecondAuthorMapping(),
  14. }
  15. },
  16. };

当然为了方便使用我们可以事先实例化好不同的MappingEngineProvider对象:

  1. public static SimpleMappingEngineProvider First = new MappingEngineProvider(Engine.First);
  2. public static SimpleMappingEngineProvider Second = new MappingEngineProvider(Engine.Second);

现在我们就可以在映射BookDto -> Book的时候同时使用这2个Engine来得到2个Author并把它们组装到字段Book.Authors里面了:

  1. public class BookDtoToBookMapping : DefaultMapping<BookDto, Book>
  2. {
  3. protected override void MapMembers(IMappingExpression<BookDto, Book> map)
  4. {
  5. map.ForMember(d => d.Authors,
  6. opt => opt.ResolveUsing<AuthorsValueResolver>());
  7. }
  8. private class AuthorsValueResolver : ValueResolver<BookDto, List<Author>>
  9. {
  10. protected override List<Author> ResolveCore(BookDto source)
  11. {
  12. var firstAuthor = SimpleMappingEngineProvider.First.Get().Map<BookDto, Author>(source);
  13. var secondAuthor = SimpleMappingEngineProvider.Second.Get().Map<BookDto, Author>(source);
  14. return firstAuthor.IsNull()
  15. ? secondAuthor.IsNull() ? new List<Author>() : new List<Author> {new Author(), secondAuthor}
  16. : secondAuthor.IsNull()
  17. ? new List<Author> {firstAuthor}
  18. : new List<Author> {firstAuthor, secondAuthor};
  19. }
  20. }
  21. }

最后,还记得我们在本节开始的时候提到的美好愿望吗?既然AutoMapper没有帮我们实现,就让我们自己来实现吧:

  1. public class MyMapper
  2. {
  3. private static readonly Dictionary<Engine, MappingEngine> Engines = new Dictionary<Engine, MappingEngine>
  4. {
  5. {Engine.Basic, MappingEngineProvider.Basic.Get()},
  6. {Engine.First, MappingEngineProvider.First.Get()},
  7. {Engine.Second, MappingEngineProvider.Second.Get()},
  8. };
  9. public static TTarget Map<TSource, TTarget>(TSource source, Engine engine = Engine.Basic)
  10. {
  11. return Engines[engine].Map<TSource, TTarget>(source);
  12. }
  13. }

一切又都回来了,我们可以这样:

  1. var firstAuthor = MyMapper.Map<BookDto,Author>(dto, Engine.First);
  2. var secondAuthor = MyMapper.Map<BookDto,Author>(dto, Engine.Second);

也可以这样了:

    1. var book = MyMapper.Map<BookDto,book>(dto);

使用AutoMapper实现Dto和Model的*转换(下)的更多相关文章

  1. 使用AutoMapper实现Dto和Model之间*转换

    应用场景:一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转 ...

  2. 使用AutoMapper实现Dto和Model的*转换

    AutoMapper是一个.NET的对象映射工具. 项目地址:https://github.com/AutoMapper/AutoMapper. 帮助文档:https://github.com/Aut ...

  3. 使用AutoMapper实现Dto和Model的*转换(上)

    在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换.例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求 ...

  4. 使用AutoMapper实现Dto和Model的*转换(中)

    在上一篇文章中我们构造出了完整的应用场景,包括我们的Model.Dto以及它们之间的转换规则.下面就可以卷起袖子,开始我们的AutoMapper之旅了. [二]以Convention方式实现零配置的对 ...

  5. AutoMapper完成Dto与Model的转换

    在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换. 例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.相反,当用户请求数 ...

  6. 自制AutoMapper实现DTO到持久层Entity的转换

    自制AutoMapper实现DTO到持久层Entity的转换 项目中经常涉及到页面DTO更新,保存到数据库的操作,这就必然牵扯到DTO和持久层对象的转换,常见的第三方库有: java:dozer .n ...

  7. Swift3&period;0语言教程字符串与URL的数据转换与*转换

    Swift3.0语言教程字符串与URL的数据转换与*转换 Swift3.0语言教程字符串与URL的数据转换 Swift3.0语言教程字符串与URL的数据转换与*转换,字符串中的字符永久保存除了可以 ...

  8. 【转贴】 Flex 预编译处理 与 手机项目、Web项目、Air项目*转换

    原文连接: http://bbs.9ria.com/forum.php?mod=viewthread&tid=263350#lastpost 由于这个论坛不稳定,所以转到这里来,这个帖子蛮有用 ...

  9. 问题:一球从某高度*落下,每次落地后反跳回原高度的一半;再落下,求它在第n次落地时,共经过多少米?第n次反弹多高?

    import java.util.Scanner; //题目:一球从100米高度*落下,每次落地后反跳回原高度的一半:再落下,求它在第10次落地时,共经过多少米?第10次反弹多高? public c ...

随机推荐

  1. CSS Hack解决浏览器IE部分属性兼容性问题

    1.Css Hack 不同厂商的流览器或某浏览器的不同版本(如IE6-IE11,Firefox/Safari/Opera/Chrome等),对CSS的支持.解析不一样,导致在不同浏览器的环境中呈现出不 ...

  2. htm&period;dropdownlist

    @Html.DropDownList("status", new SelectList(new[] { "驳回", "确认", " ...

  3. shell编程中的select用法

    select 语句 select表达式是bash的一种扩展应用,擅长于交互式场合.用户可以从一组不同的值中进行选择: select var in ... ; do break; done .... n ...

  4. 手动内存管理与ARC互相转换问题2

  5. HTML&sol;CSS的学习过程一览

    HTML/CSS的学习过程一览 说明 调试工具使用的是Google Chrome浏览器,其余浏览器出现的问题,这锅我不背[傲娇脸 可以使用浏览器查看源代码 网页列表 HTML_CSS_1 HTML基本 ...

  6. Android 签名&lpar;2&rpar;签名知识要点

    要点 1) 所有的应用程序都必须有数字证书,Android系统不会安装一个没有数字证书的应用程序 2) Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证 3) 如 ...

  7. 【转】Singleton单例模式

    对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务:一个系统只能有一个窗口管理器或文件系统:一个系统只能有一个计时工具或ID(序号)生成器. ...

  8. U盘安装CentOS7

    1:U盘启动,进入安装界面,点击Tab键,修改最后一行如下: ...=initrd.img linux dd quiet 查看centos系统盘符,例如:sdb4 2:重启电脑,进入安装界面,点击Ta ...

  9. 【剑指offer】第一个仅仅出现一次的字符

    转载请注明出处:http://blog.csdn.net/ns_code/article/details/27106997 题目描写叙述: 在一个字符串(1<=字符串长度<=10000,所 ...

  10. mysql创建和调用out参数的存储过程

    CREATE PROCEDURE sp_add(a int, b int,out c int) begin set c=a+ b; end; 调用过程: call sp_add (,,@a); sel ...