16.AutoMapper 之可查詢擴(kuò)展(Queryable Extensions)

可查詢擴(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的,你可以使用AutoMapperQueryableExtensions幫助方法去解決這個(gè)痛點(diǎn)臊恋。

Entity Framework為例怖侦,比如說(shuō)你有一個(gè)實(shí)體OrderLine,它的成員Item與另外一個(gè)實(shí)體有關(guān)聯(lián)距帅。如果你想用ItemName屬性將它映射到OrderLneDTO右锨,標(biāo)準(zhǔn)的Mapper.Map調(diào)用將導(dǎo)致實(shí)體框架查詢整個(gè)OrderLineItem表。

使用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 });

ProjectUsingConvertUsing限制略多画拾,因?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)題滨达。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市帮掉,隨后出現(xiàn)的幾起案子弦悉,更是在濱河造成了極大的恐慌,老刑警劉巖蟆炊,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件稽莉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涩搓,警方通過(guò)查閱死者的電腦和手機(jī)污秆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門(mén)劈猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人良拼,你說(shuō)我怎么就攤上這事战得。” “怎么了庸推?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵常侦,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我贬媒,道長(zhǎng)聋亡,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任际乘,我火速辦了婚禮坡倔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘脖含。我一直安慰自己罪塔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布养葵。 她就那樣靜靜地躺著征堪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪港柜。 梳的紋絲不亂的頭發(fā)上请契,一...
    開(kāi)封第一講書(shū)人閱讀 49,741評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音夏醉,去河邊找鬼爽锥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛畔柔,可吹牛的內(nèi)容都是我干的氯夷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼靶擦,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腮考!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起玄捕,我...
    開(kāi)封第一講書(shū)人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤踩蔚,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后枚粘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體馅闽,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了福也。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片局骤。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖暴凑,靈堂內(nèi)的尸體忽然破棺而出峦甩,到底是詐尸還是另有隱情,我是刑警寧澤现喳,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布凯傲,位于F島的核電站,受9級(jí)特大地震影響嗦篱,放射性物質(zhì)發(fā)生泄漏泣洞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一默色、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狮腿,春花似錦腿宰、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至贴硫,卻和暖如春椿每,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背英遭。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工间护, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人挖诸。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓汁尺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親多律。 傳聞我的和親對(duì)象是個(gè)殘疾皇子痴突,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容