Entity Framework 4 и WPF - PullRequest
       15

Entity Framework 4 и WPF

10 голосов
/ 24 марта 2011

Я пишу приложение WPF, используя дизайн MVVM с Entity Framework 4 в качестве ORM.У меня есть свойства коллекций в моей модели представления, которые будут содержать коллекции сущностей, возвращенных из EF4 в виде IEnumerable<T> коллекций в ответ на запросы, отправленные из бизнес-уровня.

Я надеялся просто обернуть набор результатов IEnumerable<T>в ObservableCollection<T>.Однако я обнаружил, что пишу код отслеживания изменений в своем хранилище или поддерживаю теневые коллекции измененных объектов, просто чтобы синхронизировать модель представления и слой постоянства.Каждый раз, когда объект добавляется в коллекцию в модели представления, мне приходилось заходить в свой репозиторий, чтобы добавить его в EF4 ObjectSet.Мне пришлось сделать то же самое с обновлениями и удалениями.

Чтобы упростить вещи, я позаимствовал класс EdmObservableCollection<T> из WPF Application Framework проекта на CodePlex (http://waf.codeplex.com/).класс обёртывает ObservableCollection<T> ссылкой на EF4 ObjectContext, так что OC может обновляться при обновлении коллекции. Я перепечатал класс EdmObservableCollection ниже. Класс работает довольно хорошо, но имеет немногоиз-за запаха кода, потому что в моей модели представления я получаю ссылку на EF4.

Вот мой вопрос: как обычно WPF-приложение поддерживает синхронизацию коллекции сущностей EF4 с ееконтекст объекта? Является ли EdmObservableCollection подходящим подходом или есть лучший способ? Я упускаю что-то фундаментальное в работе с EF4? Спасибо за вашу помощь.

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

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

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

          #endregion

          #region Constructors

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

               m_ObjectContext = objectContext;
               m_EntitySetName = entitySetName;
          }

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

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

          #endregion

          #region Method Overrides

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

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

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

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

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

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

          #endregion

          #region Public Methods

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

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

               base.Clear();
          }

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

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

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

          #endregion
     }
}

Ответы [ 3 ]

5 голосов
/ 25 марта 2011

Я думаю, что я разработал ответ.Проблема не в коллекции, а в том, что передается в коллекцию.Коллекция не должна работать напрямую с ObjectContext;вместо этого он должен работать с репозиторием для типа сущности, которую он собирает.Таким образом, класс Repository должен быть передан конструктору коллекции, и весь постоянный код в коллекции должен быть заменен простыми вызовами методов Repository.Пересмотренный класс коллекции представлен ниже:

РЕДАКТИРОВАТЬ: Слаума спросил о проверке данных (см. Его ответ), поэтому я добавил событие 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
 }
1 голос
/ 26 марта 2011

Я добавлю некоторые мысли, но без окончательного ответа.

Основной вопрос, на мой взгляд: всегда ли пользователь может выполнять операции с пользовательским интерфейсом уникальным способом, связанным с операциями с базой данных?Или более конкретно: если пользователь может удалить элемент из списка в пользовательском интерфейсе или вставить новый элемент в список, означает ли это, что запись должна быть удалена из базы данных или вставлена ​​в базу данных?

Я думаю, ответ: Нет.

Сначала я вижу хороший вариант использования для работы с EdmObservableCollection<T>.Это, например, представление о пользовательском интерфейсе WPF только с DataGrid, которое привязано к группе клиентов.Список клиентов будет выбран спецификацией запроса.Теперь пользователь может редактировать в этой DataGrid: он может изменить строки (отдельные клиенты), он может вставить новую строку и он может удалить строку.DataGrid поддерживает эти операции довольно легко, и механизм привязки данных записывает эти операции «CUD» непосредственно в связанную коллекцию EdmObservableCollection.В этой ситуации удаление строки или вставка новой строки фактически должно непосредственно отражаться в базе данных, поэтому EdmObservableCollection может быть весьма полезным, поскольку он обрабатывает вставки и удаления в ObjectContext внутренне.

Но даже в этомВ простой ситуации есть несколько моментов, которые необходимо учитывать:

  • Возможно, вам все равно нужно вставить ObjectContext / Repository в вашу ViewModel (чтобы запросить объекты, которые вы хотите поместить в коллекцию) - и он должен быть того же контекста, который вставлен в коллекцию EdmObservableCollection, для правильной обработки обновлений объекта (редактирования строки клиента).Вы также должны работать с объектами / прокси отслеживания изменений, если вы не хотите выполнять ручное «позднее» отслеживание изменений перед вызовом SaveChanges.

  • Этот тип «универсальной» операции удаленияEdmObservableCollection<T> обеспечивает не учитывает ограничения базы данных или бизнеса.Что происходит, например, если пользователь пытается удалить строку для клиента, которому назначены различные заказы?Если в базе данных есть отношение внешнего ключа, SaveChanges завершится с ошибкой и выдаст исключение.Ну, вы можете поймать это исключение, оценить его и дать сообщение пользователю.Но, возможно, он сделал много других изменений (отредактировал много других строк и добавил новых клиентов), но из-за этого нарушенного ограничения FK вся транзакция не удалась.Хорошо, также это вы можете обработать (удалить этого удаленного клиента из ObjectContext и попытаться снова сохранить изменения) или даже дать клиенту выбор, что делать.И до сих пор мы рассматривали только ограничения базы данных.Могут быть дополнительные бизнес-правила, которые не отражены в модели базы данных (клиент не может быть удален, пока он не оплатил все счета, удаление должно быть одобрено начальником отдела продаж, клиент не должен быть удален до 6 месяцев послеего последний приказ и тд и тп ...).Таким образом, для выполнения удаления безопасным и удобным для пользователя способом может быть гораздо больше, чем простым ObjectContext.DeleteObject.

Теперь давайте рассмотрим другой пример: представьте себе, что существует представление о назначении контактных лиц для заказа (ну, возможно, это необычно, но, скажем, это большие, сложные, очень индивидуальные заказы, которые включают в себя множество услуг для клиентов, и каждый заказ требует разныхконтактные лица на сайте заказчика по различным аспектам заказа).Это представление может содержать небольшое чтение только для чтения заказа, список только для чтения пула контактных лиц, которые уже находятся в основных данных клиента, а затем редактируемый список контактных лиц, назначенных заказу.Теперь, как и в первом примере, пользователь может делать похожие вещи: он может удалить контактное лицо из списка, и он может перетащить контактное лицо из основного списка, чтобы вставить его в этот список контактных лиц заказа.Если бы мы связали этот список снова с EdmObservableCollection<T> ерундой, то это произойдет: новые контактные лица будут добавлены в базу данных, а контактные лица будут удалены из базы данных.Мы не хотим этого, на самом деле мы хотим только назначать или отменять ссылки на существующие записи (основные данные контактного лица клиента), но никогда не удалять и не вставлять записи.

Итак, у нас есть два примера аналогичных операций надПользовательский интерфейс (строки удаляются из списка и вставляются в список), но за ними стоят совершенно другие бизнес-правила, а также различные операции в хранилище данных.Для WPF происходит то же самое (что может быть обработано с помощью ObservableCollection в обоих случаях), но на уровне бизнеса и базы данных необходимо выполнить разные действия.

Я бы сделал из этого несколько выводов:

  • EdmObservableCollection<T> может быть полезно в особых ситуациях, когда вам приходится иметь дело с коллекциями в пользовательском интерфейсе, и вам не нужно учитывать сложные бизнес-правила или ограничения базы данных.Но это во многих ситуациях не применимо.Конечно, вы могли бы создать производные коллекции для других ситуаций, которые перегружают и реализуют, например, Remove(T item) другим способом (например, не удаляйте из ObjectContext, а вместо этого установите ссылку на ноль или что-то еще).Но эта стратегия будет все больше и больше перемещать обязанности репозиториев или сервисного уровня в эти специализированные ObservableCollections.Если ваше приложение выполняет в основном CRUD-подобные операции в представлениях DataGrid / List, то EdmObservableCollection может оказаться подходящим вариантом.Во всем остальном я сомневаюсь.

  • Как описано, на мой взгляд, есть больше аргументов против объединения операций базы данных / хранилища с операциями вставки / удаления ObservableCollections и, следовательно, против использования конструкции, подобной EdmObservableCollection.Я считаю, что во многих случаях вашим ViewModels понадобится репозиторий или служба, внедренная для удовлетворения конкретных потребностей вашего уровня бизнеса и базы данных.Например, для операций удаления у вас может быть команда в ViewModel и в обработчике команд сделать что-то вроде:

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

    Сложные вещи, подобные этой, на мой взгляд, не относятся к производной ObservableCollection.

  • Как правило, я стараюсь, чтобы единицы работы были как можно меньше - не по техническим причинам, а по соображениям удобства использования.Если пользователь выполняет много операций в представлении (редактирует что-то, удаляет что-то, вставляет и т. Д.) И нажимает кнопку «Сохранить» в конце после большой работы, также многое может пойти не так, он может получитьдлинный список ошибок проверки и быть вынуждены исправить много вещей.Конечно, базовая проверка должна была быть выполнена в пользовательском интерфейсе, прежде чем он вообще сможет нажать кнопку «Сохранить», но более сложная проверка произойдет позже на бизнес-уровне.Например, если он удаляет строку, я удаляю через службу сразу (возможно, после сообщения с подтверждением), как в примере выше.То же самое для вставок.Обновления могут стать более сложными (особенно когда задействованы многие свойства навигации в объекте), поскольку я не работаю с прокси-серверами отслеживания изменений.(Я не уверен, что мне лучше не делать.)

  • У меня нет большой надежды сделать так, чтобы разные вещи выглядели одинаково. Для разделения проблем имеет смысл иметь CustomerService.Delete и OrderContactPersonsService.Delete, которые ViewModels не заботит о том, что происходит позади. Но где-то (бизнес-уровень, хранилище и т. Д.) Эти операции будут другими, и необходимо выполнить тяжелую работу. EdmObservableCollection со встроенным IRepository переоценивает всю цепочку от уровня представления до базы данных и пытается скрыть эти различия, что нереально в любом другом случае, кроме простейших приложений CRUD.

  • Наличие ObjectContext / DbContext по сравнению с IRepository в EdmObservableCollection является, на мой взгляд, наименьшей проблемой. Контекст EF или ObjectSets / DbSets - это почти UnitOfWork / Repositories, и в любом случае сомнительно, что вам не нужно менять контракты интерфейса, когда вам когда-либо придется менять технологию доступа к базе данных. Лично у меня есть такие вещи, как «Attach» или «LoadNavigationCollection» в моем общем хранилище, и мне не ясно, будут ли эти методы с их параметрами вообще иметь смысл с другим слоем персистентности. Но если сделать хранилище еще более абстрактным (в надежде получить реальный Add-Update-Delete-Super-Persistance-Ignorant-Interface-Marvel<T>), это только приведет к его бесполезности. Абстрагирование EF в IRepository не решает проблемы, которые я описал.

Последнее примечание в качестве отказа от ответственности: Читайте мои слова со скептицизмом. Я не опытный разработчик WPF / EF, я просто работаю над своим первым более крупным приложением (примерно через 2 месяца), которое объединяет эти две технологии. Но мой опыт показывает, что я потерял слишком много попыток сокращения кода. Я был бы рад - по причинам обслуживания и ради простоты - если бы я мог ужиться с EdmObservableCollection и только с общим репозиторием, но, наконец, есть требования к приложениям и клиентам, которые, к сожалению, требуют много другого кода.

0 голосов
/ 24 марта 2011

Я бы, вероятно, использовал бы фабричный шаблон для достижения уровня абстракции в вашей viewModel, если вы этого хотите.

Недостатком является то, что вам придется ограничиваться вызовом на фабрику каждый раз, когда вы создаете одну из своих коллекций.

так что если у вашей фабрики есть такой API (который вы можете переключать с любым другим):

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

так что теперь вы бы:

  • просто реализуйте IFactory, чтобы возвращать коллекцию EdmObservableCollection всякий раз, когда TCollection is ObservableCollection
  • всякий раз, когда ваше приложение инициализирует вызов ObjectBuilder.SetFactory()
  • и теперь в ваших viewmodels, куда вы хотите это, вы просто звоните ObjectBuilder.Create<ObservableCollection,MyEntity>();

также, если / когда вам нужно будет изменить свой бэкэнд ORM, вы просто вводите новый IFactory и звоните ObjectBuilder.SetFactory(factory)

...