Плюсы и минусы различных подходов к привязке с использованием сервисов MVVM и RIA

StackOverflow https://stackoverflow.com/questions/3400150

Вопрос

Я создаю приложение, которое использует объекты LoadOperation для возврата IEnumerable, который становится источником CollectionViewSource в моей модели представления.Теперь я обнаруживаю потенциальную ловушку этого подхода: при добавлении сущностей в мой клиент Silverlight я не могу видеть эти сущности, пока я не отправлю новую сущность, а затем не перезагрузлю ее или не поддержу отдельную коллекцию элементов, к которым я привязываюсь.

Что я действительно вижу в качестве своих вариантов:

  1. Добавьте ObservableCollection для использования в качестве источника свойства CollectionViewSource в моей ViewModel — таким образом я могу одновременно добавлять и в DomainContext, и в ObservableCollection, чтобы синхронизировать коллекции.
  2. Измените привязку непосредственно к EntitySet и добавьте обработчик событий фильтрации, чтобы обеспечить фильтрацию в CollectionViewSource.

Если у кого-то есть советы или мысли о плюсах и минусах каждого из них, я буду очень признателен.В частности, мне интересно, есть ли преимущества в производительности и/или программировании в пользу того или другого?

Это было полезно?

Решение

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

Основой моей демонстрации является единая аутентифицированная служба домена, которая возвращает единый объект Resource.Я представлю 4 команды (сохранить, отменить, добавить и удалить), а также коллекцию и свойство для хранения SelectedResource.

Этот интерфейс реализуется 2 разными классами (1 для смешивания, 1 для производства).Производство - единственное, о чем я буду говорить здесь.Обратите внимание на действие (lo.Entities) в функции GetMyResources:

public class WorkProvider
{
    static WorkContext workContext;
    public WorkProvider()
    {
        if (workContext == null)
            workContext = new WorkContext();
    }
    public void AddResource(Resource resource)
    {
        workContext.Resources.Add(resource);
    }
    public void DelResource(Resource resource)
    {
        workContext.Resources.Remove(resource);
    }
    public void UndoChanges()
    {
        workContext.RejectChanges();
    }
    public void SaveChanges(Action action)
    {
        workContext.SubmitChanges(so =>
            {
                if (so.HasError)
                    // Handle Error
                    throw so.Error;
                else
                    action();
            }, null);
    }
    public void GetMyResources(Action<IEnumerable<Resource>> action)
    {
        var query = workContext.GetResourcesQuery()
            .Where(r => r.UserName == WebContext.Current.User.Name);
        workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
            {
                if (lo.HasError)
                    // Handle Error
                    throw lo.Error;
                else
                    action(lo.Entities);
            }, null);
    }
}

В ViewModel у меня есть следующая реализация:

public class HomeViewModel : ViewModelBase
{
    WorkProvider workProvider;
    public HomeViewModel()
    {
        workProvider = new WorkProvider();
    }

    // _Source is required when returning IEnumerable<T>
    ObservableCollection<Resource> _Source; 
    public CollectionViewSource Resources { get; private set; }
    void setupCollections()
    {
        Resources = new CollectionViewSource();
        using (Resources.DeferRefresh())
        {
            _Source = new ObservableCollection<Resource>();
            Resources.Source = _Source;
            Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
            Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
            Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
        }
    }
    void loadMyResources()
    {
        workProvider.GetMyResources(results =>
            {
                using (Resources.DeferRefresh())
                {
                    // This is required when returning IEnumerable<T>
                    _Source.Clear();
                    foreach (var result in results)
                    {
                        if (!_Source.Contains(result))
                            _Source.Add(result);
                    }
                }
            });
    }
    Resource _SelectedResource;
    public Resource SelectedResource
    {
        get { return _SelectedResource; }
        set
        {
            if (_SelectedResource != value)
            {
                _SelectedResource = value;
                RaisePropertyChanged("SelectedResource");
            }
        }
    }

    public RelayCommand CmdSave { get; private set; }
    public RelayCommand CmdUndo { get; private set; }
    public RelayCommand CmdAdd { get; private set; }
    public RelayCommand CmdDelete { get; private set; }
    void setupCommands()
    {
        CmdSave = new RelayCommand(() =>
            {
                workProvider.SaveChanges(() =>
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(() =>
                            {
                                System.Windows.MessageBox.Show("Saved");
                            });
                    });
            });
        CmdUndo = new RelayCommand(() =>
            {
                workProvider.UndoChanges();
                // This is required when returning IEnumerable<T>
                loadMyResources();
            });
        CmdAdd = new RelayCommand(() =>
            {
                Resource newResource = new Resource()
                {
                    ResourceID = Guid.NewGuid(),
                    Rate = 125,
                    Title = "Staff",
                    UserName = "jsmith"
                };
                // This is required when returning IEnumerable<T>
                _Source.Add(newResource);
                workProvider.AddResource(newResource);
            });
        CmdDelete = new RelayCommand(() =>
        {
            // This is required when returning IEnumerable<T>
            _Source.Remove(_SelectedResource);
            workProvider.DelResource(_SelectedResource);
        });
    }
}

Альтернативный метод предполагает изменение класса WorkProvider следующим образом (обратите внимание на возвращаемое действие(workContext.Resources):

    public void GetMyResources(Action<IEnumerable<Resource>> action)
    {
        var query = workContext.GetResourcesQuery()
            .Where(r => r.UserName == WebContext.Current.User.Name);
        workContext.Load(query, LoadBehavior.MergeIntoCurrent, lo =>
            {
                if (lo.HasError)
                    // Handle Error
                    throw lo.Error;
                else
                    // Notice Changed Enumeration
                    action(workContext.Resources);
            }, null);
    }

Изменения в модели представления следующие (обратите внимание на удаление _Source ObservableCollection):

public class HomeViewModel : ViewModelBase
{
    WorkProvider workProvider;
    public HomeViewModel()
    {
        workProvider = new WorkProvider();
    }

    public CollectionViewSource Resources { get; private set; }
    void setupCollections()
    {
        Resources = new CollectionViewSource();
        using (Resources.DeferRefresh())
        {
            Resources.Filter += (s,a) =>
                {
                    a.Accepted = false;
                    if (s is Resource)
                    {
                        Resource res = s as Resource;
                        if (res.UserName == WebContext.Current.User.Name)
                            a.Accepted = true;
                    }
                };
            Resources.GroupDescriptions.Add(new PropertyGroupDescription("Title"));
            Resources.SortDescriptions.Add(new SortDescription("Title", ListSortDirection.Ascending));
            Resources.SortDescriptions.Add(new SortDescription("Rate", ListSortDirection.Ascending));
        }
    }
    void loadMyResources()
    {
        workProvider.GetMyResources(results =>
            {
                using (Resources.DeferRefresh())
                {
                    Resources.Source = results;
                }
            });
    }
    Resource _SelectedResource;
    public Resource SelectedResource
    {
        get { return _SelectedResource; }
        set
        {
            if (_SelectedResource != value)
            {
                _SelectedResource = value;
                RaisePropertyChanged("SelectedResource");
            }
        }
    }

    public RelayCommand CmdSave { get; private set; }
    public RelayCommand CmdUndo { get; private set; }
    public RelayCommand CmdAdd { get; private set; }
    public RelayCommand CmdDelete { get; private set; }
    void setupCommands()
    {
        CmdSave = new RelayCommand(() =>
            {
                workProvider.SaveChanges(() =>
                    {
                        DispatcherHelper.CheckBeginInvokeOnUI(() =>
                            {
                                System.Windows.MessageBox.Show("Saved");
                            });
                    });
            });
        CmdUndo = new RelayCommand(() =>
            {
                workProvider.UndoChanges();
                Resources.View.Refresh();
            });
        CmdAdd = new RelayCommand(() =>
            {
                Resource newResource = new Resource()
                {
                    ResourceID = Guid.NewGuid(),
                    Rate = 125,
                    Title = "Staff",
                    UserName = "jsmith"
                };
                workProvider.AddResource(newResource);
            });
        CmdDelete = new RelayCommand(() =>
        {
            workProvider.DelResource(_SelectedResource);
        });
    }
}

Хотя второй подход определенно требует добавления обработчика событий фильтра в конфигурацию CollectionViewSource и может рассматриваться как фильтрация данных 2 раза (1 раз на сервере и второй раз с помощью CollectionViewSource), он лишает следующих преимуществ:Существует единая коллекция, что упрощает и облегчает управление уведомлениями о коллекциях.Коллекция — это фактическая коллекция, которая будет отправлена ​​на сервер, что упрощает управление добавлением/удалением, поскольку нет возможности забыть добавить/удалить объекты из правильной коллекции, чтобы инициировать функцию добавления/удаления при обратной отправке.

Последнее, что мне нужно подтвердить, это следующее:Насколько я понимаю, в источнике представления коллекции вам следует использовать DeferRefresh() при внесении нескольких изменений, влияющих на представление.Это просто предотвращает ненужные обновления, когда внутренние изменения могут вызвать обновления, такие как настройка сортировки, группировки и т. д.Также важно вызывать .View.Refresh(), когда мы ожидаем, что пользовательский интерфейс обработает некоторые изменения обновления.Вероятно, .View.Refresh() важнее, чем DeferRefresh(), поскольку он фактически вызывает обновление пользовательского интерфейса, а не предотвращает неожиданные обновления пользовательского интерфейса.

Не знаю, поможет ли это другим, но надеюсь на это.Я определенно потратил некоторое время на то, чтобы разобраться в этом и попытаться понять это.Если у вас есть пояснения или что-то еще, что можно добавить, пожалуйста, сделайте это.

Другие советы

Райан, это может стоить ваше время, чтобы взглянуть Этот пост на коллекции привязки (а некоторые связанные). Ваша реализация, безусловно, является разумной, но я вижу, что это борется с несколькими проблемами, которые уже были решены на рамочном уровне.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top