MVVM架构:一个模型-多个视图模型+数据访问的地方

Seb*_*anR 3 c# architecture entity-framework mvvm

我对涉及数据访问的 MVVM 应用程序(以前的 WinRT,现在面向 UWP)的体系结构感到非常困惑。我非常不确定如何在 UI 中传播更改以及在何处访问数据层。

这是基本架构:

  1. 模型层:包含只有自动属性的模型(没有引用其他模型的导航属性,只有 Id;所以它们基本上只是数据库的表示)。它们不实现 INotifyPropertyChanged。
  2. 数据访问层:使用 sqlite-net 将模型存储在数据库中的存储库。它公开了基本的 CRUD 操作。它从模型层返回并接受模型。
  3. 视图模型
    • 模型的视图模型:它们环绕模型并公开属性。有时我以两种方式将控件(例如文本框)的内容绑定到属性。然后 setter 访问数据层以保留此更改。
    • Views 的 PageViewModels:它们包含上面的 ViewModels 和命令。许多命令在执行数据访问、执行领域特定逻辑和更新 PageViewModels 属性时变得很长。
  4. 视图(页面):它们绑定到 PageViewModels 并通过 DataTemplate 绑定到模型的 ViewModels。有时有双向数据绑定,有时我使用命令。

我现在对这个架构有几个问题:

问题 1:一个模型可以在多个宫殿的屏幕上显示。例如,一个主从视图,显示一个类型的所有可用实体的列表。用户可以选择其中之一,其内容显示在详细视图中。如果用户现在更改了详细视图中的属性(例如模型的名称),则更改应立即反映在主列表中。这样做的最佳方法是什么?

  1. 模型一个 ViewModel吗?我认为这没有多大意义,因为主列表只需要很少的逻辑,而详细视图要多得多。
  2. 模型实现 INotifyPropertyChanged并将更改传播到 ViewModels?我遇到的问题是,数据层目前不能保证它在一个模型 ID 上为两次读取操作返回的对象是相同的——它们只包含从数据库读取的数据,并且在读取时是新创建的(我认为这就是 sqlite-net 的工作方式)。由于来自 ViewModel 的所有 PropertyChanged 事件订阅,我也不太确定如何避免发生内存泄漏。我应该实现 IDisposable 并让 PageViewModel 调用它的孩子的 Dispose() 方法吗?
  3. 我目前在我的数据访问层上有一个DataChanged 事件。每当发生创建、更新或删除操作时都会调用它。可以同时显示的每个 ViewModel 都会侦听此事件,检查更改的模型是否是其 ViewModel 的模型,然后更新自己的属性。我再次遇到内存泄漏的问题,这变得很慢,因为太多的 ViewModel 必须检查更改是否真的适合他们。
  4. 其它的办法?

问题 2:我也不确定我访问数据的地方是否真的选择得很好。PageViewModels 变得非常复杂,基本上可以做所有事情。所有 ViewModel 都需要了解我的架构的数据层。

我一直在考虑使用 sqlite-net 取消数据访问并改用 Entity Framework 7。这是否可以解决上述问题,即当我使用相同的上下文时,它是否保证一个模型的对象身份?我还认为它会简化 ViewModel,因为我很少需要读取操作,因为这是通过导航属性完成的。

我也一直想知道在 MVVM 应用程序中使用双向数据绑定是否是个好主意,因为它需要属性设置器调用数据访问层来持久化更改。仅进行单向绑定并通过命令保留所有更改是否更好?

如果有人可以对我的架构发表评论并提出改进建议,或者指出关注我的问题的关于 MVVM 架构的好文章,我会非常高兴。

Car*_*ine 5

  1. 模型有一个 ViewModel 吗?我认为这没有多大意义,因为主列表只需要很少的逻辑,而详细视图要多得多。

ViewModel 不依赖于模型。ViewModel 使用模型来解决视图的需求。ViewModel 是视图的单一联系点,因此无论视图需要什么,视图模型都必须提供。所以它可以是单个模型/多个模型。但是您可以将单个 ViewModel 分解为多个子 ViewModel,以简化逻辑。它的详细信息窗格可以分成一个用户控件,并带有自己的视图模型。您的母版页将仅具有托管此控件的窗口,并且 MasterViewmodel 会将职责推送到子 ViewModel。

  1. 让模型实现 INotifyPropertyChanged 并将更改传播到 ViewModels?我遇到的问题是,数据层目前不能保证它为一个模型 ID 上的两次读取操作返回的对象是相同的——它们只包含从数据库读取的数据,并且在读取时是新创建的(我认为这就是 sqlite-net 的工作方式)。由于来自 ViewModel 的所有 PropertyChanged 事件订阅,我也不太确定如何避免发生内存泄漏。我应该实现 IDisposable 并让 PageViewModel 调用它的孩子的 Dispose() 方法吗?

危险不在于使用INotifyPropertyChanged,而是正如您所说的那样,订阅和取消订阅是正确的。在需要订阅任何事件的地方 - 不仅INotifyPropertyChanged需要使用IDisposable取消订阅自身及其子 ViewModel。我不清楚你描述的数据层,但如果它发布任何修改的属性更改事件,我没有看到使用INotifyPropertyChanged.

3.我目前在我的数据访问层上有一个 DataChanged 事件。每当发生创建、更新或删除操作时都会调用它。可以同时显示的每个 ViewModel 都会侦听此事件,检查更改的模型是否是其 ViewModel 的模型,然后更新自己的属性。我再次遇到内存泄漏的问题,这变得很慢,因为太多的 ViewModel 必须检查更改是否真的适合他们。

正如我之前所说,如果您为所有模型正确处理订阅/取消订阅,您就不必担心 INotifyPropertyChanged 的​​性能问题。但可能会增加问题的是您为请求数据而对数据库进行的调用次数。您是否考虑过使用 Async...Await 数据访问层,它不会阻止 UI 进行任何正在发生的更新。即使数据更新很慢,一个不会被数据调用阻塞的反应式 UI 也是一个更好的选择。

所以尝试添加一个在 DAL 层上抽象的数据访问服务,并提供一种访问数据的异步方法。还可以看看Mediator Pattern。这可能会有所帮助。

我也不确定我访问数据的地方是否真的选择得很好。PageViewModels 变得非常复杂,基本上可以做所有事情。所有 ViewModel 都需要了解我的架构的数据层。

我看到的两个主要问题,

  1. 如果您觉得 PageViewModel 太大,请分解为可管理大小的子视图模型。它非常主观,因此您必须尝试使用​​自己的视图模型将所有部分分解为自己的组件/用户控件。
  2. 当您说ViewModel 需要了解数据层时,我希望您的意思是它们依赖于管理 DAL 层服务的接口,并且不能直接访问具有 CRUD 方法的类。如果不尝试添加您在视图模型中实际执行的抽象层。这将处理 DAL CRUD 操作。

我一直在考虑使用 sqlite-net 取消数据访问并改用 Entity Framework 7。

在没有确凿证据的情况下,不要尝试用 EF 替换 sqlite-net。在尝试进行如此大的更改之前,您需要衡量应用程序的性能。如果问题在于您的代码而不是您正在使用的组件,该怎么办。首先尝试修复上述问题,然后您可以通过接口分离 DAL 层并在需要时替换它。

我也一直想知道在 MVVM 应用程序中使用双向数据绑定是否是个好主意,因为它需要属性设置器调用数据访问层来持久化更改。仅进行单向绑定并通过命令保留所有更改是否更好?

如果您每次更改字段/每次击键时都直接调用数据库,那么这是一个问题。然后,您应该拥有数据模型的副本,并且仅在单击保存按钮时才保留更改。