我有一个关于如何以及在何处使用 WPF .NET 4.0 中的 ViewModel 加载大量数据的问题(所以没有 async/await :/)。
这是我的 View 模型:
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;
}
}
此 ViewModel 在窗口中使用,我需要在窗口打开时加载所有人员。我想到了很多解决方案,但我无法确定哪个是最好的(或者也许有更好的方法来做到这一点)。我有两个禁忌: - 所有数据都必须在另一个线程中加载,因为加载可能需要几秒钟(数据库中的大量数据)而且我不想卡住 UI。 - ViewModel 必须是可测试的。
--=[第一种方案:延迟加载]=--
public class PersonsViewModel : ViewModelBase
{
private IEnumerable<Person> _persons;
public IEnumerable<Person> Persons
{
get
{
if (_persons == null)
_persons = _personRepository.GetAll();
return _persons;
}
}
}
我不喜欢这个解决方案,因为数据是在主线程中加载的。
--=[第二种方案:加载事件]=--
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();
}
}
我不太喜欢这个解决方案,因为它强制 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();
}
}
在这个解决方案中,ViewModel 的用户不需要调用额外的方法来加载数据,但它违反了单一责任原则,正如 Mark Seeman 在他的“依赖注入(inject)”一书中所说:“让构造函数不受任何逻辑。SRP 意味着成员应该只做一件事,现在我们使用构造函数来注入(inject) DEPENDENCIES,我们应该更愿意让它摆脱其他问题。”
有什么想法可以以适当的方式解决这个问题吗?
最佳答案
如果不知道如何将 ViewModel 与 View 联系起来,就很难给出准确的答案。
一种做法是拥有一个“导航感知”ViewModel(一个实现特定接口(interface)的 ViewModel,如 INavigationAware
并让您的导航服务在实例化 ViewModel/View 并将它们绑定(bind)在一起时调用此方法. 这就是 Prism 的 FrameNavigationService 的工作方式。
即
public interface INavigationAware
{
Task NavigatedTo(object param);
Task NavigatedFrom(...)
}
public class PersonWindow : ViewModelBase, INavigationAware
{
}
并在 NavigatedTo
中实现您的初始化代码,如果 ViewModel 实现了 INavigationAware
,它将被导航服务调用。
Prism for Windows 应用商店应用引用:
- INavigationAware
- FrameNavigationService => 查看
NavigateToCurrentViewModel
方法
关于c# - MVVM 中的后台加载和构造函数注入(inject),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28457037/