1

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

То, что я действительно вижу, как мои варианты:

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

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

ответ

1

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

Основой для моей демонстрации является единственная аутентифицированная служба домена, которая возвращает единую сущность ресурса. Я буду выставлять 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); 
    } 

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

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(), поскольку оно фактически вызывает обновление пользовательского интерфейса, в отличие от предотвращения неожиданных обновлений пользовательского интерфейса.

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

0

Райан, возможно, стоит потратить время, чтобы посмотреть this post on collection binding (и некоторые из связанных). Ваша реализация, безусловно, разумная, но я вижу, что она борется с несколькими проблемами, которые уже были решены на уровне структуры.