2011-05-13 22 views
16

我有一個Web溶液(在VS2010):模型,的ViewModels,DTO的在MVC 3應用

  1. Domain其保持Model類(經由實體框架映射到數據庫表)和Services其中(除其他的東西)負責CRUD操作

  2. WebUI它引用域項目

對於我創建的第一頁,我已經使用Domain項目中的Model類作爲Model在我的強類型視圖中,因爲類很小,我想要顯示和修改所有屬性。

現在我有一個頁面,應該只使用相應域模型的所有屬性的一小部分。我通過使用我的Service類中的查詢結果的投影來檢索這些屬性。但我需要項目分成一類 - 這裏來我對解決方案的問題,我能想到的:

  1. 我介紹ViewModels其住在WebUI項目和揭露IQueryables並向EF data context從服務WebUI項目。然後我可以直接投影到這些ViewModels。

  2. 如果我不希望暴露IQueryables,我把ViewModel類的Domain項目的EF數據上下文,那麼我可以返回的ViewModels直接從服務類的查詢和預測的結果。

  3. 除了ViewModelsWebUI項目中,我介紹Data transfer objects來自於服務類的ViewModels查詢移動數據。

解決方案1和2看起來像工作量相同,我傾向於選擇解決方案2以將所有數據庫問題都保留在單獨的項目中。但不知何故,在Domain項目中有查看-Model。

由於我有更多的類來創建和關注Model-DTO-ViewModel映射,因此解決方案3聽起來更多的工作。我也不明白DTO和ViewModels之間會有什麼區別。 ViewModel不是我想要顯示的Model類的選定屬性的集合嗎?他們不會包含與DTO相同的成員嗎?我爲什麼要區分ViewModels和DTO?

這三種解決方案哪一種更可取,哪些是好處和缺點?還有其他選擇嗎?

感謝您提前反饋!

編輯(因爲我的文字也許太長牆,並已要求代碼)

例子:我有一個Customer實體...

public class Customer 
{ 
    public int ID { get; set; } 
    public string Name { get; set; } 
    public City { get; set; } 
    // ... and many more properties 
} 

...和想要創建一個只顯示(也許允許編輯)列表中的客戶的視圖。在一個服務I類提取我經投影需要該視圖的數據:

public class CustomerService 
{ 
    public List<SomeClass1> GetCustomerNameList() 
    { 
     using (var dbContext = new MyDbContext()) 
     { 
      return dbContext.Customers 
       .Select(c => new SomeClass1 
          { 
           ID = c.ID, 
           Name = c.Name 
          }) 
       .ToList(); 
     } 
    } 
} 

然後是與動作方法的CustomerController。這應該如何?

無論這種方式(一)...

public ActionResult Index() 
{ 
    List<SomeClass1> list = _service.GetCustomerNameList(); 
    return View(list); 
} 

...或者更好的這種方式(B):

public ActionResult Index() 
{ 
    List<SomeClass1> list = _service.GetCustomerNameList(); 

    List<SomeClass2> newList = CreateNewList(list); 

    return View(newList); 
} 

對於選項3以上我說:SomeClass1 (生活在Domain項目中)是DTOSomeClass2(生活在WebUI項目中)是ViewModel

我想知道區分這兩個類是否有意義。爲什麼我不會總是選擇控制器操作的選項(a)(因爲它更容易)?除了DTOSomeClass1)之外,是否有理由引入ViewModelSomeClass2)?

+0

您能否發佈代碼? – jfar 2011-05-13 17:51:54

+0

@jfar:現在有一個代碼示例。 – Slauma 2011-05-13 18:33:38

+0

我很好奇 - 你選擇的方法是如何爲你工作的? – kenwarner 2012-01-24 18:55:56

回答

6

介紹其住在 WebUI中項目的ViewModels和揭露IQueryables 從 服務在WebUI項目的EF數據上下文。然後我可以直接投影到那些 ViewModels。

這樣做的麻煩是你很快遇到了使用EF試圖「扁平化」模型的問題。我遇到類似的事情時,我有一個CommentViewModel類是這樣的:

public class CommentViewModel 
{ 
    public string Content { get; set; } 
    public string DateCreated { get; set; } 
} 

以下EF4查詢投影到CommentViewModel不會爲couldn't translate the ToString() method into SQL工作:

var comments = from c in DbSet where c.PostId == postId 
       select new CommentViewModel() 
       { 
        Content = c.Content, 
        DateCreated = c.DateCreated.ToShortTimeString() 
       }; 

使用類似Automapper是一個不錯的選擇,特別是如果你有很多轉換要做。但是,您也可以創建自己的轉換器,將您的域模型轉換爲您的視圖模型。在我來說,我創建了自己的擴展方法,我Comment域模型轉換爲我CommentViewModel這樣的:

public static class ViewModelConverters 
{ 
    public static CommentViewModel ToCommentViewModel(this Comment comment) 
    { 
     return new CommentViewModel() 
     { 
      Content = comment.Content, 
      DateCreated = comment.DateCreated.ToShortDateString() 
     }; 
    } 

    public static IEnumerable<CommentViewModel> ToCommentViewModelList(this IEnumerable<Comment> comments) 
    { 
     List<CommentViewModel> commentModels = new List<CommentViewModel>(comments.Count()); 

     foreach (var c in comments) 
     { 
      commentModels.Add(c.ToCommentViewModel()); 
     } 

     return commentModels; 
    } 
} 

基本上我做的是執行標準的EF查詢帶回域模型,然後使用擴展方法將結果轉換爲視圖模型。例如,以下方法說明了用法:

public Comment GetComment(int commentId) 
{ 
    return CommentRepository.GetById(commentId); 
} 

public CommentViewModel GetCommentViewModel(int commentId) 
{ 
    return CommentRepository.GetById(commentId).ToCommentViewModel(); 
} 

public IEnumerable<Comment> GetCommentsForPost(int postId) 
{ 
    return CommentRepository.GetCommentsForPost(postId); 
} 

public IEnumerable<CommentViewModel> GetCommentViewModelsForPost(int postId) 
{ 
    return CommentRepository.GetCommentsForPost(postId).ToCommentViewModelList(); 
} 
+0

嗯,但這些擴展方法並不能解決您的問題,直接投影到EF查詢中的字符串DateTime?擴展方法僅適用於「完整」註釋對象。但是如果你想要一個投影,你將不會使用「Comment」,而是使用一些中間類型。你在做什麼?你不能使用你的'ViewModel'(因爲你描述的問題),你不想在查詢中加載一個完整的'Comment'對象。那麼,你投入哪種類型?這不會導致第三種類型 - 最後是「DTO」嗎? – Slauma 2011-05-13 20:33:46

+0

@Slauma我已更新我的回答,以顯示我如何使用它們。基本上我從EF返回一個領域模型,然後將其轉換爲它的視圖模型等效。 – 2011-05-14 13:14:05

+0

@Dan Diplo:我明白了,那麼你基本上沒有對你的查詢進行投影了。您加載完整的對象,然後在內存中「投影」到您的ViewModel。在許多情況下,如果域對象很小,這可能是可以的。但想想你有一個大的域對象,讓我們說50個屬性,但你只想在網頁上顯示10個屬性。那麼你將有一個「查詢開銷」,然後加載40個你不需要的屬性。 – Slauma 2011-05-14 13:24:26

9

我會解決您的問題,通過使用自動映射工具(如AutoMapper)爲您做映射。如果映射很簡單(例如,如果一個類中的所有屬性都應該映射到另一個類上的同名屬性),則AutoMapper將能夠爲您完成所有連接工作,並且您必須給出幾行代碼來說明兩者之間應該有一張地圖。

這樣的話,你可以有你的實體Domain,並在您WebUI幾個視圖模型類,以及地方(最好是在WebUI或相同的子命名空間)之間定義地圖。您的視圖模型將生效 DTO,但您不必擔心域和DTO類之間的轉換過程。

注意:我會強烈建議不要對給你的域實體直接到你的MVC Web UI的視圖。如果您稍後想要使用EF以外的其他功能,則您不希望EF「一直保持」到前端層。

+0

「您的視圖模型實際上是DTO」:您的意思是說我不需要單獨的DTO類?記住「投影」:我不查詢完整的實體,但僅查詢一部分屬性。我會將這些屬性投影到哪種類型?如果我沒有單獨的DTO,我需要將它們直接投影到ViewModels中,對嗎?但是,那麼我的ViewModel類將成爲Domain項目的一部分,而不是WebUI。 – Slauma 2011-05-13 17:05:20

+1

+1 - 我問了一個[類似的問題](http://stackoverflow.com/questions/5119349/ef-entities-vs-service-models-vs-view-models-mvc),並得到了一些很好的迴應。這些也可能有幫助。 – 2011-05-13 18:37:11

+0

@Slauma:由於您使用的是默認情況下使用延遲加載的EF,因此您可以在控制器中進行投影,並將投影直接映射到ViewModel中,而無需從數據庫獲取比您需要的數據更多的數據。 – 2011-05-15 17:23:04

1

談論模型,ViewModels和DTOs很混亂,我個人不喜歡使用這些術語。我更願意談論域名實體,域名服務,操作輸入/結果(又名DTOs)。所有這些類型都位於域圖層中。操作是實體和服務的行爲。除非您構建純粹的CRUD應用程序,否則表示層僅處理輸入/結果類型,而不處理實體。您不需要額外的ViewModel類型,它們是ViewModel(換句話說,視圖的模型)。該視圖用於將操作結果轉換爲HTML,但相同的結果可以序列化爲XML或JSON。您用作ViewModel的部分是域的一部分,而不是表示層。

+0

好的,那基本上是我的選擇(2)(用你的「操作輸入/結果」替換我的術語「ViewModel」)。 – Slauma 2011-05-13 20:24:20

+1

有點晚了:)有些人會不同意我認爲。 ViewModel確實可以包含表示邏輯,因此它似乎使它成爲表示層的一部分。「ViewModel封裝了表示邏輯和狀態」http://blogs.msdn.com/b/dphill/archive/2009/01/31/the-viewmodel-pattern.aspx – 2011-10-16 05:09:44

+0

@AdamTuliper是的,你可以在演示文稿中使用ViewModel層,我只是說這不是必需的,你可以使用從域圖層獲得的DTO作爲ViewModel。 – 2011-10-16 18:26:22