DTO学习系列之AutoMapper(四)
本篇目录:
- Mapping Inheritance-映射继承
- Queryable Extensions (LINQ)-扩展查询表达式
- Configuration-配置
- Conditional Mapping-条件映射
- AutoMapper版本变化点
- 类型映射优先级
- 后记
关于AutoMapper写到这基本的东西都差不多了,上一篇定义为灵活配置篇,本篇可以定义为扩展应用篇,加一些补充,关于AutoMapper的项目应用,网上找了几篇英文文章,虽然看不懂,但是代码是相通的,感觉很不错,主要是EntityFramework中运用AutoMapper,数据访问中使用AutoMapper,有支持的,也有反对的,也有提出建议的,自己也正在摸索,希望有机会写篇文章和大家分享下。
插一句:写这些东西,看的人真的很
少,还不如像前几天大家写篇水文,来讨论下C#的好坏增加点人气,呵呵,但是如果是这种思想来编程真是不可饶恕,写这种文章的目的不一定是分享给别人,也
是对自己学习的另一种修炼,毕竟肚子没有什么东西,是写不出来,也是在逼迫自己去学习,当去学习一点东西后,发现其实并不像想象的那么简单,还有很多的东西要去学习,恨只恨自己晚生了几年,还需努力。
Mapping Inheritance-映射继承
关于映射继承,其实在上一章“Lists and Array-集合和数组”这一节点有提到,但是只是说明下AutoMapper解决映射继承所使用的方式,这边我们说下关于AutoMapper在映射继承中的一些特性,比如下面转换示例:

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 { }

源对象和目标对象存在继承关系,和“Lists and Array”节点里面的的示例一样,我们首先要配置AutoMapper,添加类型映射关系和依赖关系,如下:

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>();

关于这三段代码的意义,在“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,使得我们在查询的时候就可以实现转换,比如下面示例:

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 }

代码中的.Project().To就是扩展的查询表达式,详细表达式代码:

1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Linq.Expressions;
5 using System.Reflection;
6 using System.Text.RegularExpressions;
7
8 namespace DTO_AutoMapper使用详解
9 {
10 public static class QueryableExtensions
11 {
12 public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
13 {
14 return new ProjectionExpression<TSource>(source);
15 }
16 }
17
18 public class ProjectionExpression<TSource>
19 {
20 private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>();
21
22 private readonly IQueryable<TSource> _source;
23
24 public ProjectionExpression(IQueryable<TSource> source)
25 {
26 _source = source;
27 }
28
29 public IQueryable<TDest> To<TDest>()
30 {
31 var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>();
32
33 return _source.Select(queryExpression);
34 }
35
36 private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
37 {
38 var key = GetCacheKey<TDest>();
39
40 return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
41 }
42
43 private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
44 {
45 var sourceProperties = typeof(TSource).GetProperties();
46 var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
47 var parameterExpression = Expression.Parameter(typeof(TSource), "src");
48
49 var bindings = destinationProperties
50 .Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
51 .Where(binding => binding != null);
52
53 var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression);
54
55 var key = GetCacheKey<TDest>();
56
57 ExpressionCache.Add(key, expression);
58
59 return expression;
60 }
61
62 private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
63 {
64 var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name);
65
66 if (sourceProperty != null)
67 {
68 return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
69 }
70
71 var propertyNames = SplitCamelCase(destinationProperty.Name);
72
73 if (propertyNames.Length == 2)
74 {
75 sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[0]);
76
77 if (sourceProperty != null)
78 {
79 var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[1]);
80
81 if (sourceChildProperty != null)
82 {
83 return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
84 }
85 }
86 }
87
88 return null;
89 }
90
91 private static string GetCacheKey<TDest>()
92 {
93 return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
94 }
95
96 private static string[] SplitCamelCase(string input)
97 {
98 return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
99 }
100 }
101 }

我们在前几节点中说的自定义映射规则,其实也是属于查询表达式的一种,结合实体框架可以简单的应用下,比如我们要映射到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初始化的时候,讲自定义配置添加到映射配置中,如下面示例:

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 }

先创建了一个MoneyFormatter字符格式化类,然后创建ViewModelProfile配置类,在Configure方法中,添加类型映射关系,ForSourceType指的是讲元数据类型添加格式化,配置使用代码:

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 }

可以看到在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中添加全局配置,如下:

1 public class OrganizationProfile : Profile
2 {
3 protected override void Configure()
4 {
5 SourceMemberNamingConvention = new LowerUnderscoreNamingConvention();
6 DestinationMemberNamingConvention = new PascalCaseNamingConvention();
7 }
8 }

Conditional Mapping-条件映射
AutoMapper允许在类型映射之前添加条件,例如下面示例:

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 }

上面示例表示当源数据baz大于0的时候,才能执行映射,关键字是Condition,Condition方法接受一个Func<TSource, bool>类型参数,注意已经指定返回值为bool类型,方法签名:

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);

转换效果:

AutoMapper版本变化点
在AutoMapper1.1版本中,如果我们要对类型嵌套映射中加入自定义类型映射,比如下面示例:

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));

可以看出,我们需要在每个类型映射的地方要加:.ForMember(o=& gt;o.Id, m=>m.MapFrom(s=>s.OrderId));但是Order、OnlineOrder和MailOrder存在继承关系,难道 我们如果再加一个派生类映射,就得加一段这样代码,这样就会代码就会变得难以维护。在AutoMapper2.0版本中解决了这一问题,只需要下面这样配 置就可以了:

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>();

类型映射优先级
- 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-继承的忽略属性映射:优先级最低,就是存在继承关系的忽略属性映射。
我们举个简单示例来说明下映射优先级:

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 }

转换后mapped对象的Referrer属性值为“google”,但是你发现我们在配置映射规则的时候,不是把Referrer属性给Ignore(忽略)了吗?因为OnlineOrder的ReferrerOrderDto的Referrer属性符合PascalCase命名规则,即是公约映射,虽然忽略属性映射的优先级比公约映射高,但是上面示例中Order和OnlineOrder存在继承关系,即是继承的忽略属性映射,所以优先级比公约映射要低。
后记
示例代码下载:http://pan.baidu.com/s/1comgI
如果你觉得本篇文章对你有所帮助,请点击右下部“推荐”,^_^
参考资料:
- https://github.com/AutoMapper/AutoMapper/wiki
- http://www.cnblogs.com/dudu/archive/2011/12/16/2284828.html
- http://www.cnblogs.com/ego/archive/2009/05/13/1456363.html
- http://www.cnblogs.com/jiguixin/archive/2011/09/19/2181521.html
- http://blog.csdn.net/yujunwu2525/article/details/7850486
- http://www.cnblogs.com/xishuai/p/3708483.html
DTO学习系列之AutoMapper(四)的更多相关文章
- DTO学习系列之AutoMapper(三)
本篇目录: Custom Type Converters-自定义类型转换器 Custom Value Resolvers-自定义值解析器 Null Substitution-空值替换 Containe ...
- DTO学习系列之AutoMapper(六)----EntityFramework和AutoMapper的婚后生活
写在前面 我到底是什么? 越界的可怕 做好自己 后记 文章标题主要关键字:mapping DTOs to Entities,注意并不是“Entities to DTOs”,表示实体对象到DTO的转换, ...
- DTO学习系列之AutoMapper(五)----当EntityFramework爱上AutoMapper
有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...
- DTO学习系列之AutoMapper(二)
本篇目录: Flattening-复杂到简单 Projection-简单到复杂 Configuration Validation-配置验证 Lists and Array-集合和数组 Nested m ...
- DTO学习系列之AutoMapper(一)
一.前言 DTO(Data Transfer Object)数据传输对象,注意关键字“数据”两个字,并不是对象传输对象(Object Transfer Object),所以只是传输数据,并不包含领域业 ...
- Dubbo学习系列之十四(Seata分布式事务方案AT模式)
一直说写有关最新技术的文章,但前面似乎都有点偏了,只能说算主流技术,今天这个主题,我觉得应该名副其实.分布式微服务的深水区并不是单个微服务的设计,而是服务间的数据一致性问题!解决了这个问题,才算是把分 ...
- WP8.1学习系列(第二十四章)——Json解析
.net已经集成了json解析,类名叫DataContractJsonSerializer DataContractJsonSerializer 类型公开以下成员. 构造函数 名称 说明 Da ...
- 分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)
本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用( ...
- Dubbo学习系列之十五(Seata分布式事务方案TCC模式)
上篇的续集. 工具: Idea201902/JDK11/Gradle5.6.2/Mysql8.0.11/Lombok0.27/Postman7.5.0/SpringBoot2.1.9/Nacos1.1 ...
随机推荐
- Codeforces Round #278 (Div. 1)
A A monster is attacking the Cyberland! Master Yang, a braver, is going to beat the monster. Yang an ...
- mysql 查询大量数据内存溢出
使用非buffer 的sql 查询 比如pymysql 的 pymysql.cursor.SSCursion
- 微信支付:redirect_uri参数错误 的解决办法
redirect_url参数错误: 报这个错误,说明你的公众号后台授权设置有问题(一般有两处) 一:检查授权目录 答:支付授权目录是网站发起请求的页面所在目录,并且必须是能通过url地址访问的(与真实 ...
- java学习笔记 (1) —— Strut2.3.24环境搭建
1.打开MyEclipse,添加WebProject,名称为testStruts2 2.配置Tomcat环境. 1) 在窗口——首选项——MyEclipse——Servers下找到Tomcat6.x ...
- WPF、WinForm(C#)多线程编程并更新界面(UI)(转载积累)
using System;using System.Collections.Generic;using System.ComponentModel;using System.Drawing;using ...
- JavaScript文本框统计字数
<html> <head> <title>enter</title> <script language="javascript" ...
- gdb调试整理
调试环境:linux调试工具:gdb 调试类别 1.调试core文件 gdb 应用程序名 core文件名2.调试正在执行的程序 gdb 应用程序名 pid 3.gdb 应用程序名 4 ...
- python修改txt文件内容
①以r模式打开文件并用readlines方法读入列表l中 ②修改相关行,直接用l[n]形式即可 ③关闭文件 ④以w方式打开文件,用writelines方法写入文件(覆盖文件内容) ⑤关闭文件 需要注意 ...
- git强制更新
1.下载远程的库的内容 git fetch --all 2.把HEAD指向刚刚下载的最新的版本 git reset --hard origin/master
- TEncoding & TNetEncoding(使用现成的TBase64Encoding,TEncoding和TMBCSEncoding)
TEncoding and TNetEncoding are abstract classes and you will never instantiate one of them, because ...