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)以正確地解決遞歸問題。

相關文章
相關標籤/搜索