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

相關文章
相關標籤/搜索