結婚雖易,終老不易: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/,文中提到EntityFrameworkAutoMapper在一起是可怕的,關於這篇英文文章,作為3.5級都考兩次的我來說真是天書,無奈一邊翻譯一邊猜測看了幾遍,英文好的同學可以提供下中文版,因為文章中有代碼,所以作者表達的意思還是可以理解一些。

  文章標題主要關鍵字:mapping DTOs to Entities,注意並不是“Entities to DTOs”,表示實體對象到DTO的轉換,並做一些CURD操作,代碼看似合理,但是好像又有些不合理,為什么?主要是對所涉及的概念不清晰,EntityFramework、AutoMapper和DTO,他們到底是什么?有沒有在一起的可能?請接着往下看。

我到底是什么?

  EntityFramework(父親)、AutoMapper(母親)和DTO(孩子)你我都知道的官方定義:

  • EntityFramework:是微軟以 ADO.NET 為基礎所發展出來的對象關系對應 (O/R Mapping) 解決方案。
  • AutoMapperObject-Object Mapping工具。
  • DTO數據傳輸對象(Data Transfer Object)。

  EntityFramework的定義中有“ORM“關鍵字,那ORM又是什么?

ORM:對象關系映射(Object/Relation Mapping),是隨着面向對象的軟件開發方法發展而產生的。面向對象的開發方法是當今企業級應用開發環境中的主流開發方法,關系數據庫是企業級應用環境中永久存放數據的主流數據存儲系統。對象和關系數據是業務實體的兩種表現形式,業務實體在內存中表現為對象,在數據庫中表現為關系數據。內存中的對象之間存在關聯和繼承關系,而在數據庫中,關系數據無法直接表達多對多關聯和繼承關系。   --百度百科

  概念清楚了就好辦了,我們再來分析下,從上面定義可以看出:AutoMapper是Object-Object映射工具,EntityFramework是一種ORM框架,DTO是數據傳輸對象(Data Transfer Object),請注意這三個定義中都包含對象(Object)關鍵字,毫無疑問,AutoMapper所做的工作就是ORM中的“O”和DTO中的“O”之間的映射轉換。  

  DTO中的“O”比較好理解,就是數據傳輸對象,不包含任何的業務邏輯,只是存儲數據的一種對象,用於各層之間的數據傳遞。一般的項目都會采用分層設計(也就是常見的三層架構),每一層都是一個相對內聚的設計,一種松耦合結構。而層與層之間進行通訊的就是DTO,而這個“O”常常不是ORM的O。其實也可能不是DomainEntity,也不是ViewModel,但是它卻有可能通過組合、分解等方式進行轉換。

  那ORM中“O”是什么意思?關於ORM的使用,網上有很多的爭論,拋開性能問題,有人提出說“ORM注定了業務邏輯和數據庫的高度耦合”,我覺得這種理解是錯誤的。ORM的“O”是數據對象,與表等有一定的偶合,但它從架構設計上來說,只是倉儲層的內聚設計,與業務邏輯無關(當然現在很多小系統會用它來直接替代業務邏輯對象),而真正的業務邏輯對象(按領域驅動設計來說)是領域對象,真正的的系統核心對象是領域對象,而數據對象是可變的,領域對象則相對穩定,數據對象到領域對象是通過倉儲層的適配器來實現的。

  從上面的結論中可以看出,ORM中的“O”是數據對象。關於數據對象,有人說數據對象是穩定的,那要看數據是基於“數據”的設計,還是基於“對象”的設計。如果是基於“對象”的設計,那么設計之初,就必須把業務對象分析清楚、我們常把它說成領域對象,其實這個對象基本是穩定的(至少核心部分是穩定,如果核心對象變了,那是另一個話題,需求變了),而數據對象可能就不一定了,有可能SqlServer數據有的類型其它數據庫沒有,同樣隨着重構的進行,為了提高性能,可能會對一些表進行拆分和組合,原先在一個表中的數據,分成了兩個表,或在視圖中了。但不管數據表怎么變化,最終也只是倉儲層內部的實現方式變了,當然ORM的“O”也會變,但出了這一層,一切都還是原來的樣子。

  理解這些概念很重要,理解了你會發現,我為什么把EntityFramework看做“父親”,AutoMapper看做“母親”,DTO看做“孩子”,當然這只是某種意義上的關系比作,只有在這三者結合才會出現,比如DTO可以脫離EntityFramework和AutoMapper獨立存在,但他就不是“孩子”的概念了。

越界的可怕

  什么叫越界?就是不是你干的事你卻干了,所做的工作超出自己的范圍之外,Roger Alsing的“Horrible”文章我覺得就是在表達這個意思。AutoMapper的工作范圍只是對象之間的映射轉換,也就是說EntityFramework中的“數據對象”到“DTO”之間的映射轉換,但如果涉及到一些數據訪問或是操作,這就是AutoMapper的越界行為,因為這些操作並不在她的職責范圍之內,而應該是EntityFramework所做的工作。

  某種意義上,可以看做:AutoMapper(母親)只是EntityFramework(父親)和DTO(孩子)之間的橋梁或是溝通,至於賺錢養家的事就交給EntityFramework(父親)去做,如果AutoMapper(母親)幫助EntityFramework(父親)去賺錢養家,可能會造成相反的效果,也就是說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表示自定義類型轉換器,發生在映射之前,對映射的操作做一些處理並返回相應的目標類型,注意這里的處理並不是EntityFramework中的持久化,如果在AutoMapper的自定義類型轉換器中做這些操作,就顯得有點不倫不類了。

  關於AutoMapper這樣的“越界行為”,Roger Alsing總結出了其中的優缺點,本人就不翻譯了,以免起到誤讀的效果。

  優點:

  • 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只要負責對象映射轉換即可,也就是做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參考文檔:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM