我该在ViewModel中的哪里调用模型?

3

我正在学习MVVM。

我的View从ViewModel中的ObservableCollection属性(例如属性“Oc1”和“Oc2”)中填充了两个组合框。我还有一个属性绑定到Oc1的选定项(例如属性“SelVal”),Oc2依赖于它,因此当属性SelVal更改时,Oc2需要重新从数据库获取数据。

现在,我想出了一个解决方案,它可以解决我的问题,但似乎不符合get访问器的原则,因此我想知道我将来可能会遇到什么问题,以及更好的解决方案是什么?

我的当前解决方案是:

Oc2get访问器查询数据库并将其私有字段设置为从数据库返回的值(View使用该值)。因此,当SetVal更改时,我只需在SetValset访问器中调用this.RaisePropertyChanged("Oc2"),View就会请求Oc2,然后Oc2查询数据库并返回更新的列表。

问题在于,我没有按照它的用途使用get访问器,因为我在其中分配了它的值。但我喜欢它的自包含性(即我不需要一个“BindOc2”方法,我需要在构造函数中调用它,然后再次在SelVal的set访问器中调用它)。请给予建议。有更好的方法吗?

5个回答

2

我理解你的顾虑,因为在不同属性上引发 PropertyChanged 确实有些奇怪,但在我看来并不是太糟糕。

更自然的方法是在 SelVal 的 setter 中执行数据库查询,因为这会触发数据更改。然后,将 Oc2 设置为结果,这将自动引发 PropertyChanged

唯一的问题是,如果从未访问 Oc2 getter,则可能会不必要地获取 db 结果,但考虑到你知道视图始终需要它们,我倾向于改用此解决方案。


感谢您的反馈,Gaz。您指出的问题是我不愿将db pull放入SelVal setter的主要原因。也许只是我的想法,但另一个原因是如果在以后的开发中我忘记了我把它放在那里,那么它并不是立即可追踪的从Oc2。 - Steve

2
您的怀疑是正确的,这种方式破坏了MVVM模型并且没有使用可以简化工作的机制,例如System.Windows.Interactivity中的触发器,它在Expression Blend SDK中可用。
在getter中加载数据是在自找麻烦。它将您的模型绑定到数据访问代码,使测试变得更加困难,并通过在同一属性中混合不同的关注点使代码变得更加复杂。此外,墨菲定律规定,在某些时候,您只想设置属性而不从数据库重新加载。
更好的解决方案是将数据加载代码提取到单独的方法中,该方法可以由命令或触发器调用。该方法将修改Oc2集合的内容或仅用新集合替换它。无论哪种情况,属性setter都会引发适当的通知并更新第二个组合框。
以下示例来自类似SO问题中fabien's answer的回答:
<ComboBox x:Name="fileComboBox" ItemsSource="{Binding FileList, Mode=TwoWay}">                  
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="SelectionChanged">
                <i:InvokeCommandAction 
                        Command="{Binding SelectionChangedCommand}"
                        CommandParameter="{Binding SelectedItems,           
                        ElementName=fileComboBox}"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>

    </ComboBox>

大多数框架都支持这种设计。Caliburn.Micro提供了actions,当UI事件发生时,将调用ViewModel方法,并提供了一种快捷语法,以避免编写触发器XAML和相应的命令。它只是将事件连接到ViewModel方法。XAML可以简单地写成这样:
<ComboBox x:Name="Oc1" 
    cal:Message.Attach="[Event SelectionChanged] = 
                        [Action ReloadFor($this.SelectedItem)]" />

$this 值是另一种简写,用于引用 ComboBox 本身。

如果您不想使用触发器,可以将您的组合框的 SelectedItem 属性绑定到 ViewModel 属性,并在此属性更改时执行 Reload 方法,例如:

<ComboBox ... SelectedItem={Binding CurrentOC2,Mode=TwoWay} />

你的解决方案听起来很明智(特别是考虑到墨菲定律)。你建议我为所有的组合框都单独创建一个方法吗?因为在之前的非MVVM项目中,我有超过10个方法(BindSites、BindNames、BindBusiness等),最终我将它们分组到其他初始化方法中——它们似乎与属性没有必要分开。但另一方面,我从未成功地设计过一个大型项目,所以这可能是正确的方式... - Steve

1

你现在的做法很好。通常使用属性设置和获取方法来进行这种绑定是不错的选择。


1

所以重新表述一下,你有类似于Category、CurrentCategory和SubCategory的东西。当CurrentCategory改变时,SubCategory需要刷新。

我认为你的方法很好。特别是在MVVM中,我经常看到这种情况。在MVVM之外,属性getter经常会访问数据库进行延迟加载场景(通常调用某种服务方法,而不是内联所有内容)。


谢谢 Kenneth。当你说到“到处都是”时,是指在大型项目中吗?因为正如 Panagiotis 所说,有一天我可能想设置属性,但却无法做到。 - Steve
是的,在大型项目中。然而,在大型项目中,视图模型通常仅表示针对视图的逻辑(非 UI 业务相关逻辑通常实现在某种领域/业务/服务层中)。它们通常相当精确地匹配 UI 的功能。如果 UI 不允许您设置子类别集合,则通常我不会在我的视图模型中有公共 setter 与之匹配。 - Kenneth Ito

1
如果这些数据不会改变,你可以将它们放在你的视图模型中。
总是查询数据库不应该是第一选择。
你可以用一种非常简单的方式来实现这个。
Dictionary<string, List<string>> cache;
...
List<string> subCat;
if cache.TryGetValue (selVal, out subCat) 
{
    // no need to call database
}
else
{
   // else call database
   // insert result in cache
}

我会尝试融入你的想法,Mare,谢啦。 - Steve

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