在实际的软件开发项目中,我们的“业务逻辑”常常需要我们对同样的数据进行各种变换。

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

有时候我们还会面临更多的数据使用需求,例如有多个数据使用的客户端,每个客户端都有自己对数据结构的不同需求,而这也需要我们进行更多的数据转换。 
频繁的数据转换琐碎而又凌乱,很多时候我们不得不做: 
(1)在两个类型几乎只是名字不同而结构大体相似,却只能以手工的、逐个属性赋值的方式实现数据在类型间的“传递”。 
(2)每遇到一个新的数据转换场景就手动实现一套转换逻辑,导致数据转换操作重复而又分散到应用的各个角落。 
如果有这样一个“变形金刚”般的工具,把“橘子”变成我们想要的“苹果”,而我们需要做的只是定义好转换规则——做我们真正的业务逻辑,或者甚至在简单场景下连规则都不需要定义(Convention Over Configuration),那将会是非常美好的事情。事实上在.NET中我们不用重复发明轮子,因为我们有——AutoMapper,一个强大的Object-Object Mapping工具。 
好吧,我承认自己有一点小小的激动,事实上我所做的项目正在经历以上的“困惑”,而AutoMapper确实带给我眼前一亮的感觉。因此我花了一点周末休息时间小小尝试了一把AutoMapper,通过做小的应用场景实现Dto到领域模型的映射,确实感觉到了它的“强大气场”。我将在文章中分享自己的使用心得,希望能给同样处于困惑中的你带来一点帮助。完整的项目代码我会在晚一些时候发布到自己的git repository中,欢迎大家自由参考使用。

【一】 将Model转换为Dto

先来看看我所”虚拟“的领域模型。这一次我定义了一个书店(BookStore)

   public class BookStore
{
public string Name { get; set; }
public List<Book> Books { get; set; }
public Address Address { get; set; }
}

书店有自己的地址(Address):

   1:      public class Address
   2:      {
   3:          public string Country { get; set; }
   4:          public string City { get; set; }
   5:          public string Street { get; set; }
   6:          public string PostCode { get; set; }
   7:      }

同时书店里放了n本书(Book):

   1:      public class Book
   2:      {
   3:          public string Title { get; set; }
   4:          public string Description { get; set; }
   5:          public string Language { get; set; }
   6:          public decimal Price { get; set; }
   7:          public List<Author> Authors { get; set; }
   8:          public DateTime? PublishDate { get; set; }
   9:          public Publisher Publisher { get; set; }
  10:          public int? Paperback { get; set; }
  11:      }

每本书都有出版商信息(Publisher):

   1:      public class Publisher
   2:      {
   3:          public string Name { get; set; }
   4:      }

每本书可以有最多2个作者的信息(Author):

   1:      public class Author
   2:      {
   3:          public string Name { get; set; }
   4:          public string Description { get; set; }
   5:          public ContactInfo ContactInfo { get; set; }
   6:      }

每个作者都有自己的联系方式(ContactInfo):

   1:      public class ContactInfo
   2:      {
   3:          public string Email { get; set; }
   4:          public string Blog { get; set; }
   5:          public string Twitter { get; set; }
   6:      }

差不多就是这样了,一个有着层级结构的领域模型。 
再来看看我们的Dto结构。 
在Dto中我们有与BookStore对应的BookStoreDto:

   1:      public class BookStoreDto
   2:      {
   3:          public string Name { get; set; }
   4:          public List<BookDto> Books { get; set; }
   5:          public AddressDto Address { get; set; }
   6:      }

其中包含与Address对应的AddressDto:

   1:      public class AddressDto
   2:      {
   3:          public string Country { get; set; }
   4:          public string City { get; set; }
   5:          public string Street { get; set; }
   6:          public string PostCode { get; set; }
   7:      }

以及与Book相对应的BookDto:

   1:      public class BookDto
   2:      {
   3:          public string Title { get; set; }
   4:          public string Description { get; set; }
   5:          public string Language { get; set; }
   6:          public decimal Price { get; set; }
   7:          public DateTime? PublishDate { get; set; }
   8:          public string Publisher { get; set; }
   9:          public int? Paperback { get; set; }
  10:          public string FirstAuthorName { get; set; }
  11:          public string FirstAuthorDescription { get; set; }
  12:          public string FirstAuthorEmail { get; set; }
  13:          public string FirstAuthorBlog { get; set; }
  14:          public string FirstAuthorTwitter { get; set; }
  15:          public string SecondAuthorName { get; set; }
  16:          public string SecondAuthorDescription { get; set; }
  17:          public string SecondAuthorEmail { get; set; }
  18:          public string SecondAuthorBlog { get; set; }
  19:          public string SecondAuthorTwitter { get; set; }
  20:      }

注意到我们的BookDto”拉平了“整个Book的层级结构,一个BookDto里携带了Book及其所有Author、Publisher等所有模式的数据。 
正好我们来看一下Dto到Model的映射规则。 
(1)BookStoreDto –> BookStore

BookStoreDto中的字段 BookStore中的字段
Name Name
Books Books
Address Address

(2)AddressDto –> Address

AddressDto中的字段 Address中的字段
Country Country
City City
Street Street
PostCode PostCode

(3)BookDto -> Book。 
BookDto中的一些基本字段可以直接对应到Book中的字段。

BookDto中的字段 Book中的字段
Title Title
Description Description
Language Language
Price Price
PublishDate PublishDate
Paperback Paperback

每本书至多有2个作者,在BookDto中分别使用”First“前缀和”Second“前缀的字段来表示。因此,所有FirstXXX字段都将映射成Book的Authors中的第1个Author对象,而所有SecondXXX字段则将映射成Authors中的第2个Author对象。

BookDto中的字段 Book中的Authors中的第1个Author对象中的字段
FirstAuthorName Name
FirstAuthorDescription Description
FirstAuthorEmail ContactInfo.Email
FirstAuthorBlog ContactInfo.Blog
FirstAuthorTwitter ContactInfo.Twitter

注意上表中的ContactInfo.Email表示对应到Author对象的ContactInfo的Email字段,依次类推。类似的我们有:

BookDto中的字段 Book中的Authors中的第2个Author对象中的字段
SecondAuthorName Name
SecondAuthorDescription Description
SecondAuthorEmail ContactInfo.Email
SecondAuthorBlog ContactInfo.Blog
SecondAuthorTwitter ContactInfo.Twitter

最后还有Publisher字段,它将对应到一个独立的Publisher对象。

BookDto中的字段 Publisher中的字段
Publisher Name

差不多就是这样了,我们的需求是要实现这一大坨Dto到另一大坨的Model之间的数据转换。

【二】将Dto转换为Model

1,以Convention方式实现零配置的对象映射

我们的AddressDto和Address结构完全一致,且字段名也完全相同。对于这样的类型转换,AutoMapper为我们提供了Convention,正如它的官网上所说的:

引用

AutoMapper uses a convention-based matching algorithm to match up source to destination values.

我们要做的只是将要映射的两个类型告诉AutoMapper(调用Mapper类的Static方法CreateMap并传入要映射的类型):

C#代码

Mapper.CreateMap<AddressDto, Address>();

然后就可以交给AutoMapper帮我们搞定一切了:

   1:              AddressDto dto = new AddressDto
   2:              {
   3:                  Country = "China",
   4:                  City = "Beijing",
   5:                  Street = "Dongzhimen Street",
   6:                  PostCode = "100001"
   7:              };
   8:              Address address = Mapper.Map<AddressDto,Address>(Dto);
   9:              address.Country.ShouldEqual("China");
  10:              address.City.ShouldEqual("Beijing");
  11:              address.Street.ShouldEqual("Dongzhimen Street");
  12:              address.PostCode.ShouldEqual("100001");

如果AddressDto中有值为空的属性,AutoMapper在映射的时候会把Address中的相应属性也置为空:

   1:              Address address = Mapper.Map<AddressDto,Address>(new AddressDto
   2:                                                                     {
   3:                                                                         Country = "China"
   4:                                                                     });
   5:              address.City.ShouldBeNull();
   6:              address.Street.ShouldBeNull();
   7:              address.PostCode.ShouldBeNull();

甚至如果传入一个空的AddressDto,AutoMapper也会帮我们得到一个空的Address对象。

   1:              Address address = Mapper.Map<AddressDto,Address>(null);
   2:              address.ShouldBeNull();

千万不要把这种Convention的映射方式当成“玩具”,它在映射具有相同字段名的复杂类型的时候还是具有相当大的威力的。 
例如,考虑我们的BookStoreDto到BookStore的映射,两者的字段名称完全相同,只是字段的类型不一致。如果我们定义好了BookDto到Book的映射规则,再加上上述Convention方式的AddressDto到Address的映射,就可以用“零配置”实现BookStoreDto到BookStore的映射了:

C#代码

   1:              IMappingExpression<BookDto, Book> expression = Mapper.CreateMap<BookDto,Book>();
   2:              // Define mapping rules from BookDto to Book here
   3:              Mapper.CreateMap<AddressDto, Address>();
   4:              Mapper.CreateMap<BookStoreDto, BookStore>();

然后我们就可以直接转换BookStoreDto了:

   1:              BookStoreDto dto = new BookStoreDto
   2:                                     {
   3:                                         Name = "My Store",
   4:                                         Address = new AddressDto
   5:                                                       {
   6:                                                           City = "Beijing"
   7:                                                       },
   8:                                         Books = new List<BookDto>
   9:                                                     {
  10:                                                         new BookDto {Title = "RESTful Web Service"},
  11:                                                         new BookDto {Title = "Ruby for Rails"},
  12:                                                     }
  13:                                     };
  14:              BookStore bookStore = Mapper.Map<BookStoreDto,BookStore>(dto);
  15:              bookStore.Name.ShouldEqual("My Store");
  16:              bookStore.Address.City.ShouldEqual("Beijing");
  17:              bookStore.Books.Count.ShouldEqual(2);
  18:              bookStore.Books.First().Title.ShouldEqual("RESTful Web Service");
  19:              bookStore.Books.Last().Title.ShouldEqual("Ruby for Rails");
 

以下为自己补充:

(Begin)---------------------------------------------------------------

1, 要实现BookDto到Book之间的转换还是有一断路需要走的因为他嵌套了相应的子类型如:Publisher ->ContactInfo,

Author。废话少说,直接上答案:

   1:  var exp = Mapper.CreateMap<BookDto, Book>(); 
   2:  exp.ForMember(bok=> bok.Publisher/*(变量)*/,
   3:   (map) => map.MapFrom(dto=>new Publisher(){Name= dto.Publisher/*(DTO的变量)*/}));
 
一般在我们写完规则之后通常会调用
 
         //该方法主要用来检查还有那些规则没有写完。
Mapper.AssertConfigurationIsValid();

参见:http://stackoverflow.com/questions/4928487/how-to-automap-thismapping-sub-members

其它的就以此类推。

2,如果要完成 BookStore 到 BookStoreDto 具体的应该如何映射呢 
相同的类型与名字就不说了,如BookStore.Name->BookStoreDto.Name AutoMapper会自动去找。

而对于List<Book>与List<BookDto>者我们必须在配置下面代码之前

var exp = Mapper.CreateMap<BookStore, BookStoreDto>();
exp.ForMember(dto => dto.Books, (map) => map.MapFrom(m => m.Books));

告诉AutoMapper,Book与BookDto的映射,最后效果为:

Mapper.CreateMap<Book, BookDto>();
var exp = Mapper.CreateMap<BookStore, BookStoreDto>();
exp.ForMember(dto => dto.Books, (map) => map.MapFrom(m => m.Books));

Address同理。

3,如果要完成不同类型之间的转换用AutoMapper,如string到int,string->DateTime,以及A->B之间的类型转换我们可以参照如下例子:

http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters&referringTitle=Home

4, 对于我们不想要某属性有值我们可以采用下面的方式。

exp.ForMember(ads => ads.ZipCode, dto => dto.Ignore()); //如果对于不想某属性有值,我们可以通过Ignore来忽略他,这样在调用AssertConfigurationIsValid时也不会报错.
(End)------------------------------------------------------------------------------------

【三】定义类型间的简单映射规则

前面我们看了Convention的映射方式,客观的说还是有很多类型间的映射是无法通过简单的Convention方式来做的,这时候就需要我们使用Configuration了。好在我们的Configuration是在代码中以“强类型”的方式来写的,比写繁琐易错的xml方式是要好的多了。 
先来看看BookDto到Publisher的映射。

回顾一下前面中定义的规则:BookDto.Publisher -> Publisher.Name。

在AutoMapperzhong,我们可以这样映射:

   1:  var map = Mapper.CreateMap<BookDto,Publisher>();
   2:  map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Publisher));

AutoMapper使用ForMember来指定每一个字段的映射规则:

引用

The each custom member configuration uses an action delegate to configure each member.

还好有强大的lambda表达式,规则的定义简单明了。 
此外,我们还可以使用ConstructUsing的方式一次直接定义好所有字段的映射规则。例如我们要定义BookDto到第一作者(Author)的ContactInfo的映射,使用ConstructUsing方式,我们可以:

C#代码

   1:  var map = Mapper.CreateMap<BookDto,ContactInfo>();
   2:  map.ConstructUsing(s => new ContactInfo
   3:                                            {
   4:                                                Blog = s.FirstAuthorBlog,
   5:                                                Email = s.FirstAuthorEmail,
   6:                                                Twitter = s.FirstAuthorTwitter
   7:                                            });

然后,就可以按照我们熟悉的方式来使用了:

   1:              BookDto dto = new BookDto
   2:                                      {
   3:                                          FirstAuthorEmail = "matt.rogen@abc.com",
   4:                                          FirstAuthorBlog = "matt.amazon.com",
   5:                                      };
   6:              ContactInfo contactInfo = Mapper.Map<BookDto, ContactInfo>(dto);

如果需要映射的2个类型有部分字段名称相同,又有部分字段名称不同呢?还好AutoMapper给我们提供的Convention或Configuration方式并不是“异或的”,我们可以结合使用两种方式,为名称不同的字段配置映射规则,而对于名称相同的字段则忽略配置。 
例如对于前面提到的AddressDto到Address的映射,假如AddressDto的字段Country不叫Country叫CountryName,那么在写AddressDto到Address的映射规则时,只需要:

   1:  var map = Mapper.CreateMap<AddressDto, Address>();
   2:  map.ForMember(d => d.Country, opt => opt.MapFrom(s => s.CountryName));

对于City、Street和PostCode无需定义任何规则,AutoMapper仍然可以帮我们进行正确的映射。

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的自由转换(下)

    书接上文.在上一篇文章中我们讨论了使用AutoMapper实现类型间1-1映射的两种方式——Convention和Configuration,知道了如何进行简单的OO Mapping.在这个系列的最后 ...

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

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

  6. 使用AutoMapper 处理DTO数据对象的转换

    using AutoMapper;using System; namespace DTOtEST{ class Program { static void Main(string[] args) { ...

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

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

  8. 将Model对象转换成json文本或者json二进制文件

    将Model对象转换成json文本或者json二进制文件 https://github.com/casatwy/AnyJson 注意:经过测试,不能够直接处理字典或者数组 主要源码的注释 AJTran ...

  9. 实体类与实体DTO类之间的转换

    实体类与实体DTO类之间的转换 实体类与实体DTO类之间的转换 1.通过使用第三方序列化反序列化工具Newtonsoft.Json 2.通过反射实现 3.通过表达式目录树加字典缓存实现 4. 通过表达 ...

随机推荐

  1. [html]兼容 IE6 IE7 的简单网页框架

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  2. spring默认启动位置以及contextConfigLocation设置源码解析

    这几天在看spring的源码,涉及到spring启动位置的部分,下面就看看spring到底是从哪儿开始加载的.本文使用的是spring3.0M3 首先spring的加载会借助一个监听器ContextL ...

  3. [vb.net]最简单的邮件发送

    Imports Microsoft.Office.Interop.Outlook Private Sub sendMail() Dim outObj As New Application Dim it ...

  4. Jade之Includes

    Includes jade允许利用include将其他文件(支持filters所支持的类型)中的代码嵌入当前代码中. jade: //- index.jade doctype html html in ...

  5. ClickOnce的部署(.appref-ms)在软件限制策略中的解决方案

    为了防止百度.360以及一些小厂商在妈妈的电脑里乱安装各种程序,在域中开启了软件限制策略. 今天在用Github的Windows客户端时发现由于软件限制策略无法运行. Github在Windows中采 ...

  6. ubuntu 启动项创建器 选择不了CD镜像,IOS镜像的解决方法

    自己系统是ubuntu14.04 , 想使用 ubuntu自带的启动项创建器(usb-creator-gtk)做一个CDLinux的U盘启动项, 打开程序后发现U盘识别了, 在添加镜像的时候,发现怎么 ...

  7. java反射保存

    前言 代码是我师父的,代码是我师父的,代码是我师父的,如有需要拿走的时候请标注  copyright by 山人Wu  记录这篇是为了加深理解,前段时间只是当做工具类来用,才有时间好好看一下,加深理解 ...

  8. Omu.AwesomeMvc.dll 和Omu.ValueInjecter.dll 介绍

    AwesomeMvc 让你不写一行js实现下拉列表联动 AwesomeMvc是个开源项目,地址:http://awesome.codeplex.com/ Omu.AwesomeMvc.dll 和Omu ...

  9. sql trunc()的使用

    1.TRUNC(for dates)TRUNC函数为指定元素而截去的日期值.其具体的语法格式如下:TRUNC(date[,fmt])其中:date 一个日期值fmt 日期格式,该日期将由指定的元素格式 ...

  10. RaisingStudio.SessionFactory 发布 0.1版

    功能描述: 1. 支持Orchard中方便使用自定义数据库连接. 2. 连接信息可配置. 用法: 1. 构造函数中添加IRepositoryFactory引用 private readonly IRe ...