16.AutoMapper 之可查询扩展(Queryable Extensions)

 

可查询扩展(Queryable Extensions)

当在像NHibernate或者Entity Framework之类的ORM框架中使用AutoMapper的标准方法Mapper.Map 时,您可能会注意到,当AutoMapper尝试将结果映射到目标类型时,ORM将查询图形中全部对象的全部字段。javascript

若是你的ORM表达式是IQueryable的,你可使用AutoMapperQueryableExtensions帮助方法去解决这个痛点。java

Entity Framework为例,好比说你有一个实体OrderLine,它的成员Item与另一个实体有关联。若是你想用ItemName属性将它映射到OrderLneDTO,标准的Mapper.Map调用将致使实体框架查询整个OrderLineItem表。数据库

使用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

防止延迟加载/SELECT N+1 问题

由于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 }); 

ProjectUsingConvertUsing限制略多,由于只有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支持:

  • MapFrom
  • Ignore
  • UseValue
  • NullSubstitute

不支持的:

  • Condition
  • DoNotUseDestinationValue
  • SetMappingOrder
  • UseDestinationValue
  • ResolveUsing
  • Before/AfterMap
  • 自定义解析器
  • 自定义类型转换器
  • 在程序域对象上的任何计算属性

另外,递归或自引用目标类型不被LINQ提供器支持,因此也不被支持。典型的层次关系数据模型须要公共表表达式参与(CTEs)以正确地解决递归问题。

相关文章
相关标签/搜索