当在像NHibernate
或者Entity Framework
之类的ORM
框架中使用AutoMapper的标准方法Mapper.Map
时,您可能会注意到,当AutoMapper
尝试将结果映射到目标类型时,ORM
将查询图形中全部对象的全部字段。javascript
若是你的ORM
表达式是IQueryable
的,你可使用AutoMapper
的QueryableExtensions
帮助方法去解决这个痛点。java
以Entity Framework
为例,好比说你有一个实体OrderLine
,它的成员Item
与另一个实体有关联。若是你想用Item
的Name
属性将它映射到OrderLneDTO
,标准的Mapper.Map
调用将致使实体框架查询整个OrderLine
和Item
表。数据库
使用QueryableExtensions
帮助方法代替。闭包
相关实体:app
public class OrderLine { public int Id { get; set; } public int OrderId { get; set; } public Item Item { get; set; } public decimal Quantity { get; set; } } public class Item { public int Id { get; set; } public string Name { get; set; } }
相关DTO框架
public class OrderLineDTO { public int Id { get; set; } public int OrderId { get; set; } public string Item { get; set; } public decimal Quantity { get; set; } }
你能够像这样使用Queryable Extensions
:函数
Mapper.Initialize(cfg => cfg.CreateMap<OrderLine, OrderLineDTO>() .ForMember(dto => dto.Item, conf => conf.MapFrom(ol => ol.Item.Name))); public List<OrderLineDTO> GetLinesForOrder(int orderId) { using (var context = new orderEntities()) { return context.OrderLines.Where(ol => ol.OrderId == orderId) .ProjectTo<OrderLineDTO>().ToList(); } }
.ProjectTo <OrderLineDTO>()
将告诉AutoMapper的映射引擎向IQueryable发出一个select
子句,该子句将通知实体框架它只须要查询Item表的Name列,就像用Select
子句手动将IQueryable
投影到OrderLineDTO
同样。ui
请注意,要使此功能起做用,必须在Mapping中显式处理全部类型转换。举个例子,你不能经过重写Item
类的ToString()
方法来告诉实体框架只查询Name
列,而且必须明确处理数据类型转换,例如“Double”转“Decimal”。spa
由于AutoMapper构建的LINQ投影经过查询提供器直接转换为SQL查询,映射发生在SQL/ADO.NET级别,并无涉及到你的实体。因此全部数据都被加载到你的DTO中。code
嵌套集合使用Select 映射子级DTO:
from i in db.Instructors orderby i.LastName select new InstructorIndexData.InstructorModel { ID = i.ID, FirstMidName = i.FirstMidName, LastName = i.LastName, HireDate = i.HireDate, OfficeAssignmentLocation = i.OfficeAssignment.Location, Courses = i.Courses.Select(c => new InstructorIndexData.InstructorCourseModel { CourseID = c.CourseID, CourseTitle = c.Title }).ToList() };
以上例子将致使SELECT N + 1问题,由于每一个子成员Course
都将执行一次查询,除非经过ORM指定当即获取。使用LINQ投影,ORM不须要特殊配置或规范。ORM使用LINQ投影来构建所需的确切SQL查询。
若是成员名称不对应,或者您想要建立计算属性,则可使用MapFrom(而不是ResolveUsing)为目标成员提供自定义表达式:
Mapper.Initialize(cfg => cfg.CreateMap<Customer, CustomerDto>() .ForMember(d => d.FullName, opt => opt.MapFrom(c => c.FirstName + " " + c.LastName)) .ForMember(d => d.TotalContacts, opt => opt.MapFrom(c => c.Contacts.Count()));
AutoMapper使用构建的投影传递提供的表达式. 只要您的查询提供器能够解析提供的表达式,全部内容都将一直传递到数据库。
若是表达式被您的查询提供器(Entity Framework,NHibernate等)拒绝,您可能须要调整表达式,直到找到一个被接受的表达式。
有时,你须要彻底替换源类型到目标类型的类型转换。在正常的运行时映射中,经过ConvertUsing方法完成。要在LINQ投影中达到相似的目的,请使用ProjectUsing方法:
cfg.CreateMap<Source, Dest>().ProjectUsing(src => new Dest { Value = 10 });
ProjectUsing
比ConvertUsing
限制略多,由于只有Expression中容许的内容和底层LINQ提供器支持的才有效。
若是你的目标类型有自定义的构造器,但你又不想重写整个映射,那么久使用ConstructProjectionUsing方法:
cfg.CreateMap<Source, Dest>()
.ConstructProjectionUsing(src => new Dest(src.Value + 10));
AutoMapper将根据匹配的名称自动将目标构造函数参数与源成员匹配,所以,若是AutoMapper没法正确匹配目标构造函数,或者在构造期间须要扩展定义,则只能使用此方法。
当目标成员类型是字符串而源成员类型不是时,AutoMapper将自动添加ToString()
。
public class Order { public OrderTypeEnum OrderType { get; set; } } public class OrderDto { public string OrderType { get; set; } } var orders = dbContext.Orders.ProjectTo<OrderDto>().ToList(); orders[0].OrderType.ShouldEqual("Online");
在某些状况下,例如OData,经过IQueryable控制器操做返回的通用DTO。若是没有明确的说明,AutoMapper将展开结果中的全部成员。为了在投影期间控制哪些成员要被展开,在配置中设置ExplicitExpansion而后后传入要显式展开的成员中去。
dbContext.Orders.ProjectTo<OrderDto>(
dest => dest.Customer, dest => dest.LineItems); // 或者基于字符串类型的 dbContext.Orders.ProjectTo<OrderDto>( null, "Customer", "LineItems");
LINQ能够支持聚合查询,AutoMapper又支持LINQ扩展方法。在自定义投影的例子中,若是咱们将TotalContacts
成员重命名为ContactsCount
,AutoMapper 将匹配Count()
扩展方面而且LINQ提供器将计数转换为相关子查询以聚合子记录。
若是LINQ提供程序支持,AutoMapper还能够支持复杂的聚合和嵌套限制:
cfg.CreateMap<Course, CourseModel>()
.ForMember(m => m.EnrollmentsStartingWithA, opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));
此查询返回每一个课程姓氏以字母“A”开头的学生总数。
有时候,投影须要运行时的参数作为它的值。若是须要将当前用户名做为它数据的一部分时,可使用参数化MapFrom配置,来代替使用映射后代码:
string currentUserName = null; cfg.CreateMap<Course, CourseModel>() .ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));
当咱们投影时,咱们将在运行时替换咱们的参数:
dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });
这将经过捕获原始表达式中闭包的字段名称来实现,而后使用匿名对象/字典在将查询发送给查询提供器以前将值应用于参数值。
不是全部映射选项都被支持,由于生成的表达式最终由LINQ提供器来解析。因此只有被LINQ提供器支持的才会被AutoMapper支持:
不支持的:
另外,递归或自引用目标类型不被LINQ提供器支持,因此也不被支持。典型的层次关系数据模型须要公共表表达式参与(CTEs)以正确地解决递归问题。