写在前面
AutoMapper目录:
- 【AutoMapper官方文档】DTO与Domin Model相互转换(上)
- 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
- 【AutoMapper官方文档】DTO与Domin Model相互转换(下)
- 未完待续。。。
本篇目录:
- 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 }