【AutoMapper官方文档】DTO与Domin Model相互转换(下)
Mapping Inheritance-映射继承
关于映射继承,其实在“Lists and Array-集合和数组”这一节点有提到,但是只是说明下AutoMapper解决映射继承所使用的方式,这边我们说下关于AutoMapper在映射继承中的一些特性,比如下面转换示例:
![](/assets/blank.gif)
1 public class Order { } 2 public class OnlineOrder : Order { } 3 public class MailOrder : Order { } 4 5 public class OrderDto { } 6 public class OnlineOrderDto : OrderDto { } 7 public class MailOrderDto : OrderDto { }
![](/assets/blank.gif)
源对象和目标对象存在继承关系,和“Lists and Array”节点里面的的示例一样,我们首先要配置AutoMapper,添加类型映射关系和依赖关系,如下:
![](/assets/blank.gif)
1 //配置 AutoMapper 2 Mapper.CreateMap<Order, OrderDto>() 3 .Include<OnlineOrder, OnlineOrderDto>() 4 .Include<MailOrder, MailOrderDto>(); 5 Mapper.CreateMap<OnlineOrder, OnlineOrderDto>(); 6 Mapper.CreateMap<MailOrder, MailOrderDto>();
![](/assets/blank.gif)
关于这三段代码的意义,在“Lists and Array”节点中也有说明,如果我们注释掉第一段代码,我们在做派生类映射转换的时候就会报错,如果我们把下面两段代码去掉,我们在做派生类映射转换的时候就会映射到基类,说明第一段代码的意义是,不仅仅包含Order到OrderDto之间的类型映射,还包含Order与OrderDto所有派生类之间的映射,但是只是声明,如果要对派生类之间进行类型映射转换,就还得需要创建派生类之间的类型映射关系。
我们在“Lists and Array”节点中这样执行类型映射转换:
1 var destinations = Mapper.Map<ParentSource[], ParentDestination[]>(sources);
上面这段转换代码是指定了源泛型类型(Source)和目标泛型类型类型(Dest),所以AutoMapper会根据指定的类型就可以进行转换了,前提是类型映射关系配置正确,要不然就会报“AutoMapperConfigurationException”异常。如果我们不指定目标数据类型,然后就行转换会怎样呢?比如下面转换:
1 var order = new OnlineOrder(); 2 var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto)); 3 Console.WriteLine("mapped Type:" + mapped.GetType());
转换效果:
代码中我们并没有指定目标数据类型,只是指定一个派生类的基类,如果按照我们的理解,这段代码执行的结果应该是:转换结果mapped对象的类型应该是OrderDto类型,但是确是我们希望想要的OnlineOrderDto类型,虽然我们没有指定目标类型为OnlineOrderDto,但是这一切AutoMapper都帮你做了,就是说AutoMapper会自动判断目标类型与源数据类型存在的关系,并找出最合适的派生类类型。
Queryable Extensions (LINQ)-扩展查询表达式
注:关于Entity Framework中数据类型映射正在研究,网上找了很多英文文章,还在消化中,这一节点只是简单的说下AutoMapper查询表达式的用法,过几天再整理一篇关于实体框架中运用数据类型映射的文章,功力不够,还请包涵。
当我们使用Entity Framework与AutoMapper结合进行查询对象转换的时候,使用Mapper.Map方法,就会发现查询结果对象中的所有属性和目标属性对象属性都会转换,当然你也可以在查询结果集中构建一个所需结果的示例,但是这样做并不是可取的,AutoMapper的作者扩展了QueryableExtensions,使得我们在查询的时候就可以实现转换,比如下面示例:
![](/assets/blank.gif)
1 public class OrderLine 2 { 3 public int Id { get; set; } 4 public int OrderId { get; set; } 5 public Item Item { get; set; } 6 public decimal Quantity { get; set; } 7 } 8 public class Item 9 { 10 public int Id { get; set; } 11 public string Name { get; set; } 12 } 13 14 public class OrderLineDTO 15 { 16 public int Id { get; set; } 17 public int OrderId { get; set; } 18 public string Item { get; set; } 19 public decimal Quantity { get; set; } 20 } 21 22 public List<OrderLineDTO> GetLinesForOrder(int orderId) 23 { 24 Mapper.CreateMap<OrderLine, OrderLineDTO>() 25 .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name)); 26 27 using (var context = new orderEntities()) 28 { 29 return context.OrderLines.Where(ol => ol.OrderId == orderId) 30 .Project().To<OrderLineDTO>().ToList(); 31 } 32 }
![](/assets/blank.gif)
代码中的.Project().To就是扩展的查询表达式,详细表达式代码:
![](/assets/blank.gif)
我们在前几节点中说的自定义映射规则,其实也是属于查询表达式的一种,结合实体框架可以简单的应用下,比如我们要映射到DTO中一个Count属性,来计算查询结果集中的数量,如下面代码:
1 Mapper.CreateMap<Customer, CustomerDto>() 2 .ForMember(d => d.FullName, opt => opt.MapFrom(c => c.FirstName + " " + c.LastName)) 3 .ForMember(d => d.TotalContacts, opt => opt.MapFrom(c => c.Contacts.Count()));
LINQ支持聚合查询,AutoMapper支持LINQ的扩展方法。在自定义映射中,如果我们讲属性名称TotalContacts改为ContactsCount,AutoMapper将自动匹配到COUNT()扩展方法和LINQ提供程序将转化计数到相关子查询汇总子记录。AutoMapper还可以支持复杂的聚合和嵌套的限制,如果LINQ提供的表达式支持它,例如下面代码:
1 Mapper.CreateMap<Course, CourseModel>() 2 .ForMember(m => m.EnrollmentsStartingWithA, 3 opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));
上面计算的是每门课程,学生名字开头为“A”的学生数量。
不是所有的映射选项都支持表达式,因为它必须有LINQ的支持,支持的有:
- MapFrom
- Ignore
不支持的有:
- Condition
- DoNotUseDestinationValue
- SetMappingOrder
- UseDestinationValue
- UseValue
- ResolveUsing
- Any calculated property on your domain object
Configuration-配置
Profile-修饰
AutoMapper提供了个性化设置Profile,使得我们转换后的数据格式可以多变,当然还可以配置全局格式等等,需要继承自Profile,并重写Configure方法,然后在AutoMapper初始化的时候,讲自定义配置添加到映射配置中,如下面示例:
![](/assets/blank.gif)
1 public class Order 2 { 3 public decimal Amount { get; set; } 4 } 5 public class OrderListViewModel 6 { 7 public string Amount { get; set; } 8 } 9 public class OrderEditViewModel 10 { 11 public string Amount { get; set; } 12 } 13 public class MoneyFormatter : ValueFormatter<decimal> 14 { 15 protected override string FormatValueCore(decimal value) 16 { 17 return value.ToString("c"); 18 } 19 } 20 public class ViewModelProfile : Profile 21 { 22 protected override void Configure() 23 { 24 CreateMap<Order, OrderListViewModel>(); 25 ForSourceType<decimal>().AddFormatter<MoneyFormatter>(); 26 } 27 }
![](/assets/blank.gif)
先创建了一个MoneyFormatter字符格式化类,然后创建ViewModelProfile配置类,在Configure方法中,添加类型映射关系,ForSourceType指的是讲元数据类型添加格式化,配置使用代码:
![](/assets/blank.gif)
1 public void Example() 2 { 3 var order = new Order { Amount = 50m }; 4 //配置 AutoMapper 5 Mapper.Initialize(cfg => 6 { 7 cfg.AddProfile<ViewModelProfile>(); 8 cfg.CreateMap<Order, OrderEditViewModel>(); 9 }); 10 //执行 mapping 11 var listViewModel = Mapper.Map<Order, OrderListViewModel>(order); 12 var editViewModel = Mapper.Map<Order, OrderEditViewModel>(order); 13 14 Console.WriteLine("listViewModel.Amount:" + listViewModel.Amount); 15 Console.WriteLine("editViewModel.Amount:" + editViewModel.Amount); 16 }
![](/assets/blank.gif)
可以看到在Mapper.Initialize初始化的时候,把ViewModelProfile添加到AutoMapper配置中,泛型类型参数必须是Profile的派生类,因为我们在ViewModelProfile的Configure方法中添加了Order到OrderListViewModel类型映射关系,所以我们再初始化的时候就不需要添加了,转换效果:
Naming Conventions-命名约定
在“Flattening-复杂到简单”节点中,我们说到AutoMapper映射转换遵循PascalCase(帕斯卡命名规则),所以我们在类型名称命名要按照PascalCase进行命名,除了默认的命名规则,AutoMapper还提供了一种命名规则,如下:
1 Mapper.Initialize(cfg => { 2 cfg.SourceMemberNamingConvention = new LowerUnderscoreNamingConvention(); 3 cfg.DestinationMemberNamingConvention = new PascalCaseNamingConvention(); 4 });
SourceMemberNamingConvention表示源数据类型命名规则,DestinationMemberNamingConvention表示目标数据类型命名规则,LowerUnderscoreNamingConvention和PascalCaseNamingConvention是AutoMapper提供的两个命名规则,前者命名是小写并包含下划线,后者就是帕斯卡命名规则,所以映射转换的效果是:property_name -> PropertyName。
当然除了在AutoMapper初始化的时候配置命名规则,也可以在Profile中添加全局配置,如下:
![](/assets/blank.gif)
1 public class OrganizationProfile : Profile 2 { 3 protected override void Configure() 4 { 5 SourceMemberNamingConvention = new LowerUnderscoreNamingConvention(); 6 DestinationMemberNamingConvention = new PascalCaseNamingConvention(); 7 } 8 }
![](/assets/blank.gif)
Conditional Mapping-条件映射
AutoMapper允许在类型映射之前添加条件,例如下面示例:
![](/assets/blank.gif)
1 public class Foo 2 { 3 public int baz { get; set; } 4 } 5 public class Bar 6 { 7 public uint baz { get; set; } 8 } 9 public void Example() 10 { 11 var foo = new Foo { baz = 1 }; 12 //配置 AutoMapper 13 Mapper.CreateMap<Foo, Bar>() 14 .ForMember(dest => dest.baz, opt => opt.Condition(src => (src.baz >= 0))); 15 //执行 mapping 16 var result = Mapper.Map<Foo, Bar>(foo); 17 18 Console.WriteLine("result.baz:" + result.baz); 19 }
![](/assets/blank.gif)
上面示例表示当源数据baz大于0的时候,才能执行映射,关键字是Condition,Condition方法接受一个Func<TSource, bool>类型参数,注意已经指定返回值为bool类型,方法签名:
![](/assets/blank.gif)
1 // 2 // 摘要: 3 // Conditionally map this member 4 // 5 // 参数: 6 // condition: 7 // Condition to evaluate using the source object 8 void Condition(Func<TSource, bool> condition);
![](/assets/blank.gif)
转换效果:
AutoMapper版本变化点
在AutoMapper1.1版本中,如果我们要对类型嵌套映射中加入自定义类型映射,比如下面示例:
![](/assets/blank.gif)
1 Mapper.CreateMap<Order, OrderDto>() 2 .Include<OnlineOrder, OnlineOrderDto>() 3 .Include<MailOrder, MailOrderDto>() 4 .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId)); 5 Mapper.CreateMap<OnlineOrder, OnlineOrderDto>() 6 .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId)); 7 Mapper.CreateMap<MailOrder, MailOrderDto>() 8 .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));
![](/assets/blank.gif)
可以看出,我们需要在每个类型映射的地方要加:.ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId));但是Order、OnlineOrder和MailOrder存在继承关系,难道我们如果再加一个派生类映射,就得加一段这样代码,这样就会代码就会变得难以维护。在AutoMapper2.0版本中解决了这一问题,只需要下面这样配置就可以了:
![](/assets/blank.gif)
1 Mapper.CreateMap<Order, OrderDto>() 2 .Include<OnlineOrder, OnlineOrderDto>() 3 .Include<MailOrder, MailOrderDto>() 4 .ForMember(o=>o.Id, m=>m.MapFrom(s=>s.OrderId)); 5 Mapper.CreateMap<OnlineOrder, OnlineOrderDto>(); 6 Mapper.CreateMap<MailOrder, MailOrderDto>();
![](/assets/blank.gif)
类型映射优先级
- Explicit Mapping (using .MapFrom())-显式映射:优先级最高,我们使用MapFrom方法定义映射规则,比如:.ForMember(dest => dest.EventDate, opt => opt.MapFrom(src => src.EventDate.Date))
- Inherited Explicit Mapping-继承的显式映射:就是存在继承关系的MapFrom定义映射规则映射。
- Ignore Property Mapping-忽略属性映射:使用Ignore方法指定属性忽略映射,比如:Mapper.CreateMap<Source, Destination>().ForMember(dest => dest.SomeValuefff, opt => opt.Ignore());
- Convention Mapping (Properties that are matched via convention)-公约映射:公约映射即符合PascalCase命名规则的映射。如Source类中有Value属性,Dest类中也有Value属性,Source和Dest映射关系即是公约映射。
- Inherited Ignore Property Mapping-继承的忽略属性映射:优先级最低,就是存在继承关系的忽略属性映射。
我们举个简单示例来说明下映射优先级:
![](/assets/blank.gif)
1 //Domain Objects 2 public class Order { } 3 public class OnlineOrder : Order 4 { 5 public string Referrer { get; set; } 6 } 7 public class MailOrder : Order { } 8 9 //Dtos 10 public class OrderDto 11 { 12 public string Referrer { get; set; } 13 } 14 public void Example2() 15 { 16 //配置 AutoMapper 17 Mapper.CreateMap<Order, OrderDto>() 18 .Include<OnlineOrder, OrderDto>() 19 .Include<MailOrder, OrderDto>() 20 .ForMember(o => o.Referrer, m => m.Ignore()); 21 Mapper.CreateMap<OnlineOrder, OrderDto>(); 22 Mapper.CreateMap<MailOrder, OrderDto>(); 23 24 //执行 Mapping 25 var order = new OnlineOrder { Referrer = "google" }; 26 var mapped = Mapper.Map(order, order.GetType(), typeof(OrderDto)); 27 }
![](/assets/blank.gif)
转换后mapped对象的Referrer属性值为“google”,但是你发现我们在配置映射规则的时候,不是把Referrer属性给Ignore(忽略)了吗?因为OnlineOrder的ReferrerOrderDto的Referrer属性符合PascalCase命名规则,即是公约映射,虽然忽略属性映射的优先级比公约映射高,但是上面示例中Order和OnlineOrder存在继承关系,即是继承的忽略属性映射,所以优先级比公约映射要低。
后记
示例代码下载:http://pan.baidu.com/s/1comgI
本文转自田园里的蟋蟀博客园博客,原文链接:http://www.cnblogs.com/xishuai/p/3708483.html,如需转载请自行联系原作者
【AutoMapper官方文档】DTO与Domin Model相互转换(下)相关推荐
- 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
写在前面 AutoMapper目录: [AutoMapper官方文档]DTO与Domin Model相互转换(上) [AutoMapper官方文档]DTO与Domin Model相互转换(中) [Au ...
- 【AutoMapper官方文档】DTO与Domin Model相互转换(上)
[AutoMapper官方文档]DTO与Domin Model相互转换(上) 原文: [AutoMapper官方文档]DTO与Domin Model相互转换(上) 写在前面 AutoMapper目录: ...
- Hyperledger Fabric 2.0 官方文档中文版 第6章 教程(下)
Hyperledger Fabric 2.0 官方文档中文版 第6章 教程下 总目录 6.教程(下) 使用CouchDB 为什么使用CouchDB? 在Hyperledger Fabric中启用Cou ...
- DTO与Domin Model相互转换(上)
Flattening-复杂到简单 Flattening 翻译为压扁.拉平.扁平化的意思,可以理解为使原有复杂的结构变得简化,我们先看下领域模型和DTO代码: 1 public class Order ...
- MySQL8.0.28安装教程全程参考MySQL官方文档
MySQL8.0.28详细安装教程.提供了Windows10下安装MariaDB与MySQL8.0同时共存的方法,以及Linux发行版Redhat7系列安装MySQL8.0详细教程.Windows10 ...
- Mybatis官方文档及使用简记
Mybatis官方文档及使用简记 数据库建表 入门案例 无mapper类最传统的用法 使用mybatis generator 使用mybatis-generator mybatis-spring整合 ...
- react router官方文档_阿里开源可插拔 React 跨端框架 UmiJS
点击上方"开发者技术前线",选择"星标" 18:30 在看 真爱 作者:Tamic | 编辑: 可可 阿里之前开源:阿里闲鱼开源 Flutter 应用框架 ...
- Qt官方文档阅读笔记-对官方Star Delegate Example实例的解析
对应的博文为: 目录 Star Delegate Example StarDelegate Class Definition StarDelegate Class Implementation Sta ...
- [译]1-Key-Value Coding Programming Guide 官方文档第一部分
Key-Value Coding Programming Guide 官方文档第一部分 2018.9.20 第一次修正 iOS-KVC官方文档第一部分 Key-Value Coding Program ...
最新文章
- 区块链相关论文研读5:分布式隐私保护可审计的账本,zkLedger
- machine learn in python 第二章2.1.1
- 年终盘点:2015年人工智能的五大关键词
- C/C++写无控制台窗口程序
- CentOS 7.6安装使用Ansible(二):Ansible常用的27个模块
- [Hands On ML] 3. 分类(MNIST手写数字预测)
- C++——cout输出流与字符指针
- 我的Python成长之路---第三天---Python基础(13)---2016年1月16日(雾霾)
- hdu 1710 Binary Tree Traversals (二叉树)
- 单列(写了池子pool)用list实现的方法, 与伪单例(写了池子zidianpool),用字典实现的方法,可以存入不同,i名字的物体...
- 当前局域网禁止BT下载的常用工具及其弊端。
- 深入浅出通信原理(一)
- 苹果越狱后怎么还原_iOS 13.3 越狱提升稳定性,自签又可以使用了
- android开发 自我优势_android开发简历自我评价怎么写
- 《未来世界的幸存者》后感
- excel游戏_Excel集中游戏
- 我的十年 Oracle DBA 奋斗路 - 回首向来萧瑟处,也无风雨也无晴
- 2021世界人工智能大会开幕,百度飞桨荣获“SAIL之星”奖项
- WatchGuard Firebox X50硬件防火墙
- “Whitelabel Error Page“解决方法
热门文章
- vlayout原理分析
- 系列终章 - 放眼未来!进击吧, Blazor !
- 计算机网络作业——物理层
- 2022-2023 科学道德与学风建设(chao星) 自我学习记录日志四(8-10)
- 对不起, 老师 我把知识还给您了 呜呜呜 ......面试杀手-double精度问题深入剖析 进制转换
- 逻辑与和短路与的区别
- 技术抉择:阿里云13年后重构全部核心调度系统
- Windows IIS配置Https免费证书的最简单方法(借助Certify)
- 周四见 | 物流人的一周资讯
- 横扫千军安卓系统有什么服务器,横扫千军手游无法连接服务器怎么办 处理方法一览...