Фоновая загрузка в MVVM и инжекции конструктора

У меня вопрос о том, как и где загружать большой объем данных с помощью ViewModel в WPF.NET 4.0 (так что нет async/await:/).

Вот моя ViewModel:

public class PersonsViewModel : ViewModelBase
{
 private readonly IRepository<person> _personRepository;
 private IEnumerable<person> _persons;
 public IEnumerable<person> Persons
 {
 get { return _persons; }
 private set { _persons = value; OnPropertyChanged("Persons"); }
 }
 public PersonsViewModel(IRepository<person> personRepository)
 {
 if (personRepository == null)
 throw new ArgumentNullException("personRepository");
 _personRepository = personRepository;
 }
}
</person></person></person></person>

Эта модель ViewModel используется в окне, и мне нужно загрузить всех лиц при открытии окна. Я думал о многих решениях, но я не могу понять, какой из них лучше (или, может быть, лучший способ сделать это). У меня есть два противопоказания:   - все данные должны быть загружены в другой поток, поскольку для загрузки может потребоваться несколько секунд (огромный объем данных в базе данных), и я не хочу замораживать интерфейс.   - ViewModel должен быть проверен.

- = [Первое решение: ленивая загрузка] = -

public class PersonsViewModel : ViewModelBase
{
 private IEnumerable<person> _persons;
 public IEnumerable<person> Persons
 {
 get
 {
 if (_persons == null)
 _persons = _personRepository.GetAll();
 return _persons;
 }
 }
}
</person></person>

Мне не нравится это решение, потому что данные загружаются в основной поток.

- = [Второе решение: загруженное событие] = -

public class PersonsViewModel : ViewModelBase
{
 // ...
 private Boolean _isDataLoaded;
 public Boolean IsDataLoaded
 {
 get { return _isDataLoaded; }
 private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
 }
 public void LoadDataAsync()
 {
 if(this.IsDataLoaded)
 return;
 var bwLoadData = new BackgroundWorker();
 bwLoadData.DoWork +=
 (sender, e) => e.Result = _personRepository.GetAll();
 bwLoadData.RunWorkerCompleted +=
 (sender, e) => 
 {
 this.Persons = (IEnumerable<person>)e.Result;
 this.IsDataLoaded = true;
 };
 bwLoadData.RunWorkerAsync();
 }
}
public class PersonWindow : Window
{
 private readonly PersonsViewModel _personsViewModel;
 public PersonWindow(IRepository<person> personRepository)
 {
 _personsViewModel = new PersonsViewModel(personRepository);
 this.Loaded += PersonWindow_Loaded;
 }
 private void PersonWindow_Loaded(Object sender, RoutedEventArgs e)
 {
 this.Loaded -= PersonWindow_Loaded;
 _personsViewModel.LoadDataAsync();
 }
}
</person></person>

Мне не нравится это решение, потому что он заставляет пользователя ViewModel вызывать метод LoadDataAsync.

- = [Третье решение: загрузка данных в конструкторе ViewModel] = -

public class PersonsViewModel : ViewModelBase
{
 // ...
 public PersonsViewModel(IRepository<person> personRepository)
 {
 if (personRepository == null)
 throw new ArgumentNullException("personRepository");
 _personRepository = personRepository;
 this.LoadDataAsync();
 } 
 private Boolean _isDataLoaded;
 public Boolean IsDataLoaded
 {
 get { return _isDataLoaded; }
 private set { _isDataLoaded = value; OnPropertyChanged("IsDataLoaded"); }
 }
 public void LoadDataAsync()
 {
 if(this.IsDataLoaded)
 return;
 var bwLoadData = new BackgroundWorker();
 bwLoadData.DoWork +=
 (sender, e) => e.Result = _personRepository.GetAll();
 bwLoadData.RunWorkerCompleted +=
 (sender, e) => 
 {
 this.Persons = (IEnumerable<person>)e.Result;
 this.IsDataLoaded = true;
 };
 bwLoadData.RunWorkerAsync();
 }
}
</person></person>

В этом решении пользователю ViewModel не нужно вызывать дополнительный метод для загрузки данных, но он нарушает принцип единой ответственности, как отмечает Марк Симан в своей книге "Injection of Dependency": "Не оставляйте конструктора свободным от любая логика. СРП подразумевает, что члены должны делать только одно, а теперь, когда мы используем конструктор для ввода ЗАВИСИМОСТИ, мы должны предпочесть оставить его свободным от других проблем".

Любые идеи для правильного решения этой проблемы?

1 ответ

Трудно дать точный ответ, не зная, как вы привязываете свои ViewModels к вашим представлениям.

Одна из практик заключается в том, чтобы иметь ViewModel с поддержкой навигации (ViewModel, который реализует определенный интерфейс, например INavigationAware), и чтобы ваша служба навигации вызывала этот метод, когда он создает экземпляр ViewModel/View и связывает их вместе. Призма FrameNavigationService работает.

то есть.

public interface INavigationAware
{
 Task NavigatedTo(object param);
 Task NavigatedFrom(...)
}
public class PersonWindow : ViewModelBase, INavigationAware 
{
}

и реализовать код инициализации внутри NavigatedTo, который будет вызываться из службы навигации, , если ViewModel реализует INavigationAware.

Призма для приложений в Windows Store. Ссылки:

licensed under cc by-sa 3.0 with attribution.