AutoMapper小结
一些orm框架,在用到Entity的时候有一些开源代码用到了automapper(如:nopcommence),将数据对象转成DTO。比如在ORM中,与数据库交互用的Model模型是具有很多属性变量方法神马的。而当我们与其它系统(或系统中的其它结构)进行数据交互时,出于耦合性考虑或者安全性考虑或者性能考虑(总之就是各种考虑),我们不希望直接将这个Model模型传递给它们,这时我们会创建一个贫血模型来保存数据并传递。神马是贫血模型?贫血模型(DTO,Data Transfer Object)就是说只包含属性神马的,只能保存必须的数据,木有其它任何的多余的方法数据什么的,专门用于数据传递用的类型对象。在这个创建的过程中,如果我们手动来进行,就会看到这样的代码:
B b=new B();
b.XXX1=a.XXX1;
b.XXX2=a.XXX2;
...
...
...
return b;
此时,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):
- 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):
- 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的映射规则。
(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之间的数据转换。
【二】以Convention方式实现零配置的对象映射
在上一篇文章中我们构造出了完整的应用场景,包括我们的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仍然可以帮我们进行正确的映射。
AutoMapper小结的更多相关文章
- ABP入门系列(5)——创建应用服务
一.解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相应的业务逻辑并且将DTO返回给展现层.因此,展现 ...
- ABP入门系列(4)——创建应用服务
ABP入门系列目录--学习Abp框架之实操演练 一.解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DTO(数据传输对象)参数来调用应用服务,而应用服务通过领域对象来执行相 ...
- abp(net core)+easyui+efcore仓储系统——创建应用服务(五)
abp(net core)+easyui+efcore仓储系统目录 abp(net core)+easyui+efcore仓储系统——ABP总体介绍(一) abp(net core)+easyui+e ...
- ABP创建应用服务
原文作者:圣杰 原文地址:ABP入门系列(4)——创建应用服务 在原文作者上进行改正,适配ABP新版本.内容相同 1. 解释下应用服务层 应用服务用于将领域(业务)逻辑暴露给展现层.展现层通过传入DT ...
- AutoMapper在MVC中的运用小结
配置.单元测试.AOP注入 Decimal转换成String类型 源数组转换成目标数组 源中的集合(数组)属性转换成目标中的集合(数组)属性 子类父类间的映射 源字典集合转换成目标字典集合 枚举映射 ...
- 让AutoMapper在你的项目里飞一会儿(转)
出处:http://www.cnblogs.com/WeiGe/p/3835523.html 先说说DTO DTO是个什么东东? DTO(Data Transfer Object)就是数据传输对象,说 ...
- AutoMapper使用简单总结
近期,在用AutoMapper整理一些模型对象映射,顺便小结一下使用的体会.难免有写得不对的地方,谢谢指出! 1. AutoMapper是一个.NET的对象映射工具,可以方便地进行对象间的赋值处理. ...
- AutoMapper在项目中的应用
一.先说说DTO DTO是个什么东东? DTO(Data Transfer Object)就是数据传输对象,说白了就是一个对象,只不过里边全是数据而已. 为什么要用DTO? 1.DTO更注重数据,对领 ...
- 让AutoMapper在你的项目里飞一会儿
先说说DTO DTO是个什么东东? DTO(Data Transfer Object)就是数据传输对象,说白了就是一个对象,只不过里边全是数据而已. 为什么要用DTO? 1.DTO更注重数据,对领域对 ...
随机推荐
- windows下安装iReport 并确保启动正确
突然从润乾转到iReport ,我也很蒙.突然离开了万能的客服,我心不甘.现在所有资料都要自己查找,只好做个记录.现在从安装开始说. 此时安装的最新版是5.6.0,要知道,网上大部分资料都是4.X,更 ...
- ASP.NET 导出gridview中的数据到Excel表中,并对指定单元格换行操作
1. 使用NPOI读取及生成excel表. (1)导出Click事件: 获取DataTable; 给文件加文件名: string xlsxName = "xxx_" + DateT ...
- NYOJ 536 开心的mdd(DP)
开心的mdd 时间限制:1000 ms | 内存限制:65535 KB 难度:3 描述 himdd有一天闲着无聊,随手拿了一本书,随手翻到一页,上面描述了一个神奇的问题,貌似是一个和矩阵有关的 ...
- HTML + JS随机抽号。
[设置第三次抽取的号码为 (张三6)]<script language="javascript"> var k = 0 ; function star(){ k++ ; ...
- C语言标准定义的32个关键字
关键字 意 义 auto 声明自动变量,缺省时编译器一般默认为auto int ...
- 高性能网站架构设计之缓存篇(2)- Redis C#客户端
在上一篇中我简单的介绍了如何利用redis自带的客户端连接server并执行命令来操作它,但是如何在我们做的项目或产品中操作这个强大的内存数据库呢?首先我们来了解一下redis的原理吧. 官方文档上是 ...
- RCP:为指定的导航器添加上下文菜单
可以参考Eclipse的Help->Help Content下的: Platform Plug-in Developer Guide > Programmer's Guide > P ...
- Java语法糖3:泛型
泛型初探 在泛型(Generic type或Generics)出现之前,是这么写代码的: public static void main(String[] args) { List list = ne ...
- 在英文版操作系统中安装的MS SQL server,中文字段无法匹配
在英文版的操作系统中安装的MS SQL server,会出现中文字段无法被匹配到.其原因在于英文环境下安装的MS SQL server的排序规则不包括中文. 所以解决办法就是更改MS SQL serv ...
- 单一职责原则(Single Responsibility Principle)
单一职责原则(SRP:The Single Responsibility Principle) 一个类应该有且只有一个变化的原因. There should never be more than on ...