2013-02-21 210 views
13

直接使用上下文是否是個好主意?例如,假設我有一個客戶數據庫,用戶可以按名稱搜索它們,顯示一個列表,選擇一個,然後編輯該客戶的屬性。實體框架和WPF最佳實踐

看來我應該使用上下文來獲取客戶列表(映射到POCO或CustomerViewModels),然後立即關閉上下文。然後,當用戶選擇列表中的CustomerViewModels中的一個時,UI的客戶屬性部分填充。

接下來他們可以更改名稱,類型,網站地址,公司大小等。點擊保存按鈕後,我打開一個新的上下文,使用CustomerViewModel中的ID檢索該客戶記錄,並更新其屬性。最後,我打電話SaveChanges()並關閉上下文。這是很多工作。

我的問題是爲什麼不直接與整個環境打交道?我已閱讀使用相同的長壽命範圍的背景是非常糟糕的,並且不可避免地會導致問題。我的假設是,如果應用程序只能由一個人使用,我可以保持上下文暢通無阻。但是,如果將有許多用戶,我希望保持一個簡潔的工作單位,從而根據請求打開和關閉上下文。

有什麼建議嗎?謝謝。


@PGallagher - 感謝您的完整的答案。
@Brice - 您的輸入也很有幫助

但是,@Manos D.'冗餘代碼的縮影'評論讓我有點擔心。讓我通過一個例子。假設我將客戶存儲在數據庫中,並且我的一個客戶屬性是CommunicationMethod。

[Flags] 
public enum CommunicationMethod 
{ 
    None = 0, 
    Print = 1, 
    Email = 2, 
    Fax = 4 
} 

我在WPF管理客戶頁面的UI將包含客戶通信方法(打印,電子郵件,傳真)下的三個複選框。我無法將每個複選框綁定到該枚舉,這是沒有意義的。此外,如果用戶點擊該客戶,起牀並去吃午餐......情景會在那裏呆上幾個小時,這很糟糕。相反,這是我的思維過程。

最終用戶從列表中選擇一個客戶。我新建一個上下文,找到這個客戶並返回一個CustomerViewModel,然後關閉這個上下文(爲了簡單起見,我已經把這個存儲庫留下了)。

using(MyContext ctx = new MyContext()) 
{ 
    CurrentCustomerVM = new CustomerViewModel(ctx.Customers.Find(customerId)); 
} 

此時用戶可以選擇/不選擇打印,電子郵件,傳真按鈕,因爲它們綁定到CustomerViewModel,其中也有一個Save()方法3個布爾屬性。開始。

public class CustomerViewModel : ViewModelBase 
{ 
    Customer _customer; 

    public CustomerViewModel(Customer customer) 
    { 
     _customer = customer; 
    } 


    public bool CommunicateViaEmail 
    { 
     get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email); } 
     set 
     { 
      if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Email)) return; 

      if (value) 
       _customer.CommunicationMethod |= CommunicationMethod.Email; 
      else 
       _customer.CommunicationMethod &= ~CommunicationMethod.Email; 
     } 
    } 
    public bool CommunicateViaFax 
    { 
     get { return _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax); } 
     set 
     { 
      if (value == _customer.CommunicationMethod.HasFlag(CommunicationMethod.Fax)) return; 

      if (value) 
       _customer.CommunicationMethod |= CommunicationMethod.Fax; 
      else 
       _customer.CommunicationMethod &= ~CommunicationMethod.Fax; 
     } 
    } 
    public bool CommunicateViaPrint 
    { 
     get { return _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print); } 
     set 
     { 
      if (value == _customer.CommunicateViaPrint.HasFlag(CommunicationMethod.Print)) return; 

      if (value) 
       _customer.CommunicateViaPrint |= CommunicationMethod.Print; 
      else 
       _customer.CommunicateViaPrint &= ~CommunicationMethod.Print; 
     } 
    } 

    public void Save() 
    { 
     using (MyContext ctx = new MyContext()) 
     { 
      var toUpdate = ctx.Customers.Find(_customer.Id); 
      toUpdate.CommunicateViaEmail = _customer.CommunicateViaEmail; 
      toUpdate.CommunicateViaFax = _customer.CommunicateViaFax; 
      toUpdate.CommunicateViaPrint = _customer.CommunicateViaPrint; 

      ctx.SaveChanges(); 
     } 
    } 
} 

你看到有什麼問題嗎?

回答

17

可以使用長時間運行的上下文;你只需要意識到其中的含義。

上下文表示一個工作單元。每次調用SaveChanges時,所有正在跟蹤的實體的所有未決更改都將保存到數據庫中。正因爲如此,你需要將每個上下文的範圍擴大到有意義的範圍。例如,如果您有一個用於管理客戶的選項卡,另一個用於管理產品,則每個用戶都可以使用一個上下文,以便當用戶單擊保存在客戶選項卡上時,他們對產品所做的所有更改都不會保存。

擁有大量的上下文也可能DetectChanges減緩跟蹤實體。減輕這一點的一種方法是使用更改跟蹤代理。

由於加載一個實體,並保存該實體可能是相當長的時間,打一個樂觀併發異常的機率比用短暫的背景下越大。當一個實體在加載和保存之間在外部進行更改時會發生這些異常。 Handling these exceptions非常簡單,但仍然需要注意。您可以在WPF長壽命的背景下做

一個很酷的事情是綁定到DbSet.Local特性(例如context.Customers.Local)。這是一個ObservableCollection,它包含所有未標記爲刪除的跟蹤實體。

但願這給你多一點信息,以幫助您決定幫助哪種方法。

+0

很好的答案。感謝您的回覆:英孚團隊成員的答案在這裏總是很有價值。 – JYL 2014-03-05 15:20:01

0

上下文不是永久連接到數據庫。它本質上是從磁盤加載的記錄的內存緩存。如果您強制刷新或者將更改保存到磁盤時,它只會在您請求先前未加載的記錄時纔會從數據庫請求記錄。

打開上下文,抓取記錄,關閉上下文,然後將修改後的屬性複製到來自全新上下文的對象是冗餘代碼的縮寫。您應該單獨保留原始上下文,並使用它來執行SaveChanges()。

如果你正在尋找解決併發問題,你應該做的「處理併發性」爲你的實體框架的版本谷歌搜索。

例如,我發現this

編輯迴應評論:

所以從我明白你需要記錄的列的子集與新值所覆蓋,而其餘不受影響?如果是這樣,是的,您需要手動更新這些「新」對象上的這幾列。

我的印象是,您所談論的是反映客戶對象所有字段的表單,並且意在提供對整個客戶記錄的編輯訪問權。在這種情況下,使用新的上下文並且辛苦地逐個複製所有屬性是沒有意義的,因爲最終結果(所有數據都會覆蓋表單值而不考慮年齡)將是相同的。

+0

請參閱我的博文更新,解釋爲什麼我使用CustomerViewModel而不是上下文 – BBauer42 2013-02-22 13:55:44

3

微軟參考:

http://msdn.microsoft.com/en-gb/library/cc853327.aspx

他們說;

限制的ObjectContext的

的範圍在大多數情況下,你應該創建 using語句內的ObjectContext的實例(使用...最終使用在 Visual Basic中)。

這可以通過確保在代碼退出語句塊時自動處理與對象上下文關聯的 資源 來提高性能。

然而,當 控件綁定到對象上下文管理對象,所述 ObjectContext的實例應被只要所述結合是 需要和設置的手動維護。

有關更多信息,請參閱管理對象服務(實體框架)中的資源。 http://msdn.microsoft.com/en-gb/library/bb896325.aspx

說的是;

在長時間運行的對象上下文中,您必須確保在不再需要時處置上下文 。


StackOverflow的參考:

這個StackOverflow的問題,也有一些有用的答案...

Entity Framework Best Practices In Business Logic?

其中一些人士建議,您推廣方面的更高層次並參考它,從而只保留一個單一的上下文。


我十便士的價值:

結束語在using語句的背景下,使垃圾收集清理資源,並防止內存泄漏。

顯然,在簡單的應用程序中,這並不是什麼大問題,但是,如果您有多個屏幕,所有使用大量數據,最終都會遇到麻煩,除非您確定正確配置Context。

因此,我已經採用了類似的方法,你已經提到過,我已經添加了一個AddOrUpdate方法到我的每個存儲庫,我通過我的新或修改實體,並更新或添加它取決於是否它存在。


更新實體屬性:

關於然而更新屬性,我使用它使用反射來複制從一個實體向另一個的所有屬性的簡單功能;

Public Shared Function CopyProperties(Of sourceType As {Class, New}, targetType As {Class, New})(ByVal source As sourceType, ByVal target As targetType) As targetType 
    Dim sourceProperties() As PropertyInfo = source.GetType().GetProperties() 
    Dim targetProperties() As PropertyInfo = GetType(targetType).GetProperties() 

    For Each sourceProp As PropertyInfo In sourceProperties 
     For Each targetProp As PropertyInfo In targetProperties 
      If sourceProp.Name <> targetProp.Name Then Continue For 

      ' Only try to set property when able to read the source and write the target 
      ' 
      ' *** Note: We are checking for Entity Types by Checking for the PropertyType to Start with either a Collection or a Member of the Context Namespace! 
      ' 
      If sourceProp.CanRead And _ 
        targetProp.CanWrite Then 
       ' We want to leave System types alone 
       If sourceProp.PropertyType.FullName.StartsWith("System.Collections") Or (sourceProp.PropertyType.IsClass And _ 
         sourceProp.PropertyType.FullName.StartsWith("System.Collections")) Or sourceProp.PropertyType.FullName.StartsWith("MyContextNameSpace.") Then 
        ' 
        ' Do Not Store 
        ' 
       Else 

        Try 

         targetProp.SetValue(target, sourceProp.GetValue(source, Nothing), Nothing) 

        Catch ex As Exception 

        End Try 

       End If 
      End If 

      Exit For 
     Next 
    Next 

    Return target 
End Function 

我在哪裏做類似的事情;

dbColour = Classes.clsHelpers.CopyProperties(Of Colour, Colour)(RecordToSave, dbColour) 

這減少了我需要爲每個Repository編寫的代碼量!

+0

感謝您的全面評論。對於我在水平線下做出的OP更新,您是否看到任何問題?再次感謝。 – BBauer42 2013-02-22 13:59:42