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

书接上文。在上一篇文章中我们讨论了使用AutoMapper实现类型间1-1映射的两种方式——Convention和Configuration,知道了如何进行简单的OO Mapping。在这个系列的最后一篇文章我想基于我们的需求讨论一些中级别的话题,包括:如何实现类型体型之间的映射,以及如何为两个类型实现多个映射规则。

【四】将一个类型映射为类型体系

先回顾一下我们的Dto和Model。我们有BookDto,我们有Author,每个Author有自己的ContactInfo。现在提一个问题:如何从BookDto得到第一个作者的Author对象呢?答案即简单,又不简单。

最简单的做法是,使用前面提到的CountructUsing,指定BookDto到Author的全部字段及子类型字段的映射:

C#代码  

  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为实现字段级别的特定映射逻辑而定义的类型,它的定义如下:

C#代码  

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

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

C#代码  

  1. protected abstract TDestination ResolveCore(TSource source);

其中TSource为源类型,TDestination为目标字段的类型。

回到我们的例子,我们现在可以这样来映射BookDto -> Author:

C#代码  

  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的逻辑:

C#代码  

  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方法应该这样定义:

C#代码  

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

其中有一个额外的参数tag用于标识该映射的标签。

而我们在使用的时候,就可以:

C#代码  

  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:

C#代码  

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

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

C#代码  

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

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

C#代码  

  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:

C#代码  

  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里:

C#代码  

  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对象:

C#代码  

  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里面了:

C#代码  

  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没有帮我们实现,就让我们自己来实现吧:

C#代码  

  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. }

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

C#代码  

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

也可以这样了:

C#代码  

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

这个系列的博客就写到这里, 希望对您有所帮助!

时间: 2024-08-02 02:45:27

使用AutoMapper实现Dto和Model的自由转换(下)的相关文章

使用AutoMapper实现Dto和Model的自由转换(上)【转】

转自:http://zz8ss5ww6.iteye.com/blog/1126205 注:本系列文章的代码可以在这里下载. 在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换.例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户.有时候我们还会面临更多的数据使用需求,例如有多个数据使用的客户端,每个客户端

使用AutoMapper实现Dto和Model的自由转换(上)

在实际的软件开发项目中,我们的"业务逻辑"常常需要我们对同样的数据进行各种变换.例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.另一方面,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户.有时候我们还会面临更多的数据使用需求,例如有多个数据使用的客户端,每个客户端都有自己对数据结构的不同需求,而这也需要我们进行更多的数据转换. 频繁的数据转换琐碎而又凌乱,很多时候我们不得不:

使用AutoMapper实现Dto和Model的自由转换(下)【转】

转自:http://zz8ss5ww6.iteye.com/blog/1126330 书接上文.在上一篇文章中我们讨论了使用AutoMapper实现类型间1-1映射的两种方式——Convention和Configuration,知道了如何进行简单的OO Mapping.在这个系列的最后一篇文章我想基于我们的需求讨论一些中级别的话题,包括:如何实现类型体型之间的映射,以及如何为两个类型实现多个映射规则. [四]将一个类型映射为类型体系 先回顾一下我们的Dto和Model.我们有BookDto,我们

使用AutoMapper实现Dto和Model的自由转换(中)

在上一篇文章中我们构造出了完整的应用场景,包括我们的Model.Dto以及它们之间的转换规则.下面就可以卷起袖子,开始我们的AutoMapper之旅了. [二]以Convention方式实现零配置的对象映射 我们的AddressDto和Address结构完全一致,且字段名也完全相同.对于这样的类型转换,AutoMapper为我们提供了Convention,正如它的官网上所说的: 引用 AutoMapper uses a convention-based matching algorithm to

使用AutoMapper实现Dto和Model的自由转换(中)【转】

转自:http://zz8ss5ww6.iteye.com/blog/1126219 注:本系列文章的代码可以在这里下载. 在上一篇文章中我们构造出了完整的应用场景,包括我们的Model.Dto以及它们之间的转换规则.下面就可以卷起袖子,开始我们的AutoMapper之旅了. [二]以Convention方式实现零配置的对象映射 我们的AddressDto和Address结构完全一致,且字段名也完全相同.对于这样的类型转换,AutoMapper为我们提供了Convention,正如它的官网上所说

AutoMapper完成Dto与Model的转换

在实际的软件开发项目中,我们的"业务逻辑"常常需要我们对同样的数据进行各种变换. 例如,一个Web应用通过前端收集用户的输入成为Dto,然后将Dto转换成领域模型并持久化到数据库中.相反,当用户请求数据时,我们又需要做相反的工作:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户. 有时候我们还会面临更多的数据使用需求,例如有多个数据使用的客户端,每个客户端都有自己对数据结构的不同需求,而这也需要我们进行更多的数据转换. 频繁的数据转换琐碎而又凌乱,很多时候我们不得不做

简单使用AutoMapper实现DTO转换

DTO(Data Transfer Object)数据传输对象,只是传输数据,完成与领域对象之间的转换,并不包含领域业务处理. 当领域模型设计人员只关注核心业务,满足于领域模型的精巧,而不关心具体实现的时候,DTO就会大量出现. 系统复杂化后,DTO可能是多个Domain Model组合实现. 为什么要使用DTO? 1.隔离Domain Model,使改动领域模型而不影响UI,保持领域模型的安全,不暴露业务逻辑. 2.在分布式模式下,不同的场景使用相同的数据结构有不同的需求. 简单示例: Ord

【笔试】10、一球从100米高度自由落下,求相关数据

/** * 题目:一球从100米高度自由落下,每次落地后反跳回原高度的一半:再落下,求它在 第10次落地时,共经过多少米?第10次反弹多高? * 时间:2015年7月28日15:34:18 * 文件:Lianxi10.java * 作者:cutter_point */ package bishi.zuixin50.t2015728; public class Lianxi10 { //这个是一个等比数列 public static double getAn(double begin, doubl

一球从100米高度自由落下,每次落地后反跳回原高度的一半;再落下,求它在 第10次落地时,共经过多少米?第10次反弹多高?

/** * 一球从100米高度自由落下,每次落地后反跳回原高度的一半:再落下,求它在 第10次落地时,共经过多少米?第10次反弹多高? * */ public class Test2 { public static void main(String[] args) { double num = 100, sum = 0; for (int i = 0; i < 10; i++) { sum += num*2; num /= 2; } sum -= 100; System.out.println(