2011-03-23 622 views
10

我正在編寫一個WPF應用程序,使用實體框架4作爲ORM的MVVM設計。我的視圖模型中有集合屬性,它將包含從EF4返回的實體集合,作爲IEnumerable<T>集合,以響應業務層提交的查詢。實體框架4和WPF

我本來希望簡單地將IEnumerable<T>結果集換成ObservableCollection<T>。但是,我發現自己在我的存儲庫中編寫變更跟蹤代碼,或者維護已更改對象的影集,只是爲了保持視圖模型和持久層同步。每次將實體添加到視圖模型中的集合中時,我都必須前往我的存儲庫以將其添加到EF4 ObjectSet中。我必須做更新和刪除相同的事情。

爲了簡化工作,我從CodePlex(http://waf.codeplex.com/)上的WPF應用程序框架項目借了一個EdmObservableCollection<T>類。該課程包含一個參考EF4 ObjectContextObservableCollection<T>,以便可以在更新集合時更新OC。我已經重印了下面的EdmObservableCollection課程。該類運行得非常好,但它有一些代碼味道,因爲我最終在我的視圖模型中引用了EF4。

這裏是我的問題:在WPF應用程序中,保持EF4實體集合與其對象上下文同步的常用方法是什麼? EdmObservableCollection是一個合適的方法,還是有更好的方法?我在使用EF4時缺少一些基本的東西嗎?謝謝你的幫助。


using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Data.Objects; 
using System.Linq; 

namespace Ef4Sqlce4Demo.ViewModel.BaseClasses 
{ 
    /// <summary> 
    /// An ObservableCollection for Entity Framework 4 entity collections. 
    /// </summary> 
    /// <typeparam name="T">The type of EF4 entity served.</typeparam> 
    /// <remarks>Developed from WPF Application Framework (WAF) http://waf.codeplex.com/</remarks> 
    public class EdmObservableCollection<T> : ObservableCollection<T> 
    { 
      #region Fields 

      // Member variables 
      private readonly string m_EntitySetName; 
      private readonly ObjectContext m_ObjectContext; 

      #endregion 

      #region Constructors 

      /// <summary> 
      /// Creates a new EDM Observable Collection and populates it with a list of items. 
      /// </summary> 
      /// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param> 
      /// <param name="entitySetName">The name of the entity set in the EDM.</param> 
      /// <param name="items">The items to be inserted into the collection.</param> 
      public EdmObservableCollection(ObjectContext objectContext, string entitySetName, IEnumerable<T> items) 
       : base(items ?? new T[] {}) 
      { 
       if (objectContext == null) 
       { 
        throw new ArgumentNullException("objectContext"); 
       } 
       if (entitySetName == null) 
       { 
        throw new ArgumentNullException("entitySetName"); 
       } 

       m_ObjectContext = objectContext; 
       m_EntitySetName = entitySetName; 
      } 

      /// <summary> 
      /// Creates an empty EDM Observable Collection that has an ObjectContext. 
      /// </summary> 
      /// <param name="objectContext">The EF4 ObjectContext that will manage the collection.</param> 
      /// <param name="entitySetName">The name of the entity set in the EDM.</param> 
      public EdmObservableCollection(ObjectContext objectContext, string entitySetName) 
       : this(objectContext, entitySetName, null) 
      { 
      } 

      /// <summary> 
      /// Creates an empty EDM Observable Collection, with no ObjectContext. 
      /// </summary> 
      /// <remarks> 
      /// We use this constructor to create a placeholder collection before we have an 
      /// ObjectContext to work with. This state occurs when the program is first launched, 
      /// before a file is open. We need to initialize collections in the application's 
      /// ViewModels, so that the MainWindow can get Note and Tag counts, which are zero. 
      /// </remarks> 
      public EdmObservableCollection() 
      { 
      } 

      #endregion 

      #region Method Overrides 

      protected override void InsertItem(int index, T item) 
      { 
       base.InsertItem(index, item); 
       m_ObjectContext.AddObject(m_EntitySetName, item); 
      } 

      protected override void RemoveItem(int index) 
      { 
       T itemToDelete = this[index]; 
       base.RemoveItem(index); 
       m_ObjectContext.DeleteObject(itemToDelete); 
      } 

      protected override void ClearItems() 
      { 
       T[] itemsToDelete = this.ToArray(); 
       base.ClearItems(); 

       foreach (T item in itemsToDelete) 
       { 
        m_ObjectContext.DeleteObject(item); 
       } 
      } 

      protected override void SetItem(int index, T item) 
      { 
       T itemToReplace = this[index]; 
       base.SetItem(index, item); 

       m_ObjectContext.DeleteObject(itemToReplace); 
       m_ObjectContext.AddObject(m_EntitySetName, item); 
      } 

      #endregion 

      #region Public Methods 

      /// <summary> 
      /// Adds an object to the end of the collection. 
      /// </summary> 
      /// <param name="item">The object to be added to the end of the collection.</param> 
      public new void Add(T item) 
      { 
       InsertItem(Count, item); 
      } 

      /// <summary> 
      /// Removes all elements from the collection. 
      /// </summary> 
      /// <param name="clearFromContext">Whether the items should also be deleted from the ObjectContext.</param> 
      public void Clear(bool clearFromContext) 
      { 
       if (clearFromContext) 
       { 
        foreach (T item in Items) 
        { 
         m_ObjectContext.DeleteObject(item); 
        } 
       } 

       base.Clear(); 
      } 

      /// <summary> 
      /// Inserts an element into the collection at the specified index. 
      /// </summary> 
      /// <param name="index">The zero-based index at which item should be inserted.</param> 
      /// <param name="item">The object to insert.</param> 
      public new void Insert(int index, T item) 
      { 
       base.Insert(index, item); 
       m_ObjectContext.AddObject(m_EntitySetName, item); 
      } 

      /// <summary> 
      /// Updates the ObjectContext for changes to the collection. 
      /// </summary> 
      public void Refresh() 
      { 
       m_ObjectContext.SaveChanges(); 
      } 

      /// <summary> 
      /// Removes the first occurrence of a specific object from the collection. 
      /// </summary> 
      /// <param name="item">The object to remove from the collection.</param> 
      public new void Remove(T item) 
      { 
       base.Remove(item); 
       m_ObjectContext.DeleteObject(item); 
      } 

      #endregion 
    } 
} 

回答

5

我覺得我已經做了回答。問題不在集合中,而在於傳遞給集合的內容。該集合不應該直接與ObjectContext一起工作;相反,它應該與它收集的實體類型的知識庫一起工作。因此,應該將一個Repository類傳遞給集合的構造函數,集合中的所有持久性代碼應該被對存儲庫方法的簡單調用所取代。修訂後的集合類如下所示:


編輯: Slauma詢問有關數據驗證(看他的反應),所以我添加了一個CollectionChanging事件,我原本張貼在我的答案的集合類。謝謝,Slauma,趕上!客戶端代碼應該訂閱該事件並使用它來執行驗證。將EventArgs.Cancel屬性設置爲true以取消更改。

集合類

using System; 
using System.Collections.Generic; 
using System.Collections.ObjectModel; 
using System.Linq; 
using Ef4Sqlce4Demo.Persistence.Interfaces; 
using Ef4Sqlce4Demo.ViewModel.Utility; 

namespace Ef4Sqlce4Demo.ViewModel.BaseClasses 
{ 
    /// <summary> 
    /// An ObservableCollection for Entity Framework 4 entity collections. 
    /// </summary> 
    /// <typeparam name="T">The type of EF4 entity served.</typeparam> 
    public class FsObservableCollection<T> : ObservableCollection<T> where T:class 
    { 
     #region Fields 

     // Member variables 
     private readonly IRepository<T> m_Repository; 

     #endregion 

     #region Constructors 

     /// <summary> 
     /// Creates a new FS Observable Collection and populates it with a list of items. 
     /// </summary> 
     /// <param name="items">The items to be inserted into the collection.</param> 
     /// <param name="repository">The Repository for type T.</param> 
     public FsObservableCollection(IEnumerable<T> items, IRepository<T> repository) : base(items ?? new T[] {}) 
     { 
      /* The base class constructor call above uses the null-coalescing operator (the 
      * double-question mark) which specifies a default value if the value passed in 
      * is null. The base class constructor call passes a new empty array of type t, 
      * which has the same effect as calling the constructor with no parameters-- 
      * a new, empty collection is created. */ 

      if (repository == null) throw new ArgumentNullException("repository"); 
      m_Repository = repository; 
     } 

     /// <summary> 
     /// Creates an empty FS Observable Collection, with a repository. 
     /// </summary> 
     /// <param name="repository">The Repository for type T.</param> 
     public FsObservableCollection(IRepository<T> repository) : base() 
     { 
      m_Repository = repository; 
     } 

     #endregion 

     #region Events 

     /// <summary> 
     /// Occurs before the collection changes, providing the opportunity to cancel the change. 
     /// </summary> 
     public event CollectionChangingEventHandler<T> CollectionChanging; 

     #endregion 

     #region Protected Method Overrides 

     /// <summary> 
     /// Inserts an element into the Collection at the specified index. 
     /// </summary> 
     /// <param name="index">The zero-based index at which item should be inserted.</param> 
     /// <param name="item">The object to insert.</param> 
     protected override void InsertItem(int index, T item) 
     { 
      // Raise CollectionChanging event; exit if change cancelled 
      var newItems = new List<T>(new[] {item}); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Add, null, newItems); 
      if (cancelled) return; 

      // Insert new item 
      base.InsertItem(index, item); 
      m_Repository.Add(item); 
     } 

     /// <summary> 
     /// Removes the item at the specified index of the collection. 
     /// </summary> 
     /// <param name="index">The zero-based index of the element to remove.</param> 
     protected override void RemoveItem(int index) 
     { 
      // Initialize 
      var itemToRemove = this[index]; 

      // Raise CollectionChanging event; exit if change cancelled 
      var oldItems = new List<T>(new[] { itemToRemove }); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Remove, oldItems, null); 
      if (cancelled) return; 

      // Remove new item 
      base.RemoveItem(index); 
      m_Repository.Delete(itemToRemove); 
     } 

     /// <summary> 
     /// Removes all items from the collection. 
     /// </summary> 
     protected override void ClearItems() 
     { 
      // Initialize 
      var itemsToDelete = this.ToArray(); 

      // Raise CollectionChanging event; exit if change cancelled 
      var oldItems = new List<T>(itemsToDelete); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Remove, oldItems, null); 
      if (cancelled) return; 

      // Removes all items from the collection. 
      base.ClearItems(); 
      foreach (var item in itemsToDelete) 
      { 
       m_Repository.Delete(item); 
      } 
     } 

     /// <summary> 
     /// Replaces the element at the specified index. 
     /// </summary> 
     /// <param name="index">The zero-based index of the element to replace.</param> 
     /// <param name="newItem">The new value for the element at the specified index.</param> 
     protected override void SetItem(int index, T newItem) 
     { 
      // Initialize 
      var itemToReplace = this[index]; 

      // Raise CollectionChanging event; exit if change cancelled 
      var oldItems = new List<T>(new[] { itemToReplace }); 
      var newItems = new List<T>(new[] { newItem }); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Replace, oldItems, newItems); 
      if (cancelled) return; 

      // Rereplace item 
      base.SetItem(index, newItem); 

      m_Repository.Delete(itemToReplace); 
      m_Repository.Add(newItem); 
     } 

     #endregion 

     #region Public Method Overrides 

     /// <summary> 
     /// Adds an object to the end of the collection. 
     /// </summary> 
     /// <param name="item">The object to be added to the end of the collection.</param> 
     public new void Add(T item) 
     { 
      // Raise CollectionChanging event; exit if change cancelled 
      var newItems = new List<T>(new[] { item }); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Add, null, newItems); 
      if (cancelled) return; 

      // Add new item 
      base.Add(item); 
      m_Repository.Add(item); 
     } 

     /// <summary> 
     /// Removes all elements from the collection and from the data store. 
     /// </summary> 
     public new void Clear() 
     { 
      /* We call the overload of this method with the 'clearFromDataStore' 
      * parameter, hard-coding its value as true. */ 

      // Call overload with parameter 
      this.Clear(true); 
     } 

     /// <summary> 
     /// Removes all elements from the collection. 
     /// </summary> 
     /// <param name="clearFromDataStore">Whether the items should also be deleted from the data store.</param> 
     public void Clear(bool clearFromDataStore) 
     { 
      // Initialize 
      var itemsToDelete = this.ToArray(); 

      // Raise CollectionChanging event; exit if change cancelled 
      var oldItems = new List<T>(itemsToDelete); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Remove, oldItems, null); 
      if (cancelled) return; 

      // Remove all items from the collection. 
      base.Clear(); 

      // Exit if not removing from data store 
      if (!clearFromDataStore) return; 

      // Remove all items from the data store 
      foreach (var item in itemsToDelete) 
      { 
       m_Repository.Delete(item); 
      } 
     } 

     /// <summary> 
     /// Inserts an element into the collection at the specified index. 
     /// </summary> 
     /// <param name="index">The zero-based index at which item should be inserted.</param> 
     /// <param name="item">The object to insert.</param> 
     public new void Insert(int index, T item) 
     { 
      // Raise CollectionChanging event; exit if change cancelled 
      var newItems = new List<T>(new[] { item }); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Add, null, newItems); 
      if (cancelled) return; 

      // Insert new item 
      base.Insert(index, item); 
      m_Repository.Add(item); 
     } 

     /// <summary> 
     /// Persists changes to the collection to the data store. 
     /// </summary> 
     public void PersistToDataStore() 
     { 
      m_Repository.SaveChanges(); 
     } 

     /// <summary> 
     /// Removes the first occurrence of a specific object from the collection. 
     /// </summary> 
     /// <param name="itemToRemove">The object to remove from the collection.</param> 
     public new void Remove(T itemToRemove) 
     { 
      // Raise CollectionChanging event; exit if change cancelled 
      var oldItems = new List<T>(new[] { itemToRemove }); 
      var cancelled = this.RaiseCollectionChangingEvent(NotifyCollectionChangingAction.Remove, oldItems, null); 
      if (cancelled) return; 

      // Remove target item 
      base.Remove(itemToRemove); 
      m_Repository.Delete(itemToRemove); 
     } 

     #endregion 

     #region Private Methods 

     /// <summary> 
     /// Raises the CollectionChanging event. 
     /// </summary> 
     /// <returns>True if a subscriber cancelled the change, false otherwise.</returns> 
     private bool RaiseCollectionChangingEvent(NotifyCollectionChangingAction action, IList<T> oldItems, IList<T> newItems) 
     { 
      // Exit if no subscribers 
      if (CollectionChanging == null) return false; 

      // Create event args 
      var e = new NotifyCollectionChangingEventArgs<T>(action, oldItems, newItems); 

      // Raise event 
      this.CollectionChanging(this, e); 

      /* Subscribers can set the Cancel property on the event args; the 
      * event args will reflect that change after the event is raised. */ 

      // Set return value 
      return e.Cancel; 
     } 

     #endregion 
    } 
} 

事件的args類

using System; 
using System.Collections.Generic; 

namespace Ef4Sqlce4Demo.ViewModel.Utility 
{ 

    #region Enums 

    /// <summary> 
    /// Describes the action that caused a CollectionChanging event. 
    /// </summary> 
    public enum NotifyCollectionChangingAction { Add, Remove, Replace, Move, Reset } 

    #endregion 

    #region Delegates 

    /// <summary> 
    /// Occurs before an item is added, removed, changed, moved, or the entire list is refreshed. 
    /// </summary> 
    /// <typeparam name="T">The type of elements in the collection.</typeparam> 
    /// <param name="sender">The object that raised the event.</param> 
    /// <param name="e">Information about the event.</param> 
    public delegate void CollectionChangingEventHandler<T>(object sender, NotifyCollectionChangingEventArgs<T> e); 

    #endregion 

    #region Event Args 

    public class NotifyCollectionChangingEventArgs<T> : EventArgs 
    { 
     #region Constructors 

     /// <summary> 
     /// Constructor with all arguments. 
     /// </summary> 
     /// <param name="action">The action that caused the event. </param> 
     /// <param name="oldItems">The list of items affected by a Replace, Remove, or Move action.</param> 
     /// <param name="newItems">The list of new items involved in the change.</param> 
     /// <param name="oldStartingIndex">The index at which a Move, Remove, or Replace action is occurring.</param> 
     /// <param name="newStartingIndex">The index at which the change is occurring.</param> 
     public NotifyCollectionChangingEventArgs(NotifyCollectionChangingAction action, IList<T> oldItems, IList<T> newItems, int oldStartingIndex, int newStartingIndex) 
     { 
      this.Action = action; 
      this.OldItems = oldItems; 
      this.NewItems = newItems; 
      this.OldStartingIndex = oldStartingIndex; 
      this.NewStartingIndex = newStartingIndex; 
      this.Cancel = false; 
     } 

     /// <summary> 
     /// Constructor that omits 'starting index' arguments. 
     /// </summary> 
     /// <param name="action">The action that caused the event. </param> 
     /// <param name="oldItems">The list of items affected by a Replace, Remove, or Move action.</param> 
     /// <param name="newItems">The list of new items involved in the change.</param> 
     public NotifyCollectionChangingEventArgs(NotifyCollectionChangingAction action, IList<T> oldItems, IList<T> newItems) 
     { 
      this.Action = action; 
      this.OldItems = oldItems; 
      this.NewItems = newItems; 
      this.OldStartingIndex = -1; 
      this.NewStartingIndex = -1; 
      this.Cancel = false; 
     } 

     #endregion 

     #region Properties 

     /// <summary> 
     /// Gets the action that caused the event. 
     /// </summary> 
     public NotifyCollectionChangingAction Action { get; private set; } 

     /// <summary> 
     /// Whether to cancel the pending change. 
     /// </summary> 
     /// <remarks>This property is set by an event subscriber. It enables 
     /// the subscriber to cancel the pending change.</remarks> 
     public bool Cancel { get; set; } 

     /// <summary> 
     /// Gets the list of new items involved in the change. 
     /// </summary> 
     public IList<T> NewItems { get; private set; } 

     /// <summary> 
     /// Gets the index at which the change is occurring. 
     /// </summary> 
     public int NewStartingIndex { get; set; } 

     /// <summary> 
     /// Gets the list of items affected by a Replace, Remove, or Move action. 
     /// </summary> 
     public IList<T> OldItems { get; private set; } 

     /// <summary> 
     /// Gets the index at which a Move, Remove, or Replace action is occurring. 
     /// </summary> 
     public int OldStartingIndex { get; set; } 

     #endregion 

    } 

    #endregion 
} 
0

我可能會使用一個​​實現的抽象級別在你的視圖模型,如果你想要它。

不利之處在於,每次創建一個集合時,都必須限制自己調用工廠。

所以如果你的工廠有過這樣的API(你可以用任何你想切換):

public static class ObjectBuilder 
{ 
    static Factory; 
    SetFactory(IFactory factory) { Factory = factory; }; 
    T CreateObject<T>() { return factory.Create<T>();}; 
    TCollection<T> CreateObject<TCollection,T>>() 
    { 
    return Factory.Create<TCollection,T>(); 
    }  
    TCollection<T> CreateObject<TCollection,T>>(TCollection<T> items) 
    { 
    return Factory.Create<TCollection,T>(TCollection<T> items); 
    } 
} 

所以現在你會:

  • 只實現IFactory來回報您的EdmObservableCollection每當TCollection is ObservableCollection
  • 每當你的應用程序初始化通話ObjectBuilder.SetFactory()
  • 現在在你的ViewModels不管你想要這個,你只需撥打ObjectBuilder.Create<ObservableCollection,MyEntity>();

此外,如果/當你需要改變你的ORM後端您只需執行一個新的IFactory並調用ObjectBuilder.SetFactory(factory)

1

我將一些想法,但沒有具有最終的答案扔。

的基本問題是,在我看來:是操作的用戶可以在相關數據庫操作以獨特的方式在UI常做?或者更具體的:如果一個用戶可以從列表上的用戶界面中刪除項目或插入一個新的項目到一個列表,這是否一定意味着記錄必須從刪除或插入數據庫?

我認爲,答案是:

號起初我可以看到一個很好的用例與EdmObservableCollection<T>工作。也就是說例如在WPF UI視圖,只有這勢必給客戶的集合DataGrid。客戶列表將由查詢規範提取。現在用戶可以在此DataGrid中進行編輯:他可以更改行(單個客戶),他可以插入一個新行並可以刪除一行。 DataGrid很容易支持這些操作,數據綁定引擎直接將這些「CUD」操作寫入綁定的EdmObservableCollection。在這種情況下刪除行或插入新行實際上應該直接反映在數據庫上,從而爲它處理插入,並在內部的ObjectContext刪除EdmObservableCollection可能是非常有用的。

但即使在這種簡單的情況,有考慮幾點:

  • 你可能需要注入的ObjectContext /存儲庫到您的視圖模型反正(來查詢你想放的對象到集合中) - 並且它必須與注入到EdmObservableCollection中以正確處理對象更新(編輯客戶行)相同的上下文。如果您在調用SaveChanges之前不想進行手動「遲到」更改跟蹤,則還必須使用更改跟蹤對象/代理。

  • 這種「通用」刪除操作的EdmObservableCollection<T>提供不考慮數據庫或業務限制。例如,如果用戶試圖爲分配給各種訂單的客戶刪除行,會發生什麼情況?如果數據庫中存在外鍵關係,SaveChanges將失敗並拋出異常。那麼,你可能會發現這個異常,評估它並給用戶留言。但是也許他做了很多其他的改變(編輯了許多其他的行並插入了新的客戶),但由於這違反了FK約束,整個交易都失敗了。好的,你也可以處理(從ObjectContext中刪除這個已刪除的客戶,然後再次嘗試保存更改),或者甚至讓客戶選擇要做什麼。到此爲止,我們只考慮了數據庫約束。有可能是未在數據庫模型反映(客戶不能被刪除之前,他沒有支付所有的發票,刪除必須由銷售部門上司的批准,客戶不得在6月份前後刪除額外的業務規則他的最後一個訂單,等等......)。因此,可以比簡單的「ObjectContext.DeleteObject」更多地以安全和用戶友好的方式執行刪除操作。

現在讓我們考慮另一個例子:假設有指定聯繫人負責的順序的圖(當然,不尋常的可能,但我們說的這些都是大的,複雜的,極個別的訂單,其中包括了大量的客戶服務和每個訂單需要訂單各個方面的客戶現場的不同聯繫人)。這種觀點可能包含的順序,接觸的人,其已經在客戶主數據池的只讀列表,然後被分配到訂單聯繫人的列表編輯的一小隻讀視圖。現在,像第一個例子中一樣,用戶可以做類似的事情:他可以從列表中刪除聯繫人,他可以從聯繫人列表中拖放聯繫人,將其插入到定單聯繫人列表中。如果我們再次將此列表綁定到EdmObservableCollection<T>將會發生無意義的事情:將新的聯繫人插入到數據庫中,並且聯繫人將從數據庫中刪除。我們不希望這樣,我們實際上只想分配或取消分配對現有記錄(客戶的聯繫人主數據)的引用,但不會刪除或插入記錄。

因此,我們必須在UI類似操作的兩個例子(行從刪除和插入到一個列表),但在數據存儲它們背後完全不同的業務規則也不同的操作。對於WPF也是如此(在兩種情況下都可以使用ObservableCollection來處理),但業務和數據庫層必須完成不同的事情。

我會從中得出一些結論:當你要處理的UI集合

  • EdmObservableCollection<T>可以在特殊情況下非常有用,你不必考慮困難的業務規則或數據庫限制。但在很多情況下它不適用。當然,你可能創建一個爲其超載其他情況下派生的集合,並以另一種方式實現,例如Remove(T item)(例如不從ObjectContext中刪除,但設置的參考爲空或東西代替)。但是這種策略會將存儲庫或服務層的責任越來越多地轉移到專門的ObservableCollections中。如果您的應用程序在DataGrid/List視圖中基本上執行了類似CRUD的操作,那麼EdmObservableCollection可能非常適合。對於其他任何事情,我懷疑。

  • 如上所述,在我看來有更多的參數反對使用ObservableCollections的插入/刪除操作耦合數據庫/存儲庫操作,因此反對使用像EdmObservableCollection這樣的結構。我相信在很多情況下,您的ViewModel需要注入一個存儲庫或服務來滿足業務和數據庫層的特定需求。例如,對於刪除操作,你可以在視圖模型,並在命令處理程序命令這樣做:

    private void DeleteCustomer(Customer customer) 
    { 
        Validator validator = customerService.Delete(customer); 
        // customerService.Delete checks business rules, has access to repository 
        // and checks also FK constraints before trying to delete 
        if (validator.IsValid) 
         observableCustomerCollection.RemoveItem(customer); 
        else 
         messageService.ShowMessage(
          "Dear User, you can't delete this customer because: " 
          + validator.ReasonOfFailedValidation); 
    } 
    

    這樣複雜的東西不屬於成在我看來派生的ObservableCollection。

  • 一般來說,我傾向於保持工作的單位儘可能小 - 不是技術問題,而是可用性的原因。如果用戶做了很多的東西,在一個視圖(編輯東西,刪除的東西,插入等)和大量的工作後,後期點擊「保存」按鈕,也有很多的事情可以去錯了,他可能會一長串的驗證錯誤,並被迫糾正很多事情。當然,在他完全按下「保存」按鈕之前,基本驗證應該在用戶界面上完成,但更復雜的驗證將稍後在業務層進行。例如,如果他刪除了一行,我會立即通過該服務刪除(在確認消息框之後),如上例所示。 Inserts也一樣。由於我沒有使用變更跟蹤代理,所以更新可能會變得更加複雜(特別是涉及實體中的許多導航屬性時)。 (我不知道我是不是應該做的更好。)

  • 我沒有很大的希望讓不同的東西看起來像他們一樣。爲了分離擔憂,有一個CustomerService.Delete和一個OrderContactPersonsService.Delete是有意義的,ViewModel不關心後面發生的事情。但是某處(業務層,存儲庫......)這些操作將會有所不同,並且需要努力完成。具有內部IRepository的EdmObservableCollection是從表示層到數據庫的整個鏈的泛化過程,並試圖隱藏這些差異,這在最簡單的CRUD應用程序中是不切實際的。

  • 有一個ObjectContext/DbContext與EdmObservableCollection中的IRepository在我看來是最少的問題。無論如何,EF上下文或ObjectSets/DbSets幾乎都是UnitOfWork/Repositories,如果您在更改數據庫訪問技術時不需要更改接口合約,則存在疑問。就我個人而言,我在我的通用存儲庫中擁有「Attach」或「LoadNavigationCollection」之類的東西,並且對於我來說這些方法的參數是否對另一個持久層有意義。但是讓知識庫更加抽象(希望能有一個真正的Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel<T>)只會讓它變得更加無用。將EF抽象爲IRepository並不能解決我所描述的問題。

最後一個注意事項作爲免責聲明:懷疑地閱讀我的話。我不是一個有經驗的WPF/EF開發人員,我只是在開發我的第一個稍微大一點的應用程序(大約2個月後),它將這兩種技術結合在一起。但我迄今的經驗是,我已經拋棄了很多過度抽象的代碼縮減嘗試。我很高興 - 出於維護原因和簡單起見 - 如果我能夠與EdmObservableCollection相處並且只有一個通用的存儲庫,但最終存在應用程序和客戶需求,這些需求不幸需要大量不同的工作代碼。

+0

有趣和周到的點;從我+1。我看到一些不同的問題。具體來說,我做了大量的數據和業務規則驗證。我使用視圖模型中的CollectionChanging事件來調用服務方法來進行驗證。如果驗證失敗,服務方法將取消操作並回調到視圖以顯示相應的錯誤消息。 – 2011-03-25 23:22:02

+0

@David Veeneman:我不知道這個事件,有趣!它在WPF中可用嗎?我只是在搜索,只能在Windows窗體命名空間中找到它。 – Slauma 2011-03-25 23:35:56

+0

糟糕 - 這是我在另一個版本的EdmObservableCollection上實現的事件。很容易做 - 聲明事件參數以通過操作(添加,更新,刪除),受影響的對象以及取消標誌(讀/寫)。在調用基類之前,在每個方法中引發事件並影響集合,如果Cancel設置爲true,則退出。我將就此主題編寫一篇CodeProject文章,並將在該代碼中包含該事件。謝謝你的收穫! – 2011-03-26 18:34:18