简体   繁体   English

实体框架4和WPF

[英]Entity Framework 4 and WPF

I am writing a WPF application, using an MVVM design with Entity Framework 4 as the ORM. 我正在编写WPF应用程序,使用带有Entity Framework 4作为ORM的MVVM设计。 I have collection properties in my view model that will contain collections of entities returned from EF4 as IEnumerable<T> collections in response to queries submitted from the business layer. 我的视图模型中具有集合属性,该属性将包含从EF4作为IEnumerable<T>集合返回的实体的集合,以响应从业务层提交的查询。

I had hoped to simply wrap the IEnumerable<T> result set in an ObservableCollection<T> . 我曾希望将IEnumerable<T>结果集简单地包装在ObservableCollection<T> However, I found myself writing change-tracking code in my repository, or maintaining shadow collections of changed objects, just to keep the view model and persistence layer in sync. 但是,我发现自己在存储库中编写了更改跟踪代码,或者维护了更改对象的影子集合,只是为了使视图模型和持久层保持同步。 Every time an entity is added to the collection in the view model, I had to go to my repository to add it to the EF4 ObjectSet. 每次将实体添加到视图模型中的集合时,我都必须转到存储库以将其添加到EF4 ObjectSet中。 I had to do the same sort of thing with updates and deletions. 我必须对更新和删除执行相同的操作。

To simplify things, I borrowed an EdmObservableCollection<T> class from the WPF Application Framework project on CodePlex (http://waf.codeplex.com/). 为简化起见,我从CodePlex(http://waf.codeplex.com/)上的WPF应用程序框架项目中借用了EdmObservableCollection<T>类。 The class wraps an ObservableCollection<T> with a reference to an EF4 ObjectContext , so that the OC can be updated as the collection is updated. 该类使用对EF4 ObjectContext的引用包装ObservableCollection<T> ,以便可以在更新集合时更新OC。 I have reprinted the EdmObservableCollection class below. 我已经在下面重新打印了EdmObservableCollection类。 The class works pretty well, but it has a bit of a code smell about it, because I end up with a reference to EF4 in my view model. 该类工作得很好,但是它有点代码味道,因为我最终在视图模型中引用了EF4。

Here's my question: In a WPF application, what's the usual way of keeping an EF4 entity collection in sync with its object context? 这是我的问题:在WPF应用程序中,使EF4实体集合与其对象上下文保持同步的通常方法是什么? Is the EdmObservableCollection a suitable approach, or is there a better way? EdmObservableCollection是合适的方法,还是有更好的方法? Am I missing something fundamental in working with EF4? 我是否缺少使用EF4的基本知识? Thanks for your help. 谢谢你的帮助。


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
     }
}

I think I have worked out the answer. 我想我已经解决了。 The problem isn't with the collection, it's with what is being passed to the collection. 问题不在于集合,而在于传递给集合的内容。 The collection shouldn't be working directly with the ObjectContext; 集合不应该直接与ObjectContext一起使用; instead, it should work with the Repository for the type of entity that it collects. 相反,它应针对其收集的实体类型与存储库一起使用。 So, a Repository class should be passed to the collection's constructor, and all the persistence code in the collection should be replaced by simple calls to Repository methods. 因此,应该将Repository类传递给集合的构造函数,并且应该通过对Repository方法的简单调用来替换集合中的所有持久性代码。 The revised collection class appears below: 修改后的收藏类如下所示:


EDIT: Slauma asked about data validation (see his response), so I have added a CollectionChanging event to the collection class I originally posted in my answer. 编辑: Slauma询问了有关数据验证的问题(请参阅他的回答),所以我向我最初在答案中发布的集合类添加了CollectionChanging事件。 Thanks, Slauma, for the catch! 谢谢,Slauma,抓住了! Client code should subscribe to the event and use it to perform validation. 客户端代码应订阅事件并使用它执行验证。 Set the EventArgs.Cancel property to true to cancel a change. 将EventArgs.Cancel属性设置为true以取消更改。

The Collection Class 收集类

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 事件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
 }

I will throw in some thoughts but without having a final answer. 我会提出一些想法,但没有最终的答案。

The basic question is in my opinion: Are operations a user can do on a UI always in a unique way related to database operations? 我认为基本问题是:用户是否可以始终以与数据库操作相关的独特方式对UI进行操作? Or more specific: If a user can remove an item from a list on the UI or insert a new item into a list, does that necessarily mean that a record has to be deleted from or inserted into the database? 或更具体的说:如果用户可以从UI上的列表中删除项目或将新项目插入列表,这是否意味着必须从数据库中删除记录或将记录插入数据库?

I think, the answer is: No. 我认为答案是:否。

At first I can see a good use case to work with the EdmObservableCollection<T> . 首先,我可以看到一个与EdmObservableCollection<T>一起使用的好用例。 That is for example a view on the WPF UI with only a DataGrid which is bound to a collection of customers. 例如,这是WPF UI上仅具有绑定到客户集合的DataGrid的视图。 A list of customers will be fetched by a query specification. 客户列表将通过查询规范获取。 Now the user can edit in this DataGrid: He can change rows (single customers), he can insert a new row and he can delete a row. 现在,用户可以在此DataGrid中进行编辑:他可以更改行(单个客户),可以插入新行,也可以删除行。 The DataGrid supports these operations quite easily and the databinding engine writes those "CUD" operations directly to the bound EdmObservableCollection. DataGrid非常轻松地支持这些操作,并且数据绑定引擎将这些“ CUD”操作直接写入绑定的EdmObservableCollection。 In this situation deleting a row or inserting a new row is actually supposed to be directly reflected on the database, so the EdmObservableCollection might be quite useful as it handles Inserts and Deletes in the ObjectContext internally. 在这种情况下,删除行或插入新行实际上应该直接反映在数据库上,因此EdmObservableCollection可能非常有用,因为它在内部处理ObjectContext中的插入和删除。

But even in this simple situation there are a few points to take into account: 但是即使在这种简单情况下,也要考虑以下几点:

  • You probably need to inject the ObjectContext/Repository into your ViewModel anyway (to query for the objects you want to put into the collection) - and it must be the same context as injected into the EdmObservableCollection to handle object updates (editing a customer row) properly. 无论如何,您可能仍需要将ObjectContext / Repository注入到ViewModel中(以查询要放入集合中的对象),并且它必须与注入到EdmObservableCollection中的上下文相同,以处理对象更新(编辑客户行)正确地。 You also must work with change tracking objects/proxies if you don't want to do manual "late" change tracking before you call SaveChanges. 如果您不想在调用SaveChanges之前进行手动的“后期”变更跟踪,则还必须使用变更跟踪对象/代理。

  • This kind a "generic" delete operation the EdmObservableCollection<T> provides doesn't consider database or business constraints. EdmObservableCollection<T>提供的这种“通用”删除操作不考虑数据库或业务约束。 What happens for instance if a user tries to delete a row for a customer who is assigned to various orders? 例如,如果用户尝试删除分配给各种订单的客户的行,该怎么办? If there is a foreign key relationship in the database SaveChanges will fail and throw an exception. 如果数据库中存在外键关系,SaveChanges将失败并引发异常。 Well, you might catch this exception, evaluate it and give a message to the user. 好吧,您可能会捕获此异常,对其进行评估并向用户发送消息。 But perhaps he has done a lot of other changes (edited many other rows and inserted new customers) but due to this violated FK constraint the whole transaction failed. 但是也许他做了许多其他更改(编辑了许多其他行并插入了新客户),但是由于违反了FK约束,整个交易失败了。 OK, also this you could handle (remove this deleted customer from the ObjectContext and try again to save the changes) or even give the customer a choice what to do. 是的,您还可以处理(从ObjectContext中删除此删除的客户,然后再次尝试保存更改),甚至给客户一个选择的方式。 And up to here we have only considered database constraints. 到目前为止,我们仅考虑了数据库约束。 There can be additional business rules which are not reflected in the database model (a customer can't be deleted before he hasn't paid all invoices, deletion must be approved by the sales department boss, customer must not be deleted before 6 month after his last order, and so on and so on...). 可能存在数据库模型中未反映的其他业务规则(客户在未支付所有发票之前无法删除,删除必须经过销售部门老板的批准,客户不得在之后的6个月内删除他的最后命令,依此类推...)。 So, there can be much more involved than a simple "ObjectContext.DeleteObject" to execute deletion in a safe and user friendly way. 因此,除了简单的“ ObjectContext.DeleteObject”以外,还涉及更多以安全和用户友好的方式执行删除的操作。

Now let's consider another example: Imagine there is a view to assign contact persons to an order (well, unusual probably but let's say these are large, complex, very individual orders which include a lot of customer services and every order needs different contact persons at the customer site for various aspects of the order). 现在让我们考虑另一个示例:假设有一个视图可以将联系人分配给订单(嗯,可能很不寻常,但可以说它们是大型,复杂,非常单独的订单,其中包含很多客户服务,每个订单都需要不同的联系人订单各个方面的客户网站)。 This view may contain a small readonly view of the order, a readonly list of a pool of contact persons which are already in the customer's master data and then an editable list of contact persons which are assigned to the order. 此视图可能包含订单的小型只读视图,已经在客户的主数据中的联系人池的只读列表,然后是分配给订单的联系人的可编辑列表。 Now, like in the first example, the user can do similar things: He can delete a contact person from the list and he can maybe drag and drop a contact person from the master list to insert it into that list of order contact persons. 现在,就像第一个示例一样,用户可以执行类似的操作:他可以从列表中删除联系人,并且可以从主列表中拖放联系人以将其插入到该订单联系人列表中。 If we had bound this list again to a EdmObservableCollection<T> nonsense would happen: New contact persons would be inserted into the database and contact persons would be deleted from the database. 如果我们再次将此列表绑定到EdmObservableCollection<T>则会发生废话:将新的联系人插入数据库,并将联系人从数据库中删除。 We don't want that, we actually only want to assign or unassign references to existing records (the customer's contact person master data) but never delete or insert records. 我们不希望那样,实际上我们只想分配或取消对现有记录(客户的联系人主数据)的引用,而从不删除或插入记录。

So we have two examples of similar operations on the UI (rows are deleted from and inserted into a list) but with quite different business rules behind them and also different operations in the data store. 因此,我们有两个在UI上进行类似操作的示例(行已从列表中删除并插入到列表中),但它们背后的业务规则完全不同,数据存储区中的操作也不同。 For WPF the same happens (which can be handled with an ObservableCollection in both cases), but different things must be done in the business and database layer. 对于WPF,会发生相同的情况(在两种情况下都可以使用ObservableCollection进行处理),但是必须在业务和数据库层中执行不同的操作。

I would draw a few conclusions from this: 我将从中得出一些结论:

  • EdmObservableCollection<T> can be useful in special situations when you have to deal with collections on the UI and you don't have to consider difficult business rules or database constraints. 当必须在UI上处理集合并且不必考虑困难的业务规则或数据库约束时, EdmObservableCollection<T>在特殊情况下会很有用。 But it many situations it isn't applicable. 但是它在许多情况下都不适用。 Of course you could possibly create derived collections for other situations which overload and implement for instance Remove(T item) in another way (for example don't delete from the ObjectContext but set a reference to null or something instead). 当然,您可以为其他情况创建派生的集合,这些情况会过载并以另一种方式实现例如Remove(T item) (例如,不要从ObjectContext中删除,而是将引用设置为null或其他内容)。 But this strategy would move responsibilities of repositories or a service layer more and more into those specialized ObservableCollections. 但是这种策略会将存储库或服务层的职责越来越多地转移到那些专门的ObservableCollections中。 If your application does basically CRUD-like operations in DataGrid/List views then EdmObservableCollection might be well suited. 如果您的应用程序在DataGrid / List视图中基本上执行类似于CRUD的操作,则EdmObservableCollection可能非常适合。 For anything else, I doubt. 我还有其他疑问。

  • As described there are in my opinion more arguments against coupling database/repository operations with Insert/Remove operations of ObservableCollections and therefore against using a construct like EdmObservableCollection. 如前所述,在我看来,有更多的论点反对将数据库/存储库操作与ObservableCollections的Insert / Remove操作耦合,因此反对使用类似EdmObservableCollection的构造。 I believe that in many cases your ViewModels will need a repository or service injected to fulfill the specific needs in your business and database layer. 我相信,在许多情况下,您的ViewModels将需要注入一个存储库或服务来满足您的业务和数据库层中的特定需求。 For instance for delete operations you could have a Command in the ViewModel and in the command handler do something like: 例如,对于删除操作,您可以在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); } 

    Complex stuff like this doesn't belong into a derived ObservableCollection in my opinion. 在我看来,像这样的复杂东西不属于派生的ObservableCollection。

  • Generally I tend to keep units of work as small as possible - not for technical but for usability reasons. 通常,我倾向于使工作单元尽可能的小-不是出于技术而是出于可用性的原因。 If a user does a lot of stuff in a view (edit something, delete something, insert and so on) and clicks on a "Save" button late after a lot of work, also a lot of things can go wrong, he might get a long list of validation errors and be forced to correct a lot of things. 如果用户在视图中做了很多事情(编辑,删除,插入等),并且在很多工作之后又单击“保存”按钮,那么很多事情也会出错,他可能会一长串的验证错误,并被迫纠正许多问题。 Of course basic validation should have been done in the UI before he can press the "Save" button at all, but the more complex validation will happen later in the business layer. 当然,在他可以完全按下“保存”按钮之前,应该已经在UI中完成了基本验证,但是更复杂的验证将在稍后的业务层中进行。 For example if he deletes a row, I delete through the service at once (after confirmation message box perhaps) like in the example above. 例如,如果他删除了一行,则像上面的示例一样,我立即通过服务删除(也许在确认消息框之后)。 The same for Inserts. 插入内容也一样。 Updates can become more complicated (especially when many navigation properties in an entity are involved) since I don't work with change tracking proxies. 由于我不使用变更跟踪代理,因此更新可能会变得更加复杂(尤其是当涉及实体中的许多导航属性时)。 (I am not sure if I shouldn't better do.) (我不确定是否应该做得更好。)

  • I have no big hope to make different things look like they were the same. 我不希望使不同的事物看起来一样。 To separate concerns it makes sense to have a CustomerService.Delete and a OrderContactPersonsService.Delete which the ViewModels don't care about what happens behind. 为了分开关注,有一个CustomerService.DeleteOrderContactPersonsService.DeleteOrderContactPersonsService.Delete ,而ViewModels并不关心后面会发生什么。 But somewhere (business layer, repository, ...) those operations will be different and the hard work has to be done. 但是在某些地方(业务层,存储库等),这些操作将有所不同,并且必须完成艰苦的工作。 EdmObservableCollection with an intrinsic IRepository is over-generic the whole chain from the presentation layer down to the database and tries to hide these differences which is unrealistic in any other than the simplest CRUD applications. 具有内部IRepository的EdmObservableCollection对于从表示层到数据库的整个链都是通用的,并试图隐藏这些差异,这在除最简单的CRUD应用程序之外的任何其他应用程序中都是不现实的。

  • Having an ObjectContext/DbContext versus an IRepository in the EdmObservableCollection is in my opinion the least problem. 在我看来,在EdmObservableCollection中使用ObjectContext / DbContext与IRepository相比是最小的问题。 The EF context or ObjectSets/DbSets are almost UnitOfWork/Repositories anyway and it is questionable if you don't need to change the interface contracts when you ever should change the database access technology. EF上下文或ObjectSets / DbSets几乎都是UnitOfWork / Repositories,当您应该更改数据库访问技术时是否不需要更改接口协定,这是个问题。 Personally I have things like "Attach" or "LoadNavigationCollection" on my generic repository and it's not clear for me if these methods with their parameters would make sense at all with another persistance layer. 就我个人而言,我的通用存储库中有诸如“ Attach”或“ LoadNavigationCollection”之类的东西,对于我来说尚不清楚这些方法及其参数是否对另一个持久层有意义。 But making the repository even more abstract (in a hope to have a real Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel<T> ) would only move it more towards uselessness. 但是,使存储库变得更加抽象(希望拥有一个真正的Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel<T> )只会使它更趋于无用。 Abstracting EF away into an IRepository does not solve the concerns I've described. 将EF抽象到IRepository中并不能解决我所描述的问题。

Last note as a disclaimer: Read my words with scepticism. 最后声明:免责声明:请谨慎阅读我的话。 I am not an experienced WPF/EF developer, I am just working on my first somewhat bigger application (since around 2 months) which combines these two technologies. 我不是一位经验丰富的WPF / EF开发人员,我只是在研究将这两种技术结合在一起的第一个更大的应用程序(大约两个月以来)。 But my experience so far is that I have trashed a lot of over-abstracting code reduction attempts. 但是到目前为止,我的经验是,我浪费了很多过于抽象的代码减少尝试。 I'd be happy - for maintenance reasons and for the sake of simplicity - if I could get along with an EdmObservableCollection and only a generic repository but finally there are application and customer demands which unfortunately require a lot of differently working code. 如果出于维护原因和简单起见,我会很高兴,如果我能与EdmObservableCollection和只有一个通用存储库相处,但最终会有应用程序和客户需求,不幸的是,它们需要许多不同的工作代码。

I would probably use a factory pattern to achieve a level of abstraction in your viewModel if you want it. 如果需要,我可能会使用工厂模式在viewModel中实现抽象级别。

The downside is that you will have to limit yourself to calling the factory every time you create one of your collections. 缺点是每次创建一个收藏集时,您都必须限制自己致电工厂。

so if your factory had an API like this(which you could switch with whatever you wanted): 因此,如果您的工厂有这样的API(您可以使用所需的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);
  }
}

so now you would: 所以现在您将:

  • just implement IFactory to return your EdmObservableCollection whenever TCollection is ObservableCollection 只需实现IFactory即可在TCollection is ObservableCollection时返回您的EdmObservableCollection
  • whenever you app initializes call ObjectBuilder.SetFactory() 每当您的应用初始化时,调用ObjectBuilder.SetFactory()
  • and now in your viewmodels wherever you want this you simply call ObjectBuilder.Create<ObservableCollection,MyEntity>(); 现在在您想要的视图模型中,只需调用ObjectBuilder.Create<ObservableCollection,MyEntity>();

also if/whenever you would need to change your ORM backend you simply implement a new IFactory and call ObjectBuilder.SetFactory(factory) 如果/当您需要更改ORM后端时,也只需实现一个新的IFactory并调用ObjectBuilder.SetFactory(factory)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM