2012-04-29 63 views
3

說我有一個名爲User的類,這是我的基本實體(我使用DbContext作爲DbSet Users),我用它作爲Data-Access-Layers的底層。比方說,類看起來是這樣的:實體和派生視圖模型 - 只更新公共屬性

public class User 
{ 
    [Key] 
    public int Id { get; set; } 
    public bool Active { get; set; } 
    public string Description { get; set; } 
    public string Username { get; set; } 
    public string Password { get; set; } 
    public byte[] Photo { get; set; } 
    public DateTime Created { get; set; } 
} 

現在我想有純看法,我只顯示用戶是否處於活動狀態,並且簡單的複選框,讓我改變的價值。我不想加載任何其他實體屬性,特別是屬性照片,因爲它只是瘋了。我創建ActivateUserModel認爲是這樣的:

public class ActivateUserModel 
{ 
    [Key] 
    public int Id { get; set; } 
    public bool Active { get; set; } 
} 

我都強類型的視圖叫做激活,這需要ActivateUserModel並顯示它(這只是一個複選框,並隱藏編號),然後我有[HttpPost]激活操作捕獲ActivateUserModel,將其轉換爲User實體,然後將更改保存到數據庫。這是POST動作:

[HttpPost] 
    public ActionResult Activate(ActivateUserModel model) 
    { 
     if (ModelState.IsValid) 
     { 
      User user = new User { Id = model.Id, Active = model.Active }; 
      db.Users.Attach(user); 
      db.Entry(user).Property(u => u.Active).IsModified = true; 
      db.SaveChanges(); 
      return RedirectToAction("Index"); 
     } 
     return View(model); 
    } 

這種方式就像一種魅力。我監視了我對SQL服務器發出的查詢,並且我加載的所有對都是Id/Active,我更新的所有內容也都是基於id的Active change。

但是我不喜歡它的外觀。假設我有擁有50個屬性的實體,並且查看了25個。我不想在我說IsModified = true的地方寫25行。

所以我的問題是:是否有更有效的方法來做同樣的事情,而無需挖掘任何基於反射的方法?我想將數據從任何視圖模型轉移到實體,然後只保存這些屬性。

預先感謝您的答覆,我希望我做的問題不夠清楚:)

+0

您可以使用Automapper將您的視圖模型與現有用戶同步。 – 2012-04-29 09:41:11

回答

6

你可以這樣來做:

[HttpPost] 
public ActionResult Activate(ActivateUserModel model) 
{ 
    if (ModelState.IsValid) 
    { 
     User user = db.Users.Single(u => u.Id == model.Id); 
     db.Entry(user).CurrentValues.SetValues(model); 
     db.SaveChanges(); 

     return RedirectToAction("Index"); 
    } 
    return View(model); 
} 

db.Entry(user).CurrentValues.SetValues(model)將檢查在user屬性也與存在model中的同名,如果是,則將屬性值從model複製到user。如果不是,則保留user中的屬性值不變。

我懷疑這不是基於反射的。但上面的代碼是直接的方式,旨在支持您的場景。

編輯

上面的代碼加載完整user實體包括Photo財產。如果你不喜歡加載可能較大的二進制字段,我建議通過除IsModified之外的其他策略來解決此問題。使用實體框架進行更新強烈依賴於需要加載完整實體的更改跟蹤。嘗試避免此問題時,您將使代碼複雜化並手動爲特定屬性設置Modified標誌。

您可能知道,從數據庫中獲取實體時,您不能排除單個標量屬性的加載情況。我建議將Photo資產轉換爲一個新實體UserPhoto,該實體只有一個IdPhoto屬性,並將導航屬性UserPhoto放入User類中。然後,如果您要將照片與User一起加載或不加載,則可以通過懶惰,急切或明確的加載來決定。

如果要將UserPhoto存儲在單獨的表中,您可以在UserUserPhoto之間創建一對一映射。或者,您甚至可以將Photo列保留在User表中,並通過Table Splitting將兩個實體UserUserPhoto映射到同一個表。

編輯2

關於你的評論,該方法加載 「不必要的東西」。我忘了提及以下內容:

事實上,在上面的代碼中,您有從數據庫中加載實體的成本。但是,如果使用將model應用於裝載的實體user,那麼EF將僅將那些屬性標記爲Modified,與數據庫中的原始值相比,這些屬性確實發生了變化。生成的UPDATE語句將只包含這些列。因此,編寫UPDATE語句的成本最小化。

如果您不想加載實體,您不知道數據庫中的當前列值,並且您不知道確實發生了什麼變化。你唯一的機會是強制更新所有屬性以確保數據庫中的行得到正確更新。在您的檢查中,您必須將包含在ViewModel中的所有25個屬性的IsModified設置爲true。生成的SLQ UPDATE語句將包含全部25列。所以,UPDATE語句可能要貴得多,而且 - 借用你的話 - 不需要的東西。

+0

我很抱歉,但您的建議並沒有太大的幫助。你的建議是通常的加載不必要的東西的書本方法。正如我所說的,我的解決方案工作得很好,它可以滿足需求,而且對SQL服務器運行的查詢也很少(由Anjlab SqlProfiler檢查)。我不會犧牲代碼縮減的性能。無論如何感謝你的努力。 – 2012-04-29 17:08:39

+0

@BarisaPuter:我在回答中添加了Edit2,我忘了提及它,但也許你知道。順便說一句:在這個答案(http://stackoverflow.com/a/10271910/270591)是一種方法來設置多個屬性修改*如果你知道你想更新的屬性名稱。但是爲了實現這個通用解決方案,您可能需要反射才能從ViewModel中提取屬性名稱。 – Slauma 2012-04-29 18:11:03

+0

我不明白SQL更新會如何更昂貴?它只包含我需要更新的25列,而不需要實際更新所有50列。 – 2012-04-29 19:14:23