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

📅 2026/7/4 13:03:34
恋爱虽易,相处不易:当EntityFramework爱上AutoMapper
为何相爱上面是AutoMapper对象转换示意图可以看出AutoMapper的主要用途是用在对象映射转换上她不管是什么对象只是负责转换就像一个女人在家只负责相夫教子一样。看下AutoMapper的基本用法1 // 配置 AutoMapper 2 Mapper.CreateMapOrder, OrderDto(); 3 // 执行 mapping 4 OrderDto dto Mapper.MapOrder, 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.MapOrder, OrderDto(order);相处的问题因为EntityFramework的疯狂追求再加上他显赫的地位让AutoMapper不得不接受了他于是他们就交往了但好像就是后羿和嫦娥的故事一样不是一个世界的人相处起来总会出现一些问题。虽然AutoMapper在对象转换方面很强大而且大部分应用场景是Domain与ViewModel之间的映射转换当涉及到数据访问时AutoMapper就不是那么有用了。换句话说AutoMapper工作在内存中的对象转换而不是应用在数据访问中IQueryable的接口在数据访问层我们使用EntityFramework把要查询的对象转化为SQL命令如果在数据访问层使用AutoMapper那么查询数据一定会发生在映射转换之后而且查询出的数据一定会比转换的数据多从而产生性能问题。上面的示例我们修改下1 Mapper.CreateMapOrder, OrderConsignee(); 2 var details Mapper.MapIEnumerableOrder, IEnumerableOrderConsignee(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 ProjectionExpressionTSource ProjectTSource(this IQueryableTSource source) 4 { 5 return new ProjectionExpressionTSource(source); 6 } 7 } 8 9 public class ProjectionExpressionTSource 10 { 11 private static readonly Dictionarystring, Expression ExpressionCache new Dictionarystring, Expression(); 12 13 private readonly IQueryableTSource _source; 14 15 public ProjectionExpression(IQueryableTSource source) 16 { 17 _source source; 18 } 19 20 public IQueryableTDest ToTDest() 21 { 22 var queryExpression GetCachedExpressionTDest() ?? BuildExpressionTDest(); 23 24 return _source.Select(queryExpression); 25 } 26 27 private static ExpressionFuncTSource, TDest GetCachedExpressionTDest() 28 { 29 var key GetCacheKeyTDest(); 30 31 return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as ExpressionFuncTSource, TDest : null; 32 } 33 34 private static ExpressionFuncTSource, TDest BuildExpressionTDest() 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.LambdaFuncTSource, TDest(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression); 45 46 var key GetCacheKeyTDest(); 47 48 ExpressionCache.Add(key, expression); 49 50 return expression; 51 } 52 53 private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerablePropertyInfo 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 GetCacheKeyTDest() 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.CreateMapOrder, OrderConsignee();