结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活
寫在前面
- 我到底是什么?
- 越界的可怕
- 做好自己
- 后記
上一篇《戀愛雖易,相處不易:當EntityFramework愛上AutoMapper》文章的最后提到,雖然AutoMapper為了EntityFramework做了一些改變,然后就看似幸福的在一起了,但是有人(Roger Alsing)看不下去,寫了一篇文章:http://rogeralsing.com/2013/12/01/why-mapping-dtos-to-entities-using-automapper-and-entityframework-is-horrible/,文中提到EntityFramework和AutoMapper在一起是可怕的,關(guān)于這篇英文文章,作為3.5級都考兩次的我來說真是天書,無奈一邊翻譯一邊猜測看了幾遍,英文好的同學可以提供下中文版,因為文章中有代碼,所以作者表達的意思還是可以理解一些。
文章標題主要關(guān)鍵字:mapping DTOs to Entities,注意并不是“Entities?to?DTOs”,表示實體對象到DTO的轉(zhuǎn)換,并做一些CURD操作,代碼看似合理,但是好像又有些不合理,為什么?主要是對所涉及的概念不清晰,EntityFramework、AutoMapper和DTO,他們到底是什么?有沒有在一起的可能?請接著往下看。
我到底是什么?
EntityFramework(父親)、AutoMapper(母親)和DTO(孩子)你我都知道的官方定義:
- EntityFramework:是微軟以 ADO.NET 為基礎(chǔ)所發(fā)展出來的對象關(guān)系對應(yīng) (O/R Mapping) 解決方案。
- AutoMapper:Object-Object Mapping工具。
- DTO:數(shù)據(jù)傳輸對象(Data Transfer Object)。
EntityFramework的定義中有“ORM“關(guān)鍵字,那ORM又是什么?
ORM:對象關(guān)系映射(Object/Relation Mapping),是隨著面向?qū)ο蟮能浖_發(fā)方法發(fā)展而產(chǎn)生的。面向?qū)ο蟮拈_發(fā)方法是當今企業(yè)級應(yīng)用開發(fā)環(huán)境中的主流開發(fā)方法,關(guān)系數(shù)據(jù)庫是企業(yè)級應(yīng)用環(huán)境中永久存放數(shù)據(jù)的主流數(shù)據(jù)存儲系統(tǒng)。對象和關(guān)系數(shù)據(jù)是業(yè)務(wù)實體的兩種表現(xiàn)形式,業(yè)務(wù)實體在內(nèi)存中表現(xiàn)為對象,在數(shù)據(jù)庫中表現(xiàn)為關(guān)系數(shù)據(jù)。內(nèi)存中的對象之間存在關(guān)聯(lián)和繼承關(guān)系,而在數(shù)據(jù)庫中,關(guān)系數(shù)據(jù)無法直接表達多對多關(guān)聯(lián)和繼承關(guān)系。 ? --百度百科
概念清楚了就好辦了,我們再來分析下,從上面定義可以看出:AutoMapper是Object-Object映射工具,EntityFramework是一種ORM框架,DTO是數(shù)據(jù)傳輸對象(Data Transfer Object),請注意這三個定義中都包含對象(Object)關(guān)鍵字,毫無疑問,AutoMapper所做的工作就是ORM中的“O”和DTO中的“O”之間的映射轉(zhuǎn)換。
DTO中的“O”比較好理解,就是數(shù)據(jù)傳輸對象,不包含任何的業(yè)務(wù)邏輯,只是存儲數(shù)據(jù)的一種對象,用于各層之間的數(shù)據(jù)傳遞。一般的項目都會采用分層設(shè)計(也就是常見的三層架構(gòu)),每一層都是一個相對內(nèi)聚的設(shè)計,一種松耦合結(jié)構(gòu)。而層與層之間進行通訊的就是DTO,而這個“O”常常不是ORM的O。其實也可能不是DomainEntity,也不是ViewModel,但是它卻有可能通過組合、分解等方式進行轉(zhuǎn)換。
那ORM中“O”是什么意思?關(guān)于ORM的使用,網(wǎng)上有很多的爭論,拋開性能問題,有人提出說“ORM注定了業(yè)務(wù)邏輯和數(shù)據(jù)庫的高度耦合”,我覺得這種理解是錯誤的。ORM的“O”是數(shù)據(jù)對象,與表等有一定的偶合,但它從架構(gòu)設(shè)計上來說,只是倉儲層的內(nèi)聚設(shè)計,與業(yè)務(wù)邏輯無關(guān)(當然現(xiàn)在很多小系統(tǒng)會用它來直接替代業(yè)務(wù)邏輯對象),而真正的業(yè)務(wù)邏輯對象(按領(lǐng)域驅(qū)動設(shè)計來說)是領(lǐng)域?qū)ο?#xff0c;真正的的系統(tǒng)核心對象是領(lǐng)域?qū)ο?#xff0c;而數(shù)據(jù)對象是可變的,領(lǐng)域?qū)ο髣t相對穩(wěn)定,數(shù)據(jù)對象到領(lǐng)域?qū)ο笫峭ㄟ^倉儲層的適配器來實現(xiàn)的。
從上面的結(jié)論中可以看出,ORM中的“O”是數(shù)據(jù)對象。關(guān)于數(shù)據(jù)對象,有人說數(shù)據(jù)對象是穩(wěn)定的,那要看數(shù)據(jù)是基于“數(shù)據(jù)”的設(shè)計,還是基于“對象”的設(shè)計。如果是基于“對象”的設(shè)計,那么設(shè)計之初,就必須把業(yè)務(wù)對象分析清楚、我們常把它說成領(lǐng)域?qū)ο?#xff0c;其實這個對象基本是穩(wěn)定的(至少核心部分是穩(wěn)定,如果核心對象變了,那是另一個話題,需求變了),而數(shù)據(jù)對象可能就不一定了,有可能SqlServer數(shù)據(jù)有的類型其它數(shù)據(jù)庫沒有,同樣隨著重構(gòu)的進行,為了提高性能,可能會對一些表進行拆分和組合,原先在一個表中的數(shù)據(jù),分成了兩個表,或在視圖中了。但不管數(shù)據(jù)表怎么變化,最終也只是倉儲層內(nèi)部的實現(xiàn)方式變了,當然ORM的“O”也會變,但出了這一層,一切都還是原來的樣子。
理解這些概念很重要,理解了你會發(fā)現(xiàn),我為什么把EntityFramework看做“父親”,AutoMapper看做“母親”,DTO看做“孩子”,當然這只是某種意義上的關(guān)系比作,只有在這三者結(jié)合才會出現(xiàn),比如DTO可以脫離EntityFramework和AutoMapper獨立存在,但他就不是“孩子”的概念了。
越界的可怕
什么叫越界?就是不是你干的事你卻干了,所做的工作超出自己的范圍之外,Roger Alsing的“Horrible”文章我覺得就是在表達這個意思。AutoMapper的工作范圍只是對象之間的映射轉(zhuǎn)換,也就是說EntityFramework中的“數(shù)據(jù)對象”到“DTO”之間的映射轉(zhuǎn)換,但如果涉及到一些數(shù)據(jù)訪問或是操作,這就是AutoMapper的越界行為,因為這些操作并不在她的職責范圍之內(nèi),而應(yīng)該是EntityFramework所做的工作。
某種意義上,可以看做:AutoMapper(母親)只是EntityFramework(父親)和DTO(孩子)之間的橋梁或是溝通,至于賺錢養(yǎng)家的事就交給EntityFramework(父親)去做,如果AutoMapper(母親)幫助EntityFramework(父親)去賺錢養(yǎng)家,可能會造成相反的效果,也就是說AutoMapper(母親)請做好“全職太太”即可。
我們來看下AutoMapper(母親)的“越界行為”:
1 public void CreateOrUpdateOrder(OrderDTO orderDTO) 2 { 3 var config = new ... 4 5 //create an instanced mapper instead of the static API 6 var mapper = new AutoMapper.MapperEngine(config); 7 var context = ... //get some DbContext 8 9 config.CreateMap<OrderDTO,Order>() 10 .ConstructUsing((OrderDTO orderDTO) => 11 { 12 if (orderDTO.Id == 0) 13 { 14 var order = new Order(); 15 context.OrderSet.Add(order); 16 return order; 17 } 18 return context.OrderSet.Include("Details").First(o => o.Id == orderDTO.Id); 19 }) 20 //find out what details no longer exist in the DTO and delete the corresponding entities 21 //from the dbcontext 22 .BeforeMap((dto, o) => 23 { 24 o 25 .Details 26 .Where(d => !dto.Details.Any(ddto => ddto.Id == d.Id)).ToList() 27 .ForEach(deleted => context.DetailSet.Remove(deleted)); 28 }); 29 30 config.CreateMap<DetailDTO, Detail>() 31 .ConstructUsing((DetailDTO detailDTO) => 32 { 33 if (detailDTO.Id == 0) 34 { 35 var detail = new Detail(); 36 context.DetailSet.Add(detail); 37 return detail; 38 } 39 return context.DetailSet.First(d => d.Id == detailDTO.Id); 40 }); 41 42 mapper.Map<OrderDTO,Order>(orderDTO); 43 44 context.SaveChanges(); 45 }AutoMapper的ConstructUsing的用法,我們在:http://www.cnblogs.com/xishuai/p/3704435.html#xishuai_h1中有講解,ConstructUsing表示自定義類型轉(zhuǎn)換器,發(fā)生在映射之前,對映射的操作做一些處理并返回相應(yīng)的目標類型,注意這里的處理并不是EntityFramework中的持久化,如果在AutoMapper的自定義類型轉(zhuǎn)換器中做這些操作,就顯得有點不倫不類了。
關(guān)于AutoMapper這樣的“越界行為”,Roger Alsing總結(jié)出了其中的優(yōu)缺點,本人就不翻譯了,以免起到誤讀的效果。
優(yōu)點:
- Looks simple on paper
- Easy to implement on read side and client side
缺點:
- Bloody horrible to implement on the write side, and gets even worse the larger the DTO is
- Relies on magic names if using AutoMapper
- Network ineffective if dealing with large DTOs
- You lose semantics and intentions, there is no way to know WHY a change happened
做好自己
如果我們在上面代碼中去掉AutoMapper,將會變得如何?請看下面:
1 public void CreateOrUpdateOrder(OrderDTO orderDTO) 2 { 3 var ctx = ... //get some DbContext 4 var order = ctx.OrderSet.FirstOrDefault(o => o.Id == orderDTO.Id); 5 if (order == null) 6 { 7 order = new Order(); 8 ctx.OrderSet.Add(order); 9 } 10 11 //Map properties 12 order.Address = orderDTO.Address; 13 14 //remove deleted details 15 order.Details 16 .Where(d => !orderDTO.Details.Any(detailDTO => detailDTO.Id == d.Id)) 17 .Each(deleted => ctx.DetailSet.Remove(deleted)); 18 19 //update or add details 20 orderDTO.Details.Each(detailDTO => 21 { 22 var detail = order.Details.FirstOrDefault(d => d.Id == detailDTO.Id); 23 if (detail == null) 24 { 25 detail = new Detail(); 26 order.Details.Add(detail); 27 } 28 detail.Name = detailDTO.Name; 29 detail.Quantity = detailDTO.Quantity; 30 }); 31 32 context.SaveChanges(); 33 }這樣代碼更加清潔,與使用AutoMapper的代碼形成了明顯的對比,但如果去掉AutoMapper也就失去了DTO的意義,試想沒有母親哪來的孩子?但是如果EntityFramework(父親)、AutoMapper(母親)和DTO(孩子)這三口之家想和諧的生活在一起,那怎么辦?就是AutoMapper只要負責對象映射轉(zhuǎn)換即可,也就是做EntityFramework(父親)和DTO(孩子)之間的“溝通橋梁”,也就是“全職太太”:
1 Mapper.CreateMap<OrderDTO, Order>(); 2 Mapper.CreateMap<DetailDTO, Detail>(); 3 using (var context = new OrderContext()) 4 { 5 var existOrder = context.Orders.FirstOrDefault(order => order.Id == orderDTO.Id); 6 if (existOrder == null) 7 { 8 var order = Mapper.Map<OrderDTO, Order>(orderDTO); 9 context.Orders.Add(order); 10 context.Details.AddRange(order.Details); 11 context.SaveChanges(); 12 } 13 }后記
示例代碼下載:http://pan.baidu.com/s/1o6EzFq6
做了“全職太太”的AutoMapper,就這樣和EntityFramework幸福的生活下去了,看到這有人又可能有疑問,上篇中AutoMapper為EntityFramework做的IQueryable擴展是不是“越界行為”,注意QueryableExtensions只是AutoMapper所做的擴展,并不是代替EntityFramework去完成持久化操作。
如果你覺得EntityFramework和AutoMapper可以幸福終老,那就瘋狂的“戳”右下角的“推薦”吧。^_^
AutoMapper參考文檔:
- 【AutoMapper官方文檔】DTO與Domin Model相互轉(zhuǎn)換(上)
- 【AutoMapper官方文檔】DTO與Domin Model相互轉(zhuǎn)換(中)
- 【AutoMapper官方文檔】DTO與Domin Model相互轉(zhuǎn)換(下)
轉(zhuǎn)載于:https://www.cnblogs.com/xishuai/p/3720662.html
總結(jié)
以上是生活随笔為你收集整理的结婚虽易,终老不易:EntityFramework和AutoMapper的婚后生活的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动设备和SharePoint 2013
- 下一篇: Qt 字符串QString arg()用