在 ViewModel 中具有静态 DbContext 的 WPF 应用程序

Posted

技术标签:

【中文标题】在 ViewModel 中具有静态 DbContext 的 WPF 应用程序【英文标题】:WPF app with static DbContext in ViewModel 【发布时间】:2017-08-18 06:45:35 【问题描述】:

我正在使用 MVVM、代码优先和存储库模式开发 WPF 应用程序。我需要一个后台任务,它处理来自客户端的网络服务器请求并将新数据保存到数据库中。

问题是每个 ViewModel 都有一个属性 (ObservableCollection),它获取 Repository.GetObservableCollection()。所以每个 ViewModel 都有一个存储库实例,它具有相同的 DbContext(所以我在保存复杂实体时不会得到 DbException)。这个 DbContext 一直存在到每个 repo 中的应用程序结束,并从 MainViewModel 注入到 VM 的构造函数中。

当我从后台任务将新数据保存到数据库时,GUI 不会更新,因为我在那里使用不同的 DbContext(由于并发请求,我必须这样做):

using (var db = new DbContextManager())

    var client = new Client();
    db.Client.Add(client);
    db.SaveChanges();

有两种方法,我试过了:

    在 MainViewModel 中设置 DispatcherTimer 以使用 ViewModel.Repository.LoadAll 为每个 repo 每 2 秒更新一次 ViewModel。这会每 2 秒滞后一次我的 UI,但它确实有效。

    将新数据保存到数据库时,还要通过将实体添加到存储库。

    Application.Current.Dispatcher.Invoke ( () => _clientRepository.Add(client); );

这样,实体立即出现在 GUI 中,但也有轻微的滞后(移动窗口时),我无法更新现有实体的属性。

问题是如何重构它以允许 GUI 和后台与实体交互。如何正确组合存储库和 MVVM?

【问题讨论】:

【参考方案1】:

不要直接链接 ViewModel 实体与 DB DataContext。 您需要在 ViewModel 中定义一个ObservableCollection,并在 XAML 中定义它的绑定。

之后,您将通过调用持久层中的函数来异步更新集合。所以你最好使用LoadAsync,或者你可以将你的数据库检索包装到一个任务中。这种方法会起作用,因为await 将捕获 UI 线程的SynchronizationContext 并将在 UI 线程中更新您的组件。

实际的DataContext 必须隐藏在该持久层中,并且必须保持未知对于 ViewModel。

回复cmets

如果我不将 DbContext 传递给 ViewModels(后者又将其传递给 它的存储库),我如何将上下文获取到存储库,所以我可以 打电话

point void LoadAll()  
    context.Set<T>().Load(); 
 

public ObservableCollection<T> GetObservableCollection()  
    return context.Set<T>().Local; 

根据我的回答,ViewModel 可以调用持久层函数来加载集合,(甚至不需要从持久层返回的类型是可观察的)。 关键是您不会返回 void 并且您可能会使用 LoadAsync 以便在将返回的数据分配给 ViewModel 属性的基础集合时可以 await 它。

所以我必须使用 Dispatcher.Invoke 与 repos/viewmodels 交互

不,不要使用Dispatcher.Invoke存储库 DB 上下文进行交互。与数据库异步进行交互。请注意,Dispatcher.Invoke用于 UI 层。

【讨论】:

好的,但是如果我不将 DbContext 传递给 ViewModels(后者又将其传递给它的存储库),我如何将上下文获取到存储库,所以我可以调用 public void LoadAll() context.Set&lt;T&gt;().Load(); public ObservableCollection&lt;T&gt; GetObservableCollection() return context.Set&lt;T&gt;().Local; 我已经在使用 ObservableCollection 并绑定它。此外,后台数据插入在不同的线程中运行,所以我必须使用 Dispatcher.Invoke 与 repos/viewmodels 交互。 答案已更新,如有疑问请创建mcve 我的解决方案是利用 Messenger/Dispatcher 模式在 ViewModel 之间传递消息。【参考方案2】:

你使用绑定机制吗?您可以在更改集合后更新集合(假设您的 VM 实现了INotifyPropertyChanged 接口)。

【讨论】:

是的,我愿意。这对我有用。我的问题在于我拥有的两个 DbContext 实例 - 第一个在 ViewModel.Repository 中,第二个 DbContext 是在请求​​到来时按需创建的。如何从第二个 DbContext 影响(添加)到第一个 DbContext(= 使它们保持同步)?我的意思是它的 .local 属性。我不能只在构造函数中传递第一个 DbContext,因为同一上下文上的并发请求会导致异常。 Dispatcher.Invoke 在 UI 中引入了一个小滞后。我只需要统一这些上下文,或类似的东西。 好吧,你能只使用全局版本的上下文吗? 处理web请求和向数据库插入新数据的部分代码可能不能使用相同的全局上下文,因为当同时处理两个请求时,那些在相同上下文上的操作结果在并发异常中。 也许你应该看看这些文章:source 1,source 2。这很有用。

以上是关于在 ViewModel 中具有静态 DbContext 的 WPF 应用程序的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Web API 请求的 FromBody ViewModel 中使用具有 EnumMember 属性的枚举?

如何在 BottomSheetDialog 中获取 ViewModel

如何使 WPF DataGrid 显示具有绑定和 DataTemplate 的 ViewModel 集合

WPF - 使用具有多个 ViewModel 的单个模型

具有混合字段和列表的 ViewModel

具有可观察集合类型的 Viewmodel 的 MVVM ListView 不更新视图