剧情开始

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

  在DDD(领域驱动设计)中,使用AutoMapper一般场景是(Domain Layer)领域层与Presentation Layer(表现层)之间数据对象的转换,也就是DTO与Domin Model之间的相互转换,但是如果对AutoMapper有深入了解之后,就会发现她所涉及的领域不仅仅局限如此,应该包含所有对象之间的转换。另一边,当EntityFramework还在为单身苦恼时,不经意的一瞬间相识了AutoMapper,从此就深深的爱上了她。

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

为何相爱?

  上面是AutoMapper对象转换示意图,可以看出AutoMapper的主要用途是用在对象映射转换上,她不管是什么对象,只是负责转换,就像一个女人在家只负责相夫教子一样。看下AutoMapper的基本用法:

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

  EntityFramework是什么?他是微软开发的基于ADO.NET的ORM(Object/Relational Mapping)框架,是个大人物,是有身份和地位的人,就像一个“王子”一样,而AutoMapper准确的来说只是一个小角色,就像“灰姑娘”一样,况且他们也不是一个世界的人,那为什么EntityFramework会看上AutoMapper呢?这里面必定有内情,我们来探查一番。

  假如存在这样一个业务场景,Order表中存在百万条订单数据,而且Order表有几百列,根据业务场景要求,我们要对订单进行分离,比如:客户信息订单、产品订单等等,可能只是用到订单表中的某些字段,如果我们去做这样的一个操作,可以想象这样查询出的数据是怎样的,某些我们并不需要的字段会查询出来,而且数据并没有得到过滤,所以我们要在数据访问层做下面这样一个操作:

         using (var context = new OrderContext())
{
var orderConsignee = from order in context.Orders
select new OrderConsignee
{
OrderConsigneeId = order.OrderId,
//OrderItems = order.OrderItems,
OrderItemCount = order.OrderItemCount,
ConsigneeName = order.ConsigneeName,
ConsigneeRealName = order.ConsigneeRealName,
ConsigneePhone = order.ConsigneePhone,
ConsigneeProvince = order.ConsigneeProvince,
ConsigneeAddress = order.ConsigneeAddress,
ConsigneeZip = order.ConsigneeZip,
ConsigneeTel = order.ConsigneeTel,
ConsigneeFax = order.ConsigneeFax,
ConsigneeEmail = order.ConsigneeEmail
};
Console.ReadKey();
}

  orderConsignee表示订单客户,这只是订单信息分离的一种子集,如果有多种分离的子集,并且子集中的字段并不比订单表少多少,你就会发现在数据访问层填充这些子集要做的工作量有多少了,虽然它是高效的,从生成的SQL代码中就可以看出:

 SELECT
[Extent1].[OrderItemCount] AS [OrderItemCount],
[Extent1].[OrderId] AS [OrderId],
[Extent1].[ConsigneeName] AS [ConsigneeName],
[Extent1].[ConsigneeRealName] AS [ConsigneeRealName],
[Extent1].[ConsigneePhone] AS [ConsigneePhone],
[Extent1].[ConsigneeProvince] AS [ConsigneeProvince],
[Extent1].[ConsigneeAddress] AS [ConsigneeAddress],
[Extent1].[ConsigneeZip] AS [ConsigneeZip],
[Extent1].[ConsigneeTel] AS [ConsigneeTel],
[Extent1].[ConsigneeFax] AS [ConsigneeFax],
[Extent1].[ConsigneeEmail] AS [ConsigneeEmail]
FROM [dbo].[Orders] AS [Extent1]

  但是这种效果并不能让EntityFramework满意,于是他就盯上了人家AutoMapper,为什么?因为AutoMapper的一段代码就可以搞定上面的问题:

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

相处的问题?

  因为EntityFramework的疯狂追求,再加上他显赫的地位,让AutoMapper不得不接受了他,于是他们就交往了,但好像就是后羿和嫦娥的故事一样,不是一个世界的人,相处起来总会出现一些问题。虽然AutoMapper在对象转换方面很强大,而且大部分应用场景是Domain与ViewModel之间的映射转换,当涉及到数据访问时,AutoMapper就不是那么有用了。换句话说,AutoMapper工作在内存中的对象转换,而不是应用在数据访问中IQueryable的接口,在数据访问层我们使用EntityFramework把要查询的对象转化为SQL命令,如果在数据访问层使用AutoMapper,那么查询数据一定会发生在映射转换之后,而且查询出的数据一定会比转换的数据多,从而产生性能问题。

  上面的示例我们修改下:

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

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

 SELECT
[Extent1].[OrderId] AS [OrderId],
[Extent1].[OrderItemCount] AS [OrderItemCount],
[Extent1].[UserId] AS [UserId],
[Extent1].[ReceiverId] AS [ReceiverId],
[Extent1].[ShopDate] AS [ShopDate],
[Extent1].[OrderDate] AS [OrderDate],
[Extent1].[ConsigneeRealName] AS [ConsigneeRealName],
[Extent1].[ConsigneeName] AS [ConsigneeName],
[Extent1].[ConsigneePhone] AS [ConsigneePhone],
[Extent1].[ConsigneeProvince] AS [ConsigneeProvince],
[Extent1].[ConsigneeAddress] AS [ConsigneeAddress],
[Extent1].[ConsigneeZip] AS [ConsigneeZip],
[Extent1].[ConsigneeTel] AS [ConsigneeTel],
[Extent1].[ConsigneeFax] AS [ConsigneeFax],
[Extent1].[ConsigneeEmail] AS [ConsigneeEmail],
[Extent1].[WhetherCouAndinte] AS [WhetherCouAndinte],
[Extent1].[ParvalueAndInte] AS [ParvalueAndInte],
[Extent1].[PaymentType] AS [PaymentType],
[Extent1].[Payment] AS [Payment],
[Extent1].[Courier] AS [Courier],
[Extent1].[TotalPrice] AS [TotalPrice],
[Extent1].[FactPrice] AS [FactPrice],
[Extent1].[Invoice] AS [Invoice],
[Extent1].[Remark] AS [Remark],
[Extent1].[OrderStatus] AS [OrderStatus],
[Extent1].[SaleUserID] AS [SaleUserID],
[Extent1].[SaleUserType] AS [SaleUserType],
[Extent1].[BusinessmanID] AS [BusinessmanID],
[Extent1].[Carriage] AS [Carriage],
[Extent1].[PaymentStatus] AS [PaymentStatus],
[Extent1].[OgisticsStatus] AS [OgisticsStatus],
[Extent1].[OrderType] AS [OrderType],
[Extent1].[IsOrderNormal] AS [IsOrderNormal]
FROM [dbo].[Orders] AS [Extent1]

  通过上面的SQL语句,会发现,虽然数据访问层代码写的简单了,但是查询的字段并不是我们想要的,也就是说查询发生在映射之前,可以想象如果存在上百万的数据或是上百行,使用AutoMapper进行映射转换是多么的不靠谱,难道EntityFramework和AutoMapper就没有缘分?或者只是EntityFramework的一厢情愿?请看下面。

女人的伟大?

  在EntityFramework和AutoMapper的相处过程中,虽然出现了某些问题,但其实也并不是EntityFramework的错,错就错在他们生不逢地,通过相处AutoMapper也发现EntityFramework是真心对她好,于是AutoMapper决定要做些改变,为了EntityFramework,也为了他们的将来。

  EntityFramework和AutoMapper不在一个世界的原因,前面我们也分析过,一个存在于内存中,一个存在于数据访问中,AutoMapper要做的就是去扩展IQueryable表达式(有点嫦娥下凡的意思哈),从而使他们可以存在于一个世界,于是她为了EntityFramework就做了以下工作:

     public static class QueryableExtensions
{
public static ProjectionExpression<TSource> Project<TSource>(this IQueryable<TSource> source)
{
return new ProjectionExpression<TSource>(source);
}
} public class ProjectionExpression<TSource>
{
private static readonly Dictionary<string, Expression> ExpressionCache = new Dictionary<string, Expression>(); private readonly IQueryable<TSource> _source; public ProjectionExpression(IQueryable<TSource> source)
{
_source = source;
} public IQueryable<TDest> To<TDest>()
{
var queryExpression = GetCachedExpression<TDest>() ?? BuildExpression<TDest>(); return _source.Select(queryExpression);
} private static Expression<Func<TSource, TDest>> GetCachedExpression<TDest>()
{
var key = GetCacheKey<TDest>(); return ExpressionCache.ContainsKey(key) ? ExpressionCache[key] as Expression<Func<TSource, TDest>> : null;
} private static Expression<Func<TSource, TDest>> BuildExpression<TDest>()
{
var sourceProperties = typeof(TSource).GetProperties();
var destinationProperties = typeof(TDest).GetProperties().Where(dest => dest.CanWrite);
var parameterExpression = Expression.Parameter(typeof(TSource), "src"); var bindings = destinationProperties
.Select(destinationProperty => BuildBinding(parameterExpression, destinationProperty, sourceProperties))
.Where(binding => binding != null); var expression = Expression.Lambda<Func<TSource, TDest>>(Expression.MemberInit(Expression.New(typeof(TDest)), bindings), parameterExpression); var key = GetCacheKey<TDest>(); ExpressionCache.Add(key, expression); return expression;
} private static MemberAssignment BuildBinding(Expression parameterExpression, MemberInfo destinationProperty, IEnumerable<PropertyInfo> sourceProperties)
{
var sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == destinationProperty.Name); if (sourceProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(parameterExpression, sourceProperty));
} var propertyNames = SplitCamelCase(destinationProperty.Name); if (propertyNames.Length == )
{
sourceProperty = sourceProperties.FirstOrDefault(src => src.Name == propertyNames[]); if (sourceProperty != null)
{
var sourceChildProperty = sourceProperty.PropertyType.GetProperties().FirstOrDefault(src => src.Name == propertyNames[]); if (sourceChildProperty != null)
{
return Expression.Bind(destinationProperty, Expression.Property(Expression.Property(parameterExpression, sourceProperty), sourceChildProperty));
}
}
} return null;
} private static string GetCacheKey<TDest>()
{
return string.Concat(typeof(TSource).FullName, typeof(TDest).FullName);
} private static string[] SplitCamelCase(string input)
{
return Regex.Replace(input, "([A-Z])", " $1", RegexOptions.Compiled).Trim().Split(' ');
}
}

  修改示例代码:

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

  通过AutoMapper所做的努力,使得代码更加简化,只要配置一个类型映射,传递目标类型,就可以得到我们想要的转换对象,代码如此简洁,我们再来看下生成SQL代码:

 SELECT
[Project1].[OrderId] AS [OrderId],
[Project1].[OrderItemCount] AS [OrderItemCount],
[Project1].[ConsigneeRealName] AS [ConsigneeRealName],
[Project1].[ConsigneeName] AS [ConsigneeName],
[Project1].[ConsigneePhone] AS [ConsigneePhone],
[Project1].[ConsigneeProvince] AS [ConsigneeProvince],
[Project1].[ConsigneeAddress] AS [ConsigneeAddress],
[Project1].[ConsigneeZip] AS [ConsigneeZip],
[Project1].[ConsigneeTel] AS [ConsigneeTel],
[Project1].[ConsigneeFax] AS [ConsigneeFax],
[Project1].[ConsigneeEmail] AS [ConsigneeEmail],
[Project1].[C1] AS [C1],
[Project1].[OrderItemId] AS [OrderItemId],
[Project1].[ProName] AS [ProName],
[Project1].[ProImg] AS [ProImg],
[Project1].[ProPrice] AS [ProPrice],
[Project1].[ProNum] AS [ProNum],
[Project1].[AddTime] AS [AddTime],
[Project1].[ProOtherPara] AS [ProOtherPara],
[Project1].[Order_OrderId] AS [Order_OrderId]
FROM ( SELECT
[Extent1].[OrderId] AS [OrderId],
[Extent1].[OrderItemCount] AS [OrderItemCount],
[Extent1].[ConsigneeRealName] AS [ConsigneeRealName],
[Extent1].[ConsigneeName] AS [ConsigneeName],
[Extent1].[ConsigneePhone] AS [ConsigneePhone],
[Extent1].[ConsigneeProvince] AS [ConsigneeProvince],
[Extent1].[ConsigneeAddress] AS [ConsigneeAddress],
[Extent1].[ConsigneeZip] AS [ConsigneeZip],
[Extent1].[ConsigneeTel] AS [ConsigneeTel],
[Extent1].[ConsigneeFax] AS [ConsigneeFax],
[Extent1].[ConsigneeEmail] AS [ConsigneeEmail],
[Extent2].[OrderItemId] AS [OrderItemId],
[Extent2].[ProName] AS [ProName],
[Extent2].[ProImg] AS [ProImg],
[Extent2].[ProPrice] AS [ProPrice],
[Extent2].[ProNum] AS [ProNum],
[Extent2].[AddTime] AS [AddTime],
[Extent2].[ProOtherPara] AS [ProOtherPara],
[Extent2].[Order_OrderId] AS [Order_OrderId],
CASE WHEN ([Extent2].[OrderItemId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
FROM [dbo].[Orders] AS [Extent1]
LEFT OUTER JOIN [dbo].[OrderItems] AS [Extent2] ON [Extent1].[OrderId] = [Extent2].[Order_OrderId]
) AS [Project1]
ORDER BY [Project1].[OrderId] ASC, [Project1].[C1] ASC

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

         /// <summary>
/// 订单项
/// </summary>
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说不准还真不能在一起,女人真是伟大啊。

剧情收尾?

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

  经过一切风风雨雨,EntityFramework终于和AutoMapper过上了幸福美满的日子,但是看似幸福,但是问题还是不断,有人又提出疑问:

  文章的标题用了“horrible”这个单词,翻译为可怕的,难道说EntityFramework和AutoMapper在一起有那么可怕吗?当然这只是针对EntityFramework使用AutoMapper进行CURD操作,但是我相信EntityFramework和AutoMapper会克服重重困难,生死不渝的。我们也会一直关注他们的婚后生活,未完待续。。。

  如果你也祝福EntityFramework和AutoMapper会永远在一起,那就疯狂的“戳”右下角的“推荐”吧。^_^

恋爱虽易,相处不易:当EntityFramework爱上AutoMapper的更多相关文章

  1. 结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活

    写在前面 我到底是什么? 越界的可怕 做好自己 后记 上一篇<恋爱虽易,相处不易:当EntityFramework爱上AutoMapper>文章的最后提到,虽然AutoMapper为了En ...

  2. DTO学习系列之AutoMapper(五)----当EntityFramework爱上AutoMapper

    有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...

  3. 当EntityFramework爱上AutoMapper

    有时候相识即是一种缘分,相爱也不需要太多的理由,一个眼神足矣,当EntityFramework遇上AutoMapper,就是如此,恋爱虽易,相处不易. 在DDD(领域驱动设计)中,使用AutoMapp ...

  4. DTO学习系列之AutoMapper(六)----EntityFramework和AutoMapper的婚后生活

    写在前面 我到底是什么? 越界的可怕 做好自己 后记 文章标题主要关键字:mapping DTOs to Entities,注意并不是“Entities to DTOs”,表示实体对象到DTO的转换, ...

  5. 使用Adminlite + ASP.NET MVC5(C#) + Entityframework + AutoFac + AutoMapper写了个api接口文档管理系统

    一.演示: 接口查看:http://apidoc.docode.top/ 接口后台:http://apiadmin.docode.top/ 登录:administrator,123456 二.使用到的 ...

  6. 爱与恨的抉择:ASP.NET 5+EntityFramework 7

    EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相处不易:当EntityFramework爱上AutoMapper,只不过这 ...

  7. ASP.NET 5+EntityFramework 7

    爱与恨的抉择:ASP.NET 5+EntityFramework 7   EF7 的纠缠 ASP.NET 5 的无助 忘不了你的好 一开始列出的这个博文大纲,让我想到了很久之前的一篇博文:恋爱虽易,相 ...

  8. 贫血模型;DTO:数据传输对象(Data Transfer Object);AutoMapper ;Domain Model(领域模型);DDD(领域驱动设计)

    ====================== 我自己的理解 ========================== 一:  DTO  我自己的理解,就是 比如你有一个类,跟数据库的table表结构一模一 ...

  9. Repository 仓储,你的归宿究竟在哪?(三)-SELECT 某某某。。。

    写在前面 首先,本篇博文主要包含两个主题: 领域服务中使用仓储 SELECT 某某某(有点晕?请看下面.) 上一篇:Repository 仓储,你的归宿究竟在哪?(二)-这样的应用层代码,你能接受吗? ...

随机推荐

  1. C语言 · 高精度加法

    问题描述 输入两个整数a和b,输出这两个整数的和.a和b都不超过100位. 算法描述 由于a和b都比较大,所以不能直接使用语言中的标准数据类型来存储.对于这种问题,一般使用数组来处理. 定义一个数组A ...

  2. JS调用Android、Ios原生控件

    在上一篇博客中已经和大家聊了,关于JS与Android.Ios原生控件之间相互通信的详细代码实现,今天我们一起聊一下JS调用Android.Ios通信的相同点和不同点,以便帮助我们在进行混合式开发时, ...

  3. SSH实战 · 唯唯乐购项目(中)

    用户模块 三:一级分类的查询 创建一级分类表并导入基本数据 CREATE TABLE `category` (   `cid` int(11) NOT NULL AUTO_INCREMENT,   ` ...

  4. 利用Node.js的Net模块实现一个命令行多人聊天室

    1.net模块基本API 要使用Node.js的net模块实现一个命令行聊天室,就必须先了解NET模块的API使用.NET模块API分为两大类:Server和Socket类.工厂方法. Server类 ...

  5. 【基于WinForm+Access局域网共享数据库的项目总结】之篇一:WinForm开发总体概述与技术实现

    篇一:WinForm开发总体概述与技术实现 篇二:WinForm开发扇形图统计和Excel数据导出 篇三:Access远程连接数据库和窗体打包部署 [小记]:最近基于WinForm+Access数据库 ...

  6. MySQL 系列(二) 你不知道的数据库操作

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网 ...

  7. PHP设计模式(七)适配器模式(Adapter For PHP)

    适配器模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类可以在一起工作. 如下图(借图): // 设置书的接口 // 书接口 interface BookI ...

  8. JavaScript作用域

    JavaScript作用域 JavaScript作用域一直是前端开发的难题,现在只要用五句话就可解决. 一.“JavaScript中无块级作用域” 在Java或C#中存在块级作用域,即:大括号也是一个 ...

  9. 谈谈document.ready和window.onload的区别

    在Jquery里面,我们可以看到两种写法:$(function(){}) 和$(document).ready(function(){}) 这两个方法的效果都是一样的,都是在dom文档树加载完之后执行 ...

  10. SQL SERVER全面优化-------Expert for SQL Server 诊断系列

    现在很多用户被数据库的慢的问题所困扰,又苦于花钱请一个专业的DBA成本太高.软件维护人员对数据库的了解又不是那么深入,所以导致问题迟迟不能解决,或只能暂时解决不能得到根治.开发人员解决数据问题基本又是 ...