可查詢擴(kuò)展(Queryable Extensions)
當(dāng)在像NHibernate
或者Entity Framework
之類(lèi)的ORM
框架中使用AutoMapper的標(biāo)準(zhǔn)方法Mapper.Map
時(shí)青瀑,您可能會(huì)注意到弄痹,當(dāng)AutoMapper
嘗試將結(jié)果映射到目標(biāo)類(lèi)型時(shí),ORM
將查詢圖形中所有對(duì)象的所有字段匕累。
如果你的ORM
表達(dá)式是IQueryable
的,你可以使用AutoMapper
的QueryableExtensions
幫助方法去解決這個(gè)痛點(diǎn)臊恋。
以Entity Framework
為例怖侦,比如說(shuō)你有一個(gè)實(shí)體OrderLine
,它的成員Item
與另外一個(gè)實(shí)體有關(guān)聯(lián)距帅。如果你想用Item
的Name
屬性將它映射到OrderLneDTO
右锨,標(biāo)準(zhǔn)的Mapper.Map
調(diào)用將導(dǎo)致實(shí)體框架查詢整個(gè)OrderLine
和Item
表。
使用QueryableExtensions
幫助方法代替碌秸。
相關(guān)實(shí)體:
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; }
}
相關(guān)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發(fā)出一個(gè)select
子句绍移,該子句將通知實(shí)體框架它只需要查詢Item表的Name列悄窃,就像用Select
子句手動(dòng)將IQueryable
投影到OrderLineDTO
一樣。
請(qǐng)注意蹂窖,要使此功能起作用轧抗,必須在Mapping中顯式處理所有類(lèi)型轉(zhuǎn)換。舉個(gè)例子瞬测,你不能通過(guò)重寫(xiě)Item
類(lèi)的ToString()
方法來(lái)告訴實(shí)體框架只查詢Name
列横媚,并且必須明確處理數(shù)據(jù)類(lèi)型轉(zhuǎn)換,例如“Double”轉(zhuǎn)“Decimal”月趟。
防止延遲加載/SELECT N+1 問(wèn)題
因?yàn)锳utoMapper構(gòu)建的LINQ投影通過(guò)查詢提供器直接轉(zhuǎn)換為SQL查詢灯蝴,映射發(fā)生在SQL/ADO.NET級(jí)別,并沒(méi)有涉及到你的實(shí)體孝宗。所以所有數(shù)據(jù)都被加載到你的DTO中穷躁。
嵌套集合使用Select 映射子級(jí)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()
};
以上例子將導(dǎo)致SELECT N + 1問(wèn)題,因?yàn)槊總€(gè)子成員Course
都將執(zhí)行一次查詢碳褒,除非通過(guò)ORM指定立即獲取折砸。使用LINQ投影,ORM不需要特殊配置或規(guī)范沙峻。ORM使用LINQ投影來(lái)構(gòu)建所需的確切SQL查詢睦授。
自定義投影
如果成員名稱(chēng)不對(duì)應(yīng),或者您想要?jiǎng)?chuàng)建計(jì)算屬性摔寨,則可以使用MapFrom(而不是ResolveUsing)為目標(biāo)成員提供自定義表達(dá)式:
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使用構(gòu)建的投影傳遞提供的表達(dá)式. 只要您的查詢提供器可以解析提供的表達(dá)式去枷,所有內(nèi)容都將一直傳遞到數(shù)據(jù)庫(kù)。
如果表達(dá)式被您的查詢提供器(Entity Framework是复,NHibernate等)拒絕删顶,您可能需要調(diào)整表達(dá)式,直到找到一個(gè)被接受的表達(dá)式淑廊。
自定義類(lèi)型轉(zhuǎn)換
有時(shí)逗余,你需要完全替換源類(lèi)型到目標(biāo)類(lèi)型的類(lèi)型轉(zhuǎn)換。在正常的運(yùn)行時(shí)映射中季惩,通過(guò)ConvertUsing方法完成录粱。要在LINQ投影中達(dá)到類(lèi)似的目的,請(qǐng)使用ProjectUsing方法:
cfg.CreateMap<Source, Dest>().ProjectUsing(src => new Dest { Value = 10 });
ProjectUsing
比ConvertUsing
限制略多画拾,因?yàn)橹挥蠩xpression中允許的內(nèi)容和底層LINQ提供器支持的才有效啥繁。
自定義目標(biāo)類(lèi)型構(gòu)造函數(shù)
如果你的目標(biāo)類(lèi)型有自定義的構(gòu)造器,但你又不想重寫(xiě)整個(gè)映射青抛,那么久使用ConstructProjectionUsing方法:
cfg.CreateMap<Source, Dest>()
.ConstructProjectionUsing(src => new Dest(src.Value + 10));
AutoMapper將根據(jù)匹配的名稱(chēng)自動(dòng)將目標(biāo)構(gòu)造函數(shù)參數(shù)與源成員匹配旗闽,因此,如果AutoMapper無(wú)法正確匹配目標(biāo)構(gòu)造函數(shù),或者在構(gòu)造期間需要擴(kuò)展定義适室,則只能使用此方法嫡意。
字符串轉(zhuǎn)換
當(dāng)目標(biāo)成員類(lèi)型是字符串而源成員類(lèi)型不是時(shí),AutoMapper將自動(dòng)添加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");
顯式展開(kāi)
在某些情況下鹅很,例如OData,通過(guò)IQueryable控制器操作返回的通用DTO罪帖。如果沒(méi)有明確的說(shuō)明,AutoMapper將展開(kāi)結(jié)果中的所有成員邮屁。為了在投影期間控制哪些成員要被展開(kāi)整袁,在配置中設(shè)置ExplicitExpansion然后后傳入要顯式展開(kāi)的成員中去。
dbContext.Orders.ProjectTo<OrderDto>(
dest => dest.Customer,
dest => dest.LineItems);
// 或者基于字符串類(lèi)型的
dbContext.Orders.ProjectTo<OrderDto>(
null,
"Customer",
"LineItems");
聚合
LINQ可以支持聚合查詢佑吝,AutoMapper又支持LINQ擴(kuò)展方法坐昙。在自定義投影的例子中,如果我們將TotalContacts
成員重命名為ContactsCount
芋忿,AutoMapper 將匹配Count()
擴(kuò)展方面并且LINQ提供器將計(jì)數(shù)轉(zhuǎn)換為相關(guān)子查詢以聚合子記錄炸客。
如果LINQ提供程序支持,AutoMapper還可以支持復(fù)雜的聚合和嵌套限制:
cfg.CreateMap<Course, CourseModel>()
.ForMember(m => m.EnrollmentsStartingWithA,
opt => opt.MapFrom(c => c.Enrollments.Where(e => e.Student.LastName.StartsWith("A")).Count()));
此查詢返回每個(gè)課程姓氏以字母“A”開(kāi)頭的學(xué)生總數(shù)戈钢。
參數(shù)化
有時(shí)候痹仙,投影需要運(yùn)行時(shí)的參數(shù)做為它的值。如果需要將當(dāng)前用戶名作為它數(shù)據(jù)的一部分時(shí)殉了,可以使用參數(shù)化MapFrom配置开仰,來(lái)代替使用映射后代碼:
string currentUserName = null;
cfg.CreateMap<Course, CourseModel>()
.ForMember(m => m.CurrentUserName, opt => opt.MapFrom(src => currentUserName));
當(dāng)我們投影時(shí),我們將在運(yùn)行時(shí)替換我們的參數(shù):
dbContext.Courses.ProjectTo<CourseModel>(Config, new { currentUserName = Request.User.Name });
這將通過(guò)捕獲原始表達(dá)式中閉包的字段名稱(chēng)來(lái)實(shí)現(xiàn)薪铜,然后使用匿名對(duì)象/字典在將查詢發(fā)送給查詢提供器之前將值應(yīng)用于參數(shù)值众弓。
支持的映射選項(xiàng)
不是所有映射選項(xiàng)都被支持,因?yàn)樯傻谋磉_(dá)式最終由LINQ提供器來(lái)解析隔箍。所以只有被LINQ提供器支持的才會(huì)被AutoMapper支持:
- MapFrom
- Ignore
- UseValue
- NullSubstitute
不支持的:
- Condition
- DoNotUseDestinationValue
- SetMappingOrder
- UseDestinationValue
- ResolveUsing
- Before/AfterMap
- 自定義解析器
- 自定義類(lèi)型轉(zhuǎn)換器
- 在程序域?qū)ο笊系娜魏斡?jì)算屬性
另外谓娃,遞歸或自引用目標(biāo)類(lèi)型不被LINQ提供器支持,所以也不被支持蜒滩。典型的層次關(guān)系數(shù)據(jù)模型需要公共表表達(dá)式參與(CTEs)以正確地解決遞歸問(wèn)題滨达。