【问题标题】:MVVM architecture: one model - several view models + place for data accessMVVM 架构:一个模型 - 多个视图模型 + 数据访问位置
【发布时间】:2016-02-14 13:26:56
【问题描述】:

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

这是基本架构:

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

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

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

  1. 模型的一个 ViewModel?我认为这没有多大意义,因为主列表只需要很少的逻辑,而详细视图则需要更多。
  2. 模型实现 INotifyPropertyChanged 从而将更改传播到 ViewModel?我遇到的问题是,数据层目前不保证它为一个模型 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 架构的优秀文章,我将非常高兴。

【问题讨论】:

    标签: c# entity-framework mvvm architecture


    【解决方案1】:
    1. 该模型有一个 ViewModel 吗?我认为这没有多大意义,因为主列表只需要很少的逻辑,而详细视图则需要更多。

    ViewModel 不依赖于模型。 ViewModel 使用模型来满足视图的需求。 ViewModel 是视图的单一联系点,因此无论视图需要什么,viewmodel 都必须提供。所以它可以是单个模型/多个模型。但是您可以将单个 ViewModel 分解为多个子 ViewModel 以使逻辑更容易。它类似的细节窗格可以分离成具有自己的视图模型的用户控件。您的母版页将只有一个窗口来承载此控件,而 MasterViewmodel 会将职责推送到子 ViewModel。

    1. 让模型实现 INotifyPropertyChanged 从而将更改传播到 ViewModel?我遇到的问题是 数据层目前不保证它返回的对象 对于一个模型 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 取消数据访问并使用 改为实体框架 7。

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

    我也一直想知道是否有两种方式的数据绑定是好的 在 MVVM 应用程序中完全没有想法,因为它需要属性设置器 调用数据访问层来持久化更改。是不是更好 只做单向绑定并通过命令持久化所有更改?

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

    【讨论】:

    • 我想我要添加一个数据服务层,目前所有ViewModels访问DAL的接口。我的数据访问已经是异步的,但我正在考虑将其设为 synchronous ,因为我通常连续进行数百次读取操作。 (更少但更复杂的需要直接暴露 sqlite-net 功能。)这会降低性能,因为为每个小请求创建任务都会产生开销(这就是 sqlite-net 的做法),而且它还会阻止使用的交易。因此对 DAL 的访问将是同步的,但新的数据服务层将是异步的。
    • 感谢您的长篇回答,CarbineCoder!这已经帮了我不少忙了。
    • 干杯...我很高兴它有帮助。除非您做错了,否则您不必担心任务创建的开销。数据服务层的想法是好的和需要的。除非您有证据证明异步导致了问题,否则不要删除它。您可能会在没有证据的情况下进行过早的优化,最终损害系统。我建议不要这样做,除非您可以模拟您的方法并衡量差异。
    • +1 表示最后一段。您永远不应该将更改直接保存到数据库中,尤其是在属性中。属性应该很快,长时间运行的操作会阻塞它们,并且解决方法很糟糕而且很糟糕。通过命令坚持,它们是可等待的
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-11
    • 1970-01-01
    • 2014-10-24
    • 1970-01-01
    相关资源
    最近更新 更多