一些orm框架,在用到Entity的时候有一些开源代码用到了automapper(如:nopcommence),将数据对象转成DTO。好比在ORM中,与数据库交互用的Model模型是具备不少属性变量方法神马的。而当咱们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各类考虑),咱们不但愿直接将这个Model模型传递给它们,这时咱们会建立一个贫血模型来保存数据并传递。什么是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性什么的,只能保存必须的数据,没有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个建立的过程当中,若是咱们手动来进行,就会看到这样的代码:
A a=new A();
a.X1=b.X1;
a.X2=b.X2;
...
...
...
return a;
此时,AutoMapper能够发挥的做用就是根据A的模型和B的模型中的定义,自动将A模型映射为一个全新的B模型。基于访问性的控制或从模型自己上考虑。对外开放的原则是,尽可能下降系统耦合度,不然内部一旦变动外部全部的接口都要跟随发生变动;另外,系统内部的一些数据或方法并不但愿外部能看到或调用。相似的考虑不少,只是举个例子。系统设计的原则是高内聚低耦合,尽可能依赖抽象而不依赖于具体。这里感受automapper就是使数据库实体对一个外部调用实体的转换更简便(不用一个属性一个属性的赋值)。
例如1:数据库里面有用户信息表,供别的系统调用,提供了数据接口。若是直接暴露了数据库层的表结构的话,会对系统自己产生依赖。具体表如今,假定如今由于某种须要,为用户信息增长了十个字段的信息,那么,若是不进行类型映射的话,会致使全部基于此用户数据结构的模块集体挂掉(接口约定变动)。而若是使用了映射的话,咱们能够在内部进行转换,保持原有接口不变并提供新的更全面的接口,这是保证系统的可维护性和可迁移性。
例如2:一个Web应用经过前端收集用户的输入成为Dto,而后将Dto转换成领域模型并持久化到数据库中。相反,当用户请求数据时,咱们又须要作相反的工做:将从数据库中查询出来的领域模型以相反的方式转换成Dto再呈现给用户。使用AutoMapper(一个强大的Object-Object Mapping工具),来实现这个转换。
先来看看我所虚拟的领域模型。这一次我定义了一个书店(BookStore): 前端
public class BookStore { public string Name { get; set; } public List<Book> Books { get; set; } public Address Address { get; set; } }
书店有本身的地址(Address): 数据库
public class Address { public string Country { get; set; } public string City { get; set; } public string Street { get; set; } public string PostCode { get; set; } }
同时书店里放了N本书(Book): express
public class Book { public string Title { get; set; } public string Description { get; set; } public string Language { get; set; } public decimal Price { get; set; } public List<Author> Authors { get; set; } public DateTime? PublishDate { get; set; } public Publisher Publisher { get; set; } public int? Paperback { get; set; } }
每本书都有出版商信息(Publisher): 安全
public class Publisher { public string Name { get; set; } }
每本书能够有最多2个做者的信息(Author): 数据结构
public class Author { public string Name { get; set; } public string Description { get; set; } public ContactInfo ContactInfo { get; set; } }
每一个做者都有本身的联系方式(ContactInfo): app
public class ContactInfo { public string Email { get; set; } public string Blog { get; set; } public string Twitter { get; set; } }
差很少就是这样了,一个有着层级结构的领域模型。 再来看看咱们的Dto结构。 在Dto中咱们有与BookStore对应的BookStoreDto: 框架
public class BookStoreDto { public string Name { get; set; } public List<BookDto> Books { get; set; } public AddressDto Address { get; set; } }
其中包含与Address对应的AddressDto: 工具
public class AddressDto { public string Country { get; set; } public string City { get; set; } public string Street { get; set; } public string PostCode { get; set; } }
以及与Book相对应的BookDto: 性能
public class BookDto { public string Title { get; set; } public string Description { get; set; } public string Language { get; set; } public decimal Price { get; set; } public DateTime? PublishDate { get; set; } public string Publisher { get; set; } public int? Paperback { get; set; } public string FirstAuthorName { get; set; } public string FirstAuthorDescription { get; set; } public string FirstAuthorEmail { get; set; } public string FirstAuthorBlog { get; set; } public string FirstAuthorTwitter { get; set; } public string SecondAuthorName { get; set; } public string SecondAuthorDescription { get; set; } public string SecondAuthorEmail { get; set; } public string SecondAuthorBlog { get; set; } public string SecondAuthorTwitter { get; set; } }
注意到咱们的BookDto”拉平了“整个Book的层级结构,一个BookDto里携带了Book及其全部Author、Publisher等全部模式的数据。正好咱们来看一下Dto到Model的映射规则。 this
(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之间的数据转换。
在上一篇文章中咱们构造出了完整的应用场景,包括咱们的Model、Dto以及它们之间的转换规则。下面开始咱们的AutoMapper之旅了。
咱们要作的只是将要映射的两个类型告诉AutoMapper(调用Mapper类的Static方法CreateMap并传入要映射的类型):
Mapper.CreateMap<AddressDto, Address>();
而后就能够交给AutoMapper帮咱们搞定一切了:
AddressDto dto = new AddressDto { Country = "China", City = "Beijing", Street = "Dongzhimen Street", PostCode = "100001" }; Address address = Mapper.Map<AddressDto,Address>(Dto); address.Country.ShouldEqual("China"); address.City.ShouldEqual("Beijing"); address.Street.ShouldEqual("Dongzhimen Street"); address.PostCode.ShouldEqual("100001");
若是AddressDto中有值为空的属性,AutoMapper在映射的时候会把Address中的相应属性也置为空:
Address address = Mapper.Map<AddressDto,Address>(new AddressDto { Country = "China" }); address.City.ShouldBeNull(); address.Street.ShouldBeNull(); address.PostCode.ShouldBeNull();
甚至若是传入一个空的AddressDto,AutoMapper也会帮咱们获得一个空的Address对象。
Address address = Mapper.Map<AddressDto,Address>(null); address.ShouldBeNull();
千万不要把这种Convention的映射方式当成“玩具”,它在映射具备相同字段名的复杂类型的时候仍是具备至关大的威力的。
例如,考虑咱们的BookStoreDto到BookStore的映射,二者的字段名称彻底相同,只是字段的类型不一致。若是咱们定义好了BookDto到Book的映射规则,再加上上述Convention方式的AddressDto到Address的映射,就能够用“零配置”实现BookStoreDto到BookStore的映射了:
IMappingExpression<BookDto, Book> expression = Mapper.CreateMap<BookDto,Book>(); // Define mapping rules from BookDto to Book here Mapper.CreateMap<AddressDto, Address>(); Mapper.CreateMap<BookStoreDto, BookStore>();
而后咱们就能够直接转换BookStoreDto了:
BookStoreDto dto = new BookStoreDto { Name = "My Store", Address = new AddressDto { City = "Beijing" }, Books = new List<BookDto> { new BookDto {Title = "RESTful Web Service"}, new BookDto {Title = "Ruby for Rails"}, } }; BookStore bookStore = Mapper.Map<BookStoreDto,BookStore>(dto); bookStore.Name.ShouldEqual("My Store"); bookStore.Address.City.ShouldEqual("Beijing"); bookStore.Books.Count.ShouldEqual(2); bookStore.Books.First().Title.ShouldEqual("RESTful Web Service"); bookStore.Books.Last().Title.ShouldEqual("Ruby for Rails");
实现BookDto到Book之间的转换(他嵌套了相应的子类型如:Publisher ->ContactInfo,Author):
var exp = Mapper.CreateMap<BookDto, Book>(); exp.ForMember(bok=> bok.Publisher/*(变量)*/, (map) => map.MapFrom(dto=>new Publisher(){Name= dto.Publisher/*(DTO的变量)*/}));
通常在咱们写完规则以后一般会调用,该方法主要用来检查还有那些规则没有写完。
Mapper.AssertConfigurationIsValid();
参见:http://stackoverflow.com/questions/4928487/how-to-automap-thismapping-sub-members
其它的就以此类推。
若是要完成 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同理。若是要完成不一样类型之间的转换用AutoMapper,如string到int,string->DateTime,以及A->B之间的类型转换咱们能够参照以下例子:
http://automapper.codeplex.com/wikipage?title=Custom%20Type%20Converters&referringTitle=Home
对于咱们不想要某属性有值咱们能够采用下面的方式。
exp.ForMember(ads => ads.ZipCode, dto => dto.Ignore()); //若是对于不想某属性有值,咱们能够经过Ignore来忽略他,这样在调用AssertConfigurationIsValid时也不会报错.
三,定义类型间的简单映射规则
前面咱们看了Convention的映射方式,客观的说仍是有不少类型间的映射是没法经过简单的Convention方式来作的,这时候就须要咱们使用Configuration了。好在咱们的Configuration是在代码中以“强类型”的方式来写的,比写繁琐易错的xml方式是要好的多了。 先来看看BookDto到Publisher的映射。 回顾一下前文中定义的规则:BookDto.Publisher -> Publisher.Name。 在AutoMapperzhong,咱们能够这样映射:
var map = Mapper.CreateMap<BookDto,Publisher>(); map.ForMember(d => d.Name, opt => opt.MapFrom(s => s.Publisher));
AutoMapper使用ForMember来指定每个字段的映射规则:
还好有强大的lambda表达式,规则的定义简单明了。 此外,咱们还可使用ConstructUsing的方式一次直接定义好全部字段的映射规则。例如咱们要定义BookDto到第一做者(Author)的ContactInfo的映射,使用ConstructUsing方式,咱们能够:
var map = Mapper.CreateMap<BookDto,ContactInfo>(); map.ConstructUsing(s => new ContactInfo { Blog = s.FirstAuthorBlog, Email = s.FirstAuthorEmail, Twitter = s.FirstAuthorTwitter });
而后,就能够按照咱们熟悉的方式来使用了:
BookDto dto = new BookDto { FirstAuthorEmail = "matt.rogen@abc.com", FirstAuthorBlog = "matt.amazon.com", }; ContactInfo contactInfo = Mapper.Map<BookDto, ContactInfo>(dto);
若是须要映射的2个类型有部分字段名称相同,又有部分字段名称不一样呢?还好AutoMapper给咱们提供的Convention或Configuration方式并非“异或的”,咱们能够结合使用两种方式,为名称不一样的字段配置映射规则,而对于名称相同的字段则忽略配置。
例如:对于前面提到的AddressDto到Address的映射,假如AddressDto的字段Country不叫Country叫CountryName,那么在写AddressDto到Address的映射规则时,只须要:
var map = Mapper.CreateMap<AddressDto, Address>(); map.ForMember(d => d.Country, opt => opt.MapFrom(s => s.CountryName));
对于City、Street和PostCode无需定义任何规则,AutoMapper仍然能够帮咱们进行正确的映射。