实体框架4和WPF

10
我正在编写一个WPF应用程序,使用MVVM设计和Entity Framework 4作为ORM。我在视图模型中有集合属性,这些属性将包含从业务层提交的查询响应中作为IEnumerable 集合返回的实体集合。
我希望简单地将IEnumerable 结果集包装在ObservableCollection 中。然而,我发现自己在存储库中编写了更改跟踪代码,或者维护了更改对象的影子集合,以保持视图模型和持久性层同步。每当实体添加到视图模型中的集合中时,我都必须去我的存储库将其添加到EF4 ObjectSet中。我必须在更新和删除方面做同样的事情。
为了简化事情,我从CodePlex(http://waf.codeplex.com/)的WPF Application Framework项目中借用了一个EdmObservableCollection 类。该类用EF4 ObjectContext的引用包装了一个ObservableCollection ,因此可以在更新集合时更新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
     }
}
3个回答

5
我想我已经找到了答案。问题不在于集合本身,而在于传递给集合的内容。集合不应该直接与ObjectContext一起工作;相反,它应该与收集的实体类型的Repository一起工作。因此,应该将一个Repository类传递给集合的构造函数,并且集合中所有的持久化代码都应该被简单的调用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
    }
}

The Event Args Class

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
 }

1

我会提出一些想法,但没有最终答案。

基本问题在我看来是:用户在UI上可以执行的操作是否总是与数据库操作以唯一的方式相关联?或更具体地说:如果用户可以从UI中删除列表中的项目或将新项目插入列表中,这是否必然意味着必须从数据库中删除记录或插入记录?

我认为答案是:不是。

首先,我可以看到使用EdmObservableCollection<T>的一个很好的用例。例如,WPF UI上只有一个绑定到客户集合的DataGrid视图。通过查询规范获取客户列表。现在用户可以在此DataGrid中进行编辑:他可以更改行(单个客户),可以插入新行,也可以删除行。 DataGrid相当容易支持这些操作,并且数据绑定引擎直接将这些“CUD”操作写入绑定的EdmObservableCollection。在这种情况下,删除行或插入新行实际上应该直接反映在数据库中,因此EdmObservableCollection可能非常有用,因为它在ObjectContext内部处理插入和删除。

但即使在这种简单的情况下,也有一些要考虑的要点:

  • 你可能需要将ObjectContext/Repository注入到ViewModel中(以查询要放入集合中的对象)- 并且必须是相同的上下文,以处理对象更新(编辑客户行)。

  • EdmObservableCollection<T> 提供的这种“通用”删除操作并不考虑数据库或业务约束。例如,如果用户尝试删除已分配给各个订单的客户行,则会发生什么情况?如果在数据库中存在外键关系,则SaveChanges将失败并抛出异常。好的,你可以捕获此异常,评估它并向用户发出消息。但也许他已经做了很多其他更改(编辑了许多其他行并插入了新客户),但由于此违反的FK约束整个事务失败了。好吧,你也可以处理它(从ObjectContext中删除此已删除的客户并尝试重新保存更改),甚至给客户一个选择。到目前为止,我们只考虑了数据库约束。还可以存在未在数据库模型中反映的其他业务规则(客户必须在支付所有发票之前删除,删除必须得到销售部门负责人的批准,在客户最后一笔订单后6个月之前不得删除等等...)。因此,执行安全且用户友好的删除可能涉及更多内容,而不仅仅是简单的“ObjectContext.DeleteObject”操作。

现在让我们考虑另一个例子:假设有一个视图用于将联系人分配给订单(可能不太常见,但是假设这些是大型、复杂、非常个性化的订单,包括很多客户服务,并且每个订单需要不同的联系人在客户现场处理订单的各个方面)。此视图可能包含订单的小只读视图,客户主数据中已经存在的联系人的只读列表,以及分配给订单的联系人的可编辑列表。现在,就像第一个例子一样,用户可以执行类似的操作:他可以从列表中删除联系人,也可以将主列表中的联系人拖放到订单联系人列表中进行插入。如果我们再次将此列表绑定到EdmObservableCollection<T>,那么将会发生无意义的事情:新的联系人将被插入到数据库中,联系人也将从数据库中删除。我们不希望发生这种情况,实际上我们只想分配或取消分配对现有记录(客户联系人主数据)的引用,而不是删除或插入记录。

所以我们有两个类似的UI操作示例(从列表中删除和插入行),但背后的业务规则和数据存储中的操作却有很大不同。对于WPF,情况也是如此(这可以在ObservableCollection的两种情况下处理),但在业务和数据库层面必须做不同的事情。

我会从中得出一些结论:

  • EdmObservableCollection<T> 在特殊情况下处理 UI 中的集合时非常有用,而且不需要考虑复杂的业务规则或数据库约束。但在许多情况下,它并不适用。当然,您可能会为其他情况创建派生集合,例如以另一种方式重载和实现 Remove(T item)(例如不从 ObjectContext 中删除,而是将引用设置为 null 或其他内容)。但这种策略会将存储库或服务层的责任越来越多地转移到那些专门的 ObservableCollections 中。如果您的应用程序基本上在 DataGrid/List 视图中执行 CRUD 操作,则 EdmObservableCollection 可能非常适合。对于其他任何事情,我都怀疑。

  • 如上所述,我认为与 ObservableCollections 的插入/删除操作耦合数据库/存储库操作的论点更多,因此反对使用像 EdmObservableCollection 这样的结构。我相信,在许多情况下,您的 ViewModel 将需要注入存储库或服务以满足业务和数据库层的特定需求。例如,对于删除操作,您可以在 ViewModel 中拥有一个 Command,并在命令处理程序中执行以下操作:

    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。

  • 通常,我倾向于尽可能保持工作单元的小型化 - 不是出于技术原因,而是出于可用性原因。如果用户在视图中执行了很多操作(编辑某些内容、删除某些内容、插入等等),并且在很长时间后才单击“保存”按钮,也会发生很多事情,他可能会收到一长串验证错误,并被迫纠正很多事情。当然,在他能够按下“保存”按钮之前,基本验证应该已经在 UI 中完成,但更复杂的验证将在业务层稍后进行。例如,如果他删除一行,我会立即通过服务进行删除(可能是在确认消息框之后),就像上面的示例一样。对于插入操作也是如此。更新可能变得更加复杂(特别是当实体中涉及许多导航属性时),因为我不使用更改跟踪代理。(我不确定我是否应该更好地这样做。)

  • 我没有很大的希望使不同的事物看起来像是相同的。为了分离关注点,有一个 CustomerService.Delete 和一个 OrderContactPersonsService.Delete 是有意义的,ViewModel 不关心发生了什么。但是,在某个地方(业务层、存储库等),这些操作将是不同的,并且必须完成艰苦的工作。EdmObservableCollection 与内在的 IRepository 过于通用,从表示层到数据库的整个链路都试图隐藏这些差异,这在除了最简单的 CRUD 应用程序之外是不现实的。

  • 在 EdmObservableCollection 中拥有 ObjectContext/DbContext 与 IRepository 相比,我认为后者是最小的问题。EF 上下文

    最后作为免责声明:请怀着怀疑的态度阅读我的话。我不是一名有经验的WPF/EF开发人员,我只是在处理我的第一个相对较大的应用程序(大约2个月)时结合了这两种技术。但是到目前为止,我的经验是,我已经放弃了许多过度抽象化的代码简化尝试。出于维护原因和简单性的考虑,如果我能够使用EdmObservableCollection和仅通用存储库就可以完成,那么我会很高兴,但最终存在应用程序和客户需求,需要大量不同工作的代码。


有趣且深思熟虑的观点;我给你点赞。我对其中一些问题持不同看法。具体来说,我经常进行数据和业务规则验证。我在视图模型中使用CollectionChanging事件来调用服务方法进行验证。如果验证失败,服务方法会取消操作并回调到视图以显示适当的错误消息。 - David Veeneman
@David Veeneman:我之前不知道这个事件,很有趣!它在WPF中可用吗?我刚刚搜索了一下,只能找到它在Windows Forms命名空间中的相关内容。 - Slauma
哎呀——这是我在另一个版本的EdmObservableCollection上实现的事件。很容易做到——声明事件参数以传递操作(添加、更新、删除)、受影响的对象和取消标志(读/写)。在每个方法中引发事件并影响集合,在调用基类之前退出,如果取消设置为true。我将在CodeProject文章中介绍这个主题,并在其中包含该事件。感谢你的发现! - David Veeneman
在我的回复中,ccollection类添加了CollectionChanging事件。再次感谢。 - David Veeneman
@David Veeneman:CollectionChanging事件确实是一个很大的进步!但我仍然不清楚如何处理我在两个示例中描述的不同情况(我的意思是:从DataGrid中删除并不一定意味着从数据库中删除)。尽管如此,你对ObservableCollection的扩展在许多情况下都非常有用。我已经开始宣传你的解决方案:https://dev59.com/Y1XTa4cB1Zd3GeqP3aCR ;) - Slauma

0

如果你想要在 viewModel 中达到一定程度的抽象,我可能会使用 工厂模式

缺点是,你将不得不每次创建一个集合时都调用工厂。

因此,如果你的工厂有像这样的 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,以便在TCollection为ObservableCollection时返回您的EdmObservableCollection
  • 每当您的应用程序初始化时调用ObjectBuilder.SetFactory()
  • 现在,在您想要这个的视图模型中,只需调用ObjectBuilder.Create<ObservableCollection,MyEntity>();

此外,如果/每当您需要更改ORM后端时,只需实现一个新的IFactory并调用ObjectBuilder.SetFactory(factory)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接