2011-03-23 354 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