恋爱虽易,相处不易:当EntityFramework爱上AutoMapper

剧情开始

                        恋爱虽易,相处不易:当EntityFramework爱上AutoMapper_第1张图片                                                恋爱虽易,相处不易:当EntityFramework爱上AutoMapper_第2张图片


  • 为何相爱?
  • 相处的问题?
  • 女人的伟大?
  • 剧情收尾?

  有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易。


  在DDD(领域驱动设计)中,使用AutoMapper一般场景是(Domain Layer)领域层与Presentation

 Layer(表现层)之间数据对象的转换,也就是DTO与Domin Model之间的相互转换,但是如果对AutoMapper有

深入了解之后,就会发现她所涉及的领域不仅仅局限如此,应该包含所有对象之间的转换。另一边,当

EntityFramework还在为单身苦恼时,不经意的一瞬间相识了AutoMapper,从此就深深的爱上了她。


  AutoMapper是一个强大的Object-Object Mapping工具,关于AutoMapper请参照:

  • 【AutoMapper官方文档】DTO与Domin Model相互转换(上)
  • 【AutoMapper官方文档】DTO与Domin Model相互转换(中)
  • 【AutoMapper官方文档】DTO与Domin Model相互转换(下)

为何相爱?

                         恋爱虽易,相处不易:当EntityFramework爱上AutoMapper_第3张图片


  上面是AutoMapper对象转换示意图,可以看出AutoMapper的主要用途是用在对象映射转换上,她不管是什么

对象,只是负责转换,就像一个女人在家只负责相夫教子一样。看下AutoMapper的基本用法:


1       // 配置 AutoMapper
2       Mapper.CreateMap<Order, OrderDto>(); 3       // 执行 mapping
4       OrderDto dto = Mapper.Map<Order, OrderDto>(order);

  

       EntityFramework是什么?他是微软开发的基于ADO.NET的ORM(Object/Relational Mapping)框架,是个

大人物,是有身份和地位的人,就像一个“王子”一样,而AutoMapper准确的来说只是一个小角色,就像“灰姑娘”一

样,况且他们也不是一个世界的人,那为什么EntityFramework会看上AutoMapper呢?这里面必定有内情,我们来

探查一番。


  假如存在这样一个业务场景,Order表中存在百万条订单数据,而且Order表有几百列,根据业务场景要求,我们

要对订单进行分离,比如:客户信息订单、产品订单等等,可能只是用到订单表中的某些字段,如果我们去做这样的

一个操作,可以想象这样查询出的数据是怎样的,某些我们并不需要的字段会查询出来,而且数据并没有得到过滤,

所以我们要在数据访问层做下面这样一个操作:



复制代码
 1         using (var context = new OrderContext())  2  {  3             var orderConsignee = from order in context.Orders  4                                  select new OrderConsignee  5  {  6                                      OrderConsigneeId = order.OrderId,  7                                      //OrderItems = order.OrderItems,
 8                                      OrderItemCount = order.OrderItemCount,  9                                      ConsigneeName = order.ConsigneeName, 10                                      ConsigneeRealName = order.ConsigneeRealName, 11                                      ConsigneePhone = order.ConsigneePhone, 12                                      ConsigneeProvince = order.ConsigneeProvince, 13                                      ConsigneeAddress = order.ConsigneeAddress, 14                                      ConsigneeZip = order.ConsigneeZip, 15                                      ConsigneeTel = order.ConsigneeTel, 16                                      ConsigneeFax = order.ConsigneeFax, 17                                      ConsigneeEmail = order.ConsigneeEmail 18  };
19 Console.ReadKey(); 20 }
复制代码

  

        orderConsignee表示订单客户,这只是订单信息分离的一种子集,如果有多种分离的子集,并且子集中的字

段并不比订单表少多少,你就会发现在数据访问层填充这些子集要做的工作量有多少了,虽然它是高效的,从生成的

SQL代码中就可以看出:

复制代码
 1 SELECT 
 2     [Extent1].[OrderItemCount] AS [OrderItemCount],  3     [Extent1].[OrderId] AS [OrderId],  4     [Extent1].[ConsigneeName] AS [ConsigneeName],  5     [Extent1].[ConsigneeRealName] AS [ConsigneeRealName],  6     [Extent1].[ConsigneePhone] AS [ConsigneePhone],  7     [Extent1].[ConsigneeProvince] AS [ConsigneeProvince],  8     [Extent1].[ConsigneeAddress] AS [ConsigneeAddress],  9     [Extent1].[ConsigneeZip] AS [ConsigneeZip], 10     [Extent1].[ConsigneeTel] AS [ConsigneeTel], 11     [Extent1].[ConsigneeFax] AS [ConsigneeFax], 12     [Extent1].[ConsigneeEmail] AS [ConsigneeEmail]
13     FROM [dbo].[Orders] AS [Extent1]
复制代码

  

      但是这种效果并不能让EntityFramework满意,于是他就盯上了人家AutoMapper,为什么?因为AutoMapper

的一段代码就可以搞定上面的问题:


1     OrderDto dto = Mapper.Map<Order, OrderDto>(order);

相处的问题?

  

     因为EntityFramework的疯狂追求,再加上他显赫的地位,让AutoMapper不得不接受了他,于是他们就交往

了,但好像就是后羿和嫦娥的故事一样,不是一个世界的人,相处起来总会出现一些问题。虽然AutoMapper在对象

转换方面很强大,而且大部分应用场景是Domain与ViewModel之间的映射转换,当涉及到数据访问时,

AutoMapper就不是那么有用了。换句话说,AutoMapper工作在内存中的对象转换,而不是应用在数据访问中

IQueryable的接口,在数据访问层我们使用EntityFramework把要查询的对象转化为SQL命令,如果在数据访问层

使用AutoMapper,那么查询数据一定会发生在映射转换之后,而且查询出的数据一定会比转换的数据多,从而产生

性能问题。


  上面的示例我们修改下:

1     Mapper.CreateMap<Order, OrderConsignee>(); 2     var details = Mapper.Map<IEnumerable<Order>, IEnumerable<OrderConsignee>>(context.Orders).ToList();

  

      其实这就是EntityFramework看上AutoMapper的原因,也是EntityFramework想要的效果,看下生成的SQL

语句:

复制代码
 1 SELECT
 2     [Extent1].[OrderId] AS [OrderId],  3     [Extent1].[OrderItemCount] AS [OrderItemCount],  4     [Extent1].[UserId] AS [UserId],  5     [Extent1].[ReceiverId] AS [ReceiverId],  6     [Extent1].[ShopDate] AS [ShopDate],  7     [Extent1].[OrderDate] AS [OrderDate],  8     [Extent1].[ConsigneeRealName] AS [ConsigneeRealName],  9     [Extent1].[ConsigneeName] AS [ConsigneeName], 10     [Extent1].[ConsigneePhone] AS [ConsigneePhone], 11     [Extent1].[ConsigneeProvince] AS [ConsigneeProvince], 12     [Extent1].[ConsigneeAddress] AS [ConsigneeAddress], 13     [Extent1].[ConsigneeZip] AS [ConsigneeZip], 14     [Extent1].[ConsigneeTel] AS [ConsigneeTel], 15     [Extent1].[ConsigneeFax] AS [ConsigneeFax], 16     [Extent1].[ConsigneeEmail] AS [ConsigneeEmail], 17     [Extent1].[WhetherCouAndinte] AS [WhetherCouAndinte], 18     [Extent1].[ParvalueAndInte] AS [ParvalueAndInte], 19     [Extent1].[PaymentType] AS [PaymentType], 20     [Extent1].[Payment] AS [Payment], 21     [Extent1].[Courier] AS [Courier], 22     [Extent1].[TotalPrice] AS [TotalPrice], 23     [Extent1].[FactPrice] AS [FactPrice], 24     [Extent1].[Invoice] AS [Invoice], 25     [Extent1].[Remark] AS [Remark], 26     [Extent1].[OrderStatus] AS [OrderStatus], 27     [Extent1].[SaleUserID] AS [SaleUserID], 28     [Extent1].[SaleUserType] AS [SaleUserType], 29     [Extent1].[BusinessmanID] AS [BusinessmanID], 30     [Extent1].[Carriage] AS [Carriage], 31     [Extent1].[PaymentStatus] AS [PaymentStatus], 32     [Extent1].[OgisticsStatus] AS [OgisticsStatus], 33     [Extent1].[OrderType] AS [OrderType], 34     [Extent1].[IsOrderNormal] AS [IsOrderNormal]
35     FROM [dbo].[Orders] AS [Extent1]
复制代码

  

      通过上面的SQL语句,会发现,虽然数据访问层代码写的简单了,但是查询的字段并不是我们想要的,也就是说

查询发生在映射之前,可以想象如果存在上百万的数据或是上百行,使用AutoMapper进行映射转换是多么的不靠

谱,难道EntityFramework和AutoMapper就没有缘分?或者只是EntityFramework的一厢情愿?请看下面。


女人的伟大?

  

     在EntityFramework和AutoMapper的相处过程中,虽然出现了某些问题,但其实也并不是EntityFramework

的错,错就错在他们生不逢地,通过相处AutoMapper也发现EntityFramework是真心对她好,于是AutoMapper决

定要做些改变,为了EntityFramework,也为了他们的将来。


  EntityFramework和AutoMapper不在一个世界的原因,前面我们也分析过,一个存在于内存中,一个存在于数

据访问中,AutoMapper要做的就是去扩展IQueryable表达式(有点嫦娥下凡的意思哈),从而使他们可以存在于一

个世界,于是她为了EntityFramework就做了以下工作:


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

  

      修改示例代码:

1       Mapper.CreateMap<Order, OrderConsignee>(); 2       var details = context.Orders.Project().To<OrderConsignee>();

  

     通过AutoMapper所做的努力,使得代码更加简化,只要配置一个类型映射,传递目标类型,就可以得到我们想

要的转换对象,代码如此简洁,我们再来看下生成SQL代码:


复制代码
 1 SELECT 
 2     [Project1].[OrderId] AS [OrderId],  3     [Project1].[OrderItemCount] AS [OrderItemCount],  4     [Project1].[ConsigneeRealName] AS [ConsigneeRealName],  5     [Project1].[ConsigneeName] AS [ConsigneeName],  6     [Project1].[ConsigneePhone] AS [ConsigneePhone],  7     [Project1].[ConsigneeProvince] AS [ConsigneeProvince],  8     [Project1].[ConsigneeAddress] AS [ConsigneeAddress],  9     [Project1].[ConsigneeZip] AS [ConsigneeZip], 10     [Project1].[ConsigneeTel] AS [ConsigneeTel], 11     [Project1].[ConsigneeFax] AS [ConsigneeFax], 12     [Project1].[ConsigneeEmail] AS [ConsigneeEmail], 13     [Project1].[C1] AS [C1], 14     [Project1].[OrderItemId] AS [OrderItemId], 15     [Project1].[ProName] AS [ProName], 16     [Project1].[ProImg] AS [ProImg], 17     [Project1].[ProPrice] AS [ProPrice], 18     [Project1].[ProNum] AS [ProNum], 19     [Project1].[AddTime] AS [AddTime], 20     [Project1].[ProOtherPara] AS [ProOtherPara], 21     [Project1].[Order_OrderId] AS [Order_OrderId]
22     FROM ( SELECT 
23         [Extent1].[OrderId] AS [OrderId], 24         [Extent1].[OrderItemCount] AS [OrderItemCount], 25         [Extent1].[ConsigneeRealName] AS [ConsigneeRealName], 26         [Extent1].[ConsigneeName] AS [ConsigneeName], 27         [Extent1].[ConsigneePhone] AS [ConsigneePhone], 28         [Extent1].[ConsigneeProvince] AS [ConsigneeProvince], 29         [Extent1].[ConsigneeAddress] AS [ConsigneeAddress], 30         [Extent1].[ConsigneeZip] AS [ConsigneeZip], 31         [Extent1].[ConsigneeTel] AS [ConsigneeTel], 32         [Extent1].[ConsigneeFax] AS [ConsigneeFax], 33         [Extent1].[ConsigneeEmail] AS [ConsigneeEmail], 34         [Extent2].[OrderItemId] AS [OrderItemId], 35         [Extent2].[ProName] AS [ProName], 36         [Extent2].[ProImg] AS [ProImg], 37         [Extent2].[ProPrice] AS [ProPrice], 38         [Extent2].[ProNum] AS [ProNum], 39         [Extent2].[AddTime] AS [AddTime], 40         [Extent2].[ProOtherPara] AS [ProOtherPara], 41         [Extent2].[Order_OrderId] AS [Order_OrderId], 42         CASE WHEN ([Extent2].[OrderItemId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
43         FROM  [dbo].[Orders] AS [Extent1]
44         LEFT OUTER JOIN [dbo].[OrderItems] AS [Extent2] ON [Extent1].[OrderId] = [Extent2].[Order_OrderId]
45     )  AS [Project1]
46     ORDER BY [Project1].[OrderId] ASC, [Project1].[C1] ASC
复制代码

  

      可以看出因为Order和OrderConsignee包含对OrderItems子集的映射关系:


1         /// <summary>
2         /// 订单项 3         /// </summary>
4         public virtual ICollection<OrderItem> OrderItems { get; set; }

  

       所以AutoMapper会自动匹配关联子集进行查询,当然也可以在创建映射关系的时候对OrderItems进行忽略:

Mapper.CreateMap<Order, OrderConsignee>().ForMember(dest => dest.OrderItems, opt =>

 opt.Ignore()); 排除OrderItems关联因素,从SQL代码可以看出并没有查询多余的字段,也就是我们想要的效果,

这所以的一切都归功于AutoMapper,也许如果没有AutoMapper的努力,她和EntityFramework说不准还真不能在

一起,女人真是伟大啊。


剧情收尾?

                                       恋爱虽易,相处不易:当EntityFramework爱上AutoMapper_第4张图片


  示例代码下载:http://pan.baidu.com/s/1c0h9TNM


  经过一切风风雨雨,EntityFramework终于和AutoMapper过上了幸福美满的日子,但是看似幸福,但是问题还

是不断,有人又提出疑问:


  • http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/

  文章的标题用了“horrible”这个单词,翻译为可怕的,难道说EntityFramework和AutoMapper在一起有那么可

怕吗?当然这只是针对EntityFramework使用AutoMapper进行CURD操作,但是我相信EntityFramework和

AutoMapper会克服重重困难,生死不渝的。我们也会一直关注他们的婚后生活,未完待续。。。

你可能感兴趣的:(恋爱虽易,相处不易:当EntityFramework爱上AutoMapper)