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