AutoMapper完成Dto与Model的转换

在实际的软件开发项目中,咱们的“业务逻辑”经常须要咱们对一样的数据进行各类变换。前端

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

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

【一】 将Model转换为Dto

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

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

书店有本身的地址(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):app

   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):this

   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):spa

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

   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仍然能够帮咱们进行正确的映射。

该文转自:http://zz8ss5ww6.iteye.com/blog/1126219

相关文章
相关标签/搜索