【问题标题】:Where should the crud logic be implemented in mvvm?在 mvvm 中应该在哪里实现 crud 逻辑?
【发布时间】:2024-04-13 19:00:02
【问题描述】:

在我的 MVVM Light 应用程序中,我在客户列表中进行搜索。搜索缩小了显示在主/详细视图中的客户列表,其中包含一个数据网格(主 CustomerSearchResultView)和一个单独定义的带有名字、姓氏、地址等的用户控件(详细信息 - CustomerSearchDetailView)。以下是主/明细视图的主要内容:

 <StackPanel MinWidth="150" >
        <TextBlock Text="Customer Search Result List" />
             <Grid>
                <DataGrid Name="CustomerList" ItemsSource="{Binding SearchResult}" SelectedItem="{Binding SelectedRow, Mode=TwoWay}" >
                      .....
                </DataGrid>
            </Grid>
            <Grid Grid.Column="2">
                <TextBlock Text="Customer Details" Style="{StaticResource Heading2}" Margin="30,-23,0,0"/>
                <content:CustomerSearchDetail DataContext="{Binding SelectedRow}" />
            </Grid>
        </Grid>
    </StackPanel>

两者都有对应的 ViewModel。请备注 CustomerSearchDetail 的 DC,SelectedRow - 它是 CustomerSearchResultViewModel 上的一个属性,定义如下:

private Customer _selectedRow;
...
public Customer SelectedRow
        {
            get { return _selectedRow; }
            set
            {
                _selectedRow = value;
                RaisePropertyChanged("SelectedRow");
            }
        }
...

因此,我没有在 CustomerSearchDetailView 上定义任何 DC - 它是在“主”视图的绑定中设置的(如上所示),它似乎工作正常。

在我的模型文件夹中,我创建了此处使用的客户类。它实现 ObservableObject 和 IDataErrorInfo 并具有引发propertychanged 事件的公共属性。

我运行应用程序,一切似乎都正常。注意:CustomerSearchDetailView 的 ViewModel(即 CustomerSearchDetailViewModel.cs)在这个阶段只是一个空壳并且没有被使用(据我所见......构造函数永远不会被访问)

现在我想在详细视图中为我的客户添加保存/更新功能。好的,我向 CustomerSearchDetailView 添加一个保存按钮,如下所示:

<Button Content="Save" Command="{Binding Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>

我在 CustomerSearchDetailViewModel 中创建了我的“SaveCommand”RelayCommand 属性 - 但它从未被访问过。

嗯……经过一番谷歌搜索后,我想出了这个:

 <Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>

我在此视图中将“MyCustDetails”定义为指向 CustomerSearchDetailViewModel 的资源。瞧!我现在在调试时点击了该方法……但是,我的客户当然是“空的”。 (事实上​​,我在这里花了 2 个小时实现 CommandParameter 并将其绑定到主视图上的“SelectedRow”属性 - 但客户仍然是“null”)。

更多地搜索和搜索 mvvm 示例,我在 Customer 类(模型对象)上实现了我的“SaveCommand”。你猜怎么着?编辑后的客户被传递了 - 我可以将它发送到我的 EF 层,一切似乎都很好......

而且 - 如果你还在我身边 - 我的问题来了:

1.) 我希望 - 并且认为这是做事的“正确的 MVVM 方式” - 在 ViewModel 中访问我的 CRUD/Repository。我该如何在我的场景中做到这一点?

2.) 现在我已经通过模型类(客户)设置了我的 CRUD - 我应该为问题 1 烦恼吗?事实上,我已经删除了 CustomerSearchDetailViewModel 并且一切运行正常。我觉得我发明了 View - Model (MV) 框架... :-P

我非常希望对此提供反馈 - 我为这个“文字墙”道歉。

【问题讨论】:

    标签: wpf mvvm crud


    【解决方案1】:

    假设 DC 表示DataContext

    只是我的看法:

    • 第一个问题是你在CustomerSearchResultViewModel 中对SelectedRow 做了什么特别的事情吗?

    如果答案是否定的,只需删除该属性并使用{Binding ElementName=CustomerList, Path=SelectedItem} 将您的CustomSearchDetailView 直接绑定到DataGrid

    • 现在Button 需要在CustomerSearchDetailView 中使用您的保存/更新命令。因此,我立即倾向于为该视图使用单独的 VM,并在那里定义这些命令。

    现在您提到这些命令未被访问。答案是因为在你的程序中你从来没有真正创建过CustomerSearchDetailViewModel

    正常操作是您的 View 的 DataContext 是 VM(如果它需要一个。在您的情况下,您执行 imo 因为您需要它来保存您的命令)

    查看您的代码,我猜您使用的是 MVVM Light。因此,在ViewModelLocator 中,您拥有Main 属性,在主视图中,您使用Main 属性和Source={StaticResource Locator} 设置了DataContext,其中Locator 是在App.xaml 资源中创建的ViewModelLocator。因此,这将为定义该 DataContext 的该视图创建该 ViewModel。你当然可以在代码隐藏中做同样的事情,但我们不要跑题。

    因此,在您的情况下,您将 DataContext 设置为 SelectedRow,其类型为 Customer,并且使用 DataContext 解析绑定,这就是为什么当您的命令在 Customer 中定义时,它可以正常工作,但是当它在它没有的虚拟机。

    那么为什么当你在你的虚拟机中有命令并使用它时它会起作用

    <Button Content="Save" Command="{Binding Source={StaticResource MyCustDetails}, Path = SaveCommand}" Width="80" Margin="0,0,15,0"/>
    

    ^^ 之所以有效,是因为 DataContext 未使用,因为 Source 已明确指定。在资源中定义了MyCustDetails 的任何位置,都会创建VM。

    所以它起作用了,有什么问题?

    嗯,这真是一团糟。就像你提到的Customer 在那个虚拟机中的细节是空的。好吧,我希望你现在能猜到为什么会这样。这是因为您的 VM 是通过 x:Key="MyCustDetails" 在资源中创建的,但除了绑定明确引用它时,其中没有任何内容被使用或设置

    在这个系统中,我们得到的命令要么引用完全错误的模型,要么引用虚拟机,而虚拟机是为此目的而创建的资源。 DataContext 与“SearchResults”视图密切相关,这使得未来的扩展或布局更新变得不那么容易。

    如果我们将 View VM 保持为 1 1 的关系,我们可以避免所有这些混淆。因此,总而言之,我们可以一起回答您的两个问题。虽然这可行,但请不要让您的代码变成这样,并对其进行调整以更好地帮助未来扩展并遵守一些基本准则。

    那么我们该怎么做呢?

    方法 1:

    1. 在您的 CustomerSearchDetail 视图中,添加 DependencyProperty 类型为 Customer 让我们称之为 SelectedCustomer

    2. 现在在CustomerSearchResultView 中将DataContext="{Binding SelectedRow}" 替换为SelectedCustomer="{Binding SelectedRow}"

    3. 现在将 CustomerSerachDetailView 的 DataContext 设置为 VM,类似于 CustomerSerachResultsView 链接到其 VM 的方式(使用 ViewModelLocator 在 xaml 中通过 DataContext Binding 猜测)

    4. 现在您可以在ButtonCustomerSerachDetailView 中使用您的命令,就像&lt;Button Command="{Binding SaveCommand}" ... 一样

    5. 最后因为SelectedRow 不再是CustomerSerachDetailsView 中的DataContext,所以您的FirstName、Lastname、Address 绑定似乎都将停止工作。

    我们有很多选择来解决这个问题。

    首先是在每个 Binding 中使用指向 CustomerSerachDetailsView 的 RelativeSource FindAncestor 绑定,并通过我们在获取相应字段之前创建的 CurrentCustomer DP(DependencyProperty) 进行绑定。

    例如:

    <TextBlock Text={Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:CustomerDetailsView}}, Path=CurrentCustomer.FirstName}" />
    

    现在,如果您有多个属性,这很快就会开始变得烦人。所以然后选择一个共同的祖先(比如这些TextBlocks 中的3 个被分组在StackPanel 下)并通过与^^ 的类似绑定将它的DataContext 应用为CurrentCustomer 元素。现在StackPanel 的子DataContext 将成为Customer 元素,因此在它们的每个绑定中,您不必执行整个RelativeSource 事情,只需提及{Binding Path=FirstName} 等等。

    就是这样。现在你有两个视图,它们有各自的 VM 和一个模型 (Customer),每个视图都有各自的任务。

    太好了,我们完成了吗?还没有出错。

    虽然方法 1 比我们开始的方法更好,但它仍然只是“meh”。我们可以做得更好。

    方法 2

    MVVMLight 有一个Messenger 类,它允许您以弱依赖格式在不同类之间进行通信。如果您还没有,您需要研究一下。

    那么我们如何处理Messenger

    很简单:

    1. CustomerSearchResultsViewModelSelectedRow 的设置器中,我们将向CustomerSearchDetailsViewModel 发送一条带有新传入value 的消息。

    2. 现在在CustomerSearchResultsViewModel 中,我们将添加一个属性CurrentCustomer 并为其分配这个传入值。

    3. CustomerSerachDetailsView 中,我们不再创建 DP。这意味着我们不再将 SelectedRow 设置为来自 CustomerSearchResultsViewCustomerSerachDetailsView 中的任何内容(DataContext 或 DP)(甜少的工作:))

    4. 至于我们分配DataContextCustomerSerachDetailsView 的方式或绑定Button.Command 的方式 - 它们与方法1

    5. 相同
    6. 最后是实际的“FirstName”和 Binding 的。现在CurrentCustomerCustomerSearchDetailsViewModel 的属性。所以绑定到它就像按钮绑定到它的命令一样

    ^^ 现在可以正常工作了,因为 DataContextTextBlock 是 VM,并且属性 CurrentCustomer 存在于其中。

    【讨论】:

    • 哇! +1 非常彻底的回复。对此,我真的非常感激!如您所见,我刚刚开始学习 WPF/MVVM - 来自 ASP.NET MVC 背景。我将尽快开始深入研究,并尝试您建议的信使方法。非常感谢!
    最近更新 更多