高清宁 发表于 2025-5-29 17:00:41

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

剧情开始



[*]为何相爱?
[*]相处的问题?
[*]女人的伟大?
[*]剧情收尾?
  有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当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相互转换(下)
为何相爱?


  上面是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                                  };<br>19             Console.ReadKey();
20         }  orderConsignee表示订单客户,这只是订单信息分离的一种子集,如果有多种分离的子集,并且子集中的字段并不比订单表少多少,你就会发现在数据访问层填充这些子集要做的工作量有多少了,虽然它是高效的,从生成的SQL代码中就可以看出:
1 SELECT
2   . AS ,
3   . AS ,
4   . AS ,
5   . AS ,
6   . AS ,
7   . AS ,
8   . AS ,
9   . AS ,
10   . AS ,
11   . AS ,
12   . AS
13   FROM . AS   但是这种效果并不能让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   . AS ,
3   . AS ,
4   . AS ,
5   . AS ,
6   . AS ,
7   . AS ,
8   . AS ,
9   . AS ,
10   . AS ,
11   . AS ,
12   . AS ,
13   . AS ,
14   . AS ,
15   . AS ,
16   . AS ,
17   . AS ,
18   . AS ,
19   . AS ,
20   . AS ,
21   . AS ,
22   . AS ,
23   . AS ,
24   . AS ,
25   . AS ,
26   . AS ,
27   . AS ,
28   . AS ,
29   . AS ,
30   . AS ,
31   . AS ,
32   . AS ,
33   . AS ,
34   . AS
35   FROM . AS   通过上面的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 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);
67
68               if (sourceProperty != null)
69               {
70                     var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames);
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, "()", " $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   . AS ,
3   . AS ,
4   . AS ,
5   . AS ,
6   . AS ,
7   . AS ,
8   . AS ,
9   . AS ,
10   . AS ,
11   . AS ,
12   . AS ,
13   . AS ,
14   . AS ,
15   . AS ,
16   . AS ,
17   . AS ,
18   . AS ,
19   . AS ,
20   . AS ,
21   . AS
22   FROM ( SELECT
23         . AS ,
24         . AS ,
25         . AS ,
26         . AS ,
27         . AS ,
28         . AS ,
29         . AS ,
30         . AS ,
31         . AS ,
32         . AS ,
33         . AS ,
34         . AS ,
35         . AS ,
36         . AS ,
37         . AS ,
38         . AS ,
39         . AS ,
40         . AS ,
41         . AS ,
42         CASE WHEN (. IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS
43         FROM. AS
44         LEFT OUTER JOIN . AS ON . = .
45   )AS
46   ORDER BY . ASC, . ASC  可以看出因为Order和OrderConsignee包含对OrderItems子集的映射关系:
1         /// <summary>
2         /// 订单项
3         /// </summary>
4         public virtual ICollection<OrderItem> OrderItems { get; set; }  所以AutoMapper会自动匹配关联子集进行查询,当然也可以在创建映射关系的时候对OrderItems进行忽略:Mapper.CreateMap().ForMember(dest => dest.OrderItems, opt => opt.Ignore()); 排除OrderItems关联因素,从SQL代码可以看出并没有查询多余的字段,也就是我们想要的效果,这所以的一切都归功于AutoMapper,也许如果没有AutoMapper的努力,她和EntityFramework说不准还真不能在一起,女人真是伟大啊。
剧情收尾?


  示例代码下载: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会永远在一起,那就疯狂的“戳”右下角的“推荐”吧。^_^

        分享到:        QQ空间        新浪微博        腾讯微博        微信        更多
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 恋爱虽易,相处不易:当EntityFramework爱上AutoMapper