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

3

关于MVVM应用程序(曾经是WinRT,现在针对UWP)的架构和数据访问,我感到有些困惑。我不确定如何跨UI传播更改以及在哪里放置数据层访问。

以下是基本体系结构:

  1. 模型层: 包含仅具有自动属性的模型(没有导航属性引用其他模型,只有ID;因此它们基本上只是数据库的表示)。它们不实现INotifyPropertyChanged接口。
  2. 数据访问层: 使用sqlite-net存储模型到数据库的仓储。它公开基本的CRUD操作。它返回并接受模型层的模型。
  3. 视图模型:
    • 模型的视图模型: 它们包装模型并公开属性。有时我使用双向绑定将控件(例如TextBoxes)内容绑定到属性。然后,setter访问数据层以持久化此更改。
    • 视图的页面视图模型: 它们包含上述的视图模型和命令。许多命令已变得非常长,因为它们执行数据访问、执行特定于域的逻辑并更新PageViewModels属性。
  4. 视图 (页面): 它们绑定到PageViewModels,并通过DataTemplate到模型的ViewModle。有时使用双向数据绑定,有时使用命令。

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

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

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

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

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

我还在想MVVM应用程序中是否有双向数据绑定的好主意,因为它要求属性设置器调用数据访问层以持久化更改。仅进行单向绑定并通过命令持久化所有更改是否更好?

如果有人能评论我的架构并提出改进建议或指向关注我的问题的MVVM架构的好文章,我会非常高兴。

1个回答

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

ViewModel不依赖于模型。ViewModel使用模型来满足视图的需求。ViewModel是视图的唯一联系点,所以无论视图需要什么,ViewModel都必须提供。因此,它可以是单个模型/多个模型。但是,您可以将单个ViewModel分解为多个子ViewModel,以使逻辑更加简单。就像详细面板可以被分离成具有自己的视图模型的用户控件。您的主页面将只有窗口,用于托管此控件,而MasterViewmodel将把责任推给子ViewModel。

危险并不在于使用INotifyPropertyChanged,但正如您所说,问题在于订阅和取消订阅。无论何时需要订阅任何事件 - 不仅是INotifyPropertyChanged,都需要使用IDisposable来取消订阅自身及其子ViewModels。我不清楚您描述的数据层,但如果它为任何修改发布属性更改事件,我认为使用INotifyPropertyChanged没有任何问题。
我目前在我的数据访问层上有一个DataChanged事件。每当创建、更新或删除操作发生时,它就会被调用。每个可以同时显示的ViewModel都会监听此事件,检查更改的模型是否是其所属的ViewModel,然后更新自己的属性。再次出现了内存泄漏问题,由于太多的ViewModel必须检查更改是否真正属于它们,导致速度变慢。
正如我之前所说,如果你对所有需要订阅/取消订阅的模型进行正确处理,就不必担心INotifyPropertyChanged的性能问题。但可能导致问题加剧的是你对数据库请求数据的调用次数。你考虑过使用Async...Await来访问数据访问层吗?这不会阻塞UI,即使数据更新很慢,也比被数据调用阻塞的反应式UI更好。
因此,请尝试添加一个数据访问服务,它在DAL层上进行抽象,并提供异步访问数据的方法。另外,请看一下中介者模式。那可能会证明有用。
我也不确定我访问数据的位置是否真的很好。PageViewModels变得非常复杂,基本上做了所有事情。而且我的架构中所有的ViewModel都需要了解数据层知识。我看到两个主要问题:
1.如果您认为PageViewModel太大,请将其拆分为可管理大小的子视图模型。这非常主观,因此您必须尝试查看哪些部分可以拆分为具有自己的组件/用户控件及其自己的视图模型。
2.当您说“ViewModels需要了解数据层”时,我希望您指的是它们依赖于管理DAL层服务的接口,并且没有直接访问具有CRUD方法的类。如果没有,请尝试添加一个抽象层,该层实际上在您的视图模型中执行操作。这将处理DAL CRUD操作。
我一直在考虑放弃使用sqlite-net进行数据访问,改用Entity Framework 7。
不要没有确凿证据就试图用EF替换sqlite-net。在尝试进行如此大的更改之前,您需要在应用程序中测量性能。如果问题出在您的代码而不是您正在使用的组件上,那该怎么办呢?首先尝试解决上述问题,然后可以通过接口隔离DAL层并进行替换(如果需要)。
我也一直在想,在MVVM应用程序中是否拥有双向数据绑定是一个好主意,因为它需要属性设置器调用数据访问层来保存更改。只做单向绑定,并通过命令持久化所有更改是否更好?
如果您每次更改字段/按键时都直接调用数据库,则存在问题。然后,您应该拥有数据模型的副本,并仅在单击保存按钮时持久化更改。

我想要添加一个数据服务层,目前所有的ViewModel都访问DAL的接口。我的数据访问已经是异步的,但是考虑将其改为同步的,因为我通常会有很多连续的读操作(较少但更复杂的操作需要直接暴露sqlite-net功能)。这会降低性能,因为每个小请求都需要创建任务的开销(这是sqlite-net的方式),并且它还阻止了事务的使用。因此,对DAL的访问将是同步的,而新的数据服务层将是异步的。 - SebastianR
谢谢你的长篇回答,CarbineCoder!这已经对我有很大帮助了。 - SebastianR
谢谢...我很高兴它有帮助。除非你做错了,否则不必担心任务创建的开销。数据服务层的想法是好的,也是必要的。除非你有证据表明异步操作导致了问题,否则不要删除它。如果没有证据就进行过早的优化,可能会损害系统。我建议,除非你能够模拟你的方法并测量差异,否则不要这样做。 - Carbine
永远不要直接将更改持久化到数据库,特别是在属性中。属性应该是快速的,长时间运行的操作会阻塞它们,并且此类问题的解决方法很糟糕。通过命令进行持久化,它们是可等待的。最后一段加1分:) - Tseng

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接