实体框架 4 和 WPF

发布于 2024-10-25 05:29:23 字数 7075 浏览 1 评论 0原文

我正在编写一个 WPF 应用程序,使用 MVVM 设计和 Entity Framework 4 作为 ORM。我的视图模型中有集合属性,其中包含从 EF4 作为 IEnumerable 集合返回的实体集合,以响应从业务层提交的查询。

我原本希望简单地将 IEnumerable 结果集包装在 ObservableCollection 中。然而,我发现自己在存储库中编写更改跟踪代码,或者维护更改对象的影子集合,只是为了保持视图模型和持久层同步。每次将实体添加到视图模型中的集合时,我都必须转到存储库将其添加到 EF4 ObjectSet。我必须对更新和删除做同样的事情。

为了简化事情,我从 CodePlex (http://waf.codeplex.com/) 上的 WPF 应用程序框架 项目借用了一个 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
     }
}

I am writing a WPF application, using an MVVM design with Entity Framework 4 as the ORM. 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.

I had hoped to simply wrap the IEnumerable<T> result set in an 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. 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/). 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. I have reprinted the EdmObservableCollection class below. 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.

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? Is the EdmObservableCollection a suitable approach, or is there a better way? Am I missing something fundamental in working with 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
     }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

长途伴 2024-11-01 05:29:23

我想我已经找到答案了。问题不在于集合,而在于传递给集合的内容。该集合不应该直接与 ObjectContext 一起使用;相反,它应该与它收集的实体类型的存储库一起使用。因此,应该将 Repository 类传递给集合的构造函数,并且集合中的所有持久性代码都应该通过对 Repository 方法的简单调用来替换。修改后的集合类如下所示:


编辑: Slauma 询问了数据验证问题(请参阅他的回复),因此我在我最初在答案中发布的集合类中添加了一个 CollectionChanging 事件。谢谢斯劳马的帮助!客户端代码应该订阅该事件并使用它来执行验证。将 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
    }
}

事件参数类

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 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; 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. 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. Thanks, Slauma, for the catch! Client code should subscribe to the event and use it to perform validation. Set the EventArgs.Cancel property to true to cancel a change.

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

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
 }
绾颜 2024-11-01 05:29:23

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

我认为基本问题是:用户在 UI 上执行的操作是否始终以与数据库操作相关的独特方式进行?或者更具体地说:如果用户可以从 UI 上的列表中删除项目或将新项目插入列表中,这是否一定意味着必须从数据库中删除或插入一条记录?

我认为答案是:不。

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

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

  • 您可能需要将 ObjectContext/Repository 注入您的 ViewModel 中(以查询要放入集合中的对象) - 并且它必须与注入 EdmObservableCollection 的上下文相同,以正确处理对象更新(编辑客户行)。如果您不想在调用 SaveChanges 之前进行手动“后期”更改跟踪,您还必须使用更改跟踪对象/代理。

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

现在让我们考虑另一个例子:假设有一个视图为订单分配联系人(嗯,可能不常见,但假设这些是大型、复杂、非常个性化的订单,其中包括大量客户服务,并且每个订单都需要不同的联系人客户网站了解订单的各个方面)。该视图可以包含订单的小型只读视图、已经在客户主数据中的联系人池的只读列表以及分配给订单的联系人的可编辑列表。现在,与第一个示例一样,用户可以执行类似的操作:他可以从列表中删除联系人,并且可以从主列表中拖放联系人以将其插入订单联系人列表中。如果我们再次将此列表绑定到 EdmObservableCollection 则会发生无意义的情况:新的联系人将被插入到数据库中,而联系人将从数据库中删除。我们不希望这样,我们实际上只想分配或取消分配对现有记录(客户的联系人主数据)的引用,但从不删除或插入记录。

因此,我们有两个 UI 上类似操作的示例(从列表中删除行和将行插入到列表中),但它们背后的业务规则完全不同,并且数据存储中的操作也不同。对于 WPF 也会发生同样的情况(在这两种情况下都可以使用 ObservableCollection 来处理),但必须在业务层和数据库层中完成不同的操作。

我会从中得出一些结论:

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

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

    private void DeleteCustomer(客户客户)
    {
        验证器 validator = customerService.Delete(customer);
        // customerService.Delete 检查业务规则,有权访问存储库
        // 并在尝试删除之前检查 FK 约束
        if (验证器.IsValid)
            observableCustomerCollection.RemoveItem(客户);
        别的
            消息服务.ShowMessage(
                “尊敬的用户,您无法删除该客户,因为:” 
                + 验证器.ReasonOfFailedValidation);
    }
    

    在我看来,像这样的复杂东西不属于派生的 ObservableCollection。

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

  • 我没有太大希望让不同的东西看起来像它们一样。为了分离关注点,有一个 CustomerService.Delete 和一个 OrderContactPersonsService.Delete 是有意义的,ViewModel 不关心后面发生的事情。但在某些地方(业务层、存储库……)这些操作会有所不同,并且必须完成艰苦的工作。具有内在 IRepository 的 EdmObservableCollection 过于通用,从表示层到数据库的整个链都过于通用,并试图隐藏这些差异,这在除了最简单的 CRUD 应用程序之外的任何其他应用程序中都是不现实的。

  • 在我看来,在 EdmObservableCollection 中拥有 ObjectContext/DbContext 与 IRepository 是最不重要的问题。无论如何,EF 上下文或 ObjectSets/DbSets 几乎是 UnitOfWork/Repositories,当您需要更改数据库访问技术时,如果您不需要更改接口契约,这是有问题的。就我个人而言,我的通用存储库上有“Attach”或“LoadNavigationCollection”之类的东西,我不清楚这些方法及其参数是否对另一个持久层有意义。但是,使存储库变得更加抽象(希望有一个真正的Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel)只会使其更加无用。将 EF 抽象到 IRepository 中并不能解决我所描述的问题。

最后注释作为免责声明:以怀疑的态度阅读我的话。我不是一位经验丰富的 WPF/EF 开发人员,我只是在开发我的第一个更大的应用程序(大约 2 个月以来),它结合了这两种技术。但到目前为止,我的经验是我已经放弃了很多过度抽象的代码缩减尝试。我会很高兴 - 出于维护原因和简单性 - 如果我可以使用 EdmObservableCollection 并且只有通用存储库,但最终有应用程序和客户需求,不幸的是需要很多不同的工作代码。

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

I think, the answer is: No.

At first I can see a good use case to work with the EdmObservableCollection<T>. That is for example a view on the WPF UI with only a DataGrid which is bound to a collection of customers. 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. The DataGrid supports these operations quite easily and the databinding engine writes those "CUD" operations directly to the bound 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.

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. You also must work with change tracking objects/proxies if you don't want to do manual "late" change tracking before you call SaveChanges.

  • This kind a "generic" delete operation the EdmObservableCollection<T> provides doesn't consider database or business constraints. 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. 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. 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. 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...). So, there can be much more involved than a simple "ObjectContext.DeleteObject" to execute deletion in a safe and user friendly way.

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

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. 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). But this strategy would move responsibilities of repositories or a service layer more and more into those specialized ObservableCollections. If your application does basically CRUD-like operations in DataGrid/List views then EdmObservableCollection might be well suited. 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. 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. For instance for delete operations you could have a Command in the ViewModel and in the command handler do something like:

    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.

  • 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. 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. 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.

  • Having an ObjectContext/DbContext versus an IRepository in the EdmObservableCollection is in my opinion the least problem. 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. 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. 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. Abstracting EF away into an IRepository does not solve the concerns I've described.

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

塔塔猫 2024-11-01 05:29:23

如果需要的话,我可能会使用 工厂模式 在 vi​​ewModel 中实现一定程度的抽象。

缺点是,每次创建一个集合时,您都必须限制自己调用工厂。

因此,如果您的工厂有这样的 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();

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

I would probably use a factory pattern to achieve a level of abstraction in your viewModel if you want it.

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):

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
  • whenever you app initializes call ObjectBuilder.SetFactory()
  • and now in your viewmodels wherever you want this you simply call 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)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文