MVVM设计感觉太笨拙了,我做错了吗?
本文关键字:错了 感觉 MVVM | 更新日期: 2023-09-27 18:17:11
我最近开始创建一个WPF应用程序,我只是希望有人能向我确认我正在正确地构建我的整体系统架构,或者纠正我,如果我在错误的方向。特别是因为我正在尝试做MVVM,有很多层涉及,我不确定我做的事情是否正确。
下面是系统的简化描述:
数据存储在SQL Server数据库中,通过Linq to SQL访问数据库。假设数据库包含两个表,USERS
和USER_GROUPS
。每个表都有一个自动生成的Linq to SQL类,DB_USER
和DB_USER_GROUP
。
现在在应用程序中,我想显示一个ListBox
,其中每个ListBoxItem
包含用于显示/修改用户信息的各种UI元素,这是使用DataTemplate
完成的。
我有一个窗口的视图模型类,它使用Linq to SQL查询(连接两个表)来填充名为UserList
的ObservableCollection<User>
,窗口中的ListBox
已绑定为其ItemsSource
。User
是一个实现INotifyPropertyChanged
的类,它处理WPF控件所需的所有数据库数据的格式化/获取/设置。处理这个问题的代码部分类似于:
DBDataContext db = new DBDataContext();
var allUsers = from user in db.USERs
.Where(u => u.ENABLED == true)
from group in db.USER_GROUPs
.Where(g => g.GROUPID == u.GROUPID)
.DefaultIfEmpty()
select new { user, group };
foreach (var user in allUsers)
{
User u = new User(db, user.user, user.group);
UserList.Add(u);
}
所以User
类是用DB_USER
、DB_USER_GROUP
和数据库DataContext类的私有属性构造的。所有User
的公共属性基本上都包装了相关的列,它们的get
方法返回WPF使用的值,set
更改列,然后在私有DataContext属性上调用SubmitChanges()
来更新数据库。
这一切都工作得很好,但感觉有点笨拙,所以我只是想知道我是否错过了一些可以使它更干净的东西。具体来说,在UserList
的每个元素中存储一个DataContext似乎很奇怪,但我不确定是否有更好的方法能够在UI中更改数据时更新数据库。
任何反馈都是感激的,如果有什么不清楚的,请告诉我,我不确定我解释得有多好。
首先,让我们在这里做的事情上放一些标签:DB_USER
是你的模型,User
是你的ViewModel(我更喜欢后者的UserViewModel
,以便更清楚发生了什么)。
显而易见的一件事是,它不是真的适合你的ViewModel有适合你的模型的功能,即DataContext
不属于它目前的位置。这是一段信息,要么应该在你的模型中,要么封装在一些DataStore
/DataService
(选择)类中。然后,当需要保存任何更改时,您的ViewModel将负责告诉DataStore
"这是此模型的更新快照,请为我保存"(这很可能通过ICommand
暴露给UI)。这感觉更清晰,并强调了ViewModel是一个将模型的实际情况适应您所选择的UI的层。
除了以上,我觉得在你的描述中没有什么需要"纠正"的。但是,对于你没有详细说明的事情,我可以提供一些建议。
通过ViewModel从Model中公开数据总是可以通过多种方式实现的。在考虑采用何种方法时,您应该考虑同一模型同时通过不同视图公开的可能性。在这种情况下,我认为首选的方法是为每个视图都有一个单独的ViewModel(视图可能是不同类型的,因此它们可能对ViewModel适配器有不同的期望,从而也指向多种类型的ViewModel),所以你需要使用一种模式,允许从一个ViewModel"实时"地与任何其他ViewModel进行通信。
这样做的一种方法是让你的模型自己实现INotifyPropertyChanged
,并让每个ViewModel挂钩到它的模型中以获得通知,所以当发生更改时,ViewModel a将更改推送到模型,并且模型通知ViewModel b。
然而,我个人不喜欢用本质上只满足UI需求的代码来污染我的模型,所以需要另一种方法。这将使我上面提到的DataService
暴露功能(方法和事件),通过它ViewModel A可以告诉服务"嘿,我包装的模型发生了一些变化";请注意,这与"我希望您持久化此模型的当前快照"不同。ViewModel B已经连接到一个合适的"ModelChanged"事件,因此它得到通知并从服务中提取更新的信息。这还有一个额外的好处,如果在任何时候服务检测到后台数据存储库已经被当前进程外部的源更新了,那么就有一个现成的机制来广播一个"调用所有ViewModels: Model X已经更新,任何感兴趣的人请与我讨论学习细节"的消息。
最重要的是,始终记住没有"一种真正的MVVM风格",有无数种可能的方法。选择哪一个不仅取决于硬事实和滑块在YAGNI/HyperEngineering尺度上的当前位置,而且还取决于,我敢说,你的品味。
棘手的问题,我可以理解为什么人们不争先恐后地回答,主要是因为你没有做任何技术上的错误。但是既然你问了,我将告诉你我将改变什么来收紧MVVM(你要求的)和数据访问(你没有)。
我处理ViewModel的方式是作为底层数据模型的包装器,而不是作为视图的增强。它通过提供INotifyPropertyChanged和所有这些来帮助视图,但我试图使ViewModel对它最终在里面的任何视图有用。在我看来,这就是MVC和MVVM的主要区别。你所拥有的更多的是一个MVC,你的用户对象充当视图的控制器。
同样,我可能会放弃UserList而使用ListBox。ItemsSource用于在绑定列表一次后管理它。实际上,在WPF中,我更习惯于有一个CollectionViewSource并将UI控件绑定到那个。使它更容易跟踪选定的项目和添加和删除它们不是太繁重。
对于数据访问,我根本不会将它们提供给对象。数据访问属于您的模型,持久化更改应该在模型中处理。. net框架在连接管理方面做得非常好,以至于重新实例化和拆除是仅次于无成本的最好方法。这允许您将事情保持紧密,并将数据连接封装在using子句中。如果您真的觉得必须在每次属性更改时都持久化到数据库中,那么让模型订阅PropertyChanged事件——毕竟,这就是它的作用。:)
通常,我让我的模型尽可能哑(POCO)。我的视图模型具有查询数据库和设置绑定到UI的属性的繁重逻辑。在该布局中,视图模型具有DataContext的单个实例,并且不需要在模型内部传递它。
我通常遵循这种方法,但有一些差异,那里有很大的惊喜:)在较大的项目中,我不喜欢在我的ViewModels中包装或包含我的域实体。这是我从ASP继承下来的东西。在Action书籍中,你可以在视图接收到模型之前映射(AutoMapper)域实体来显示模型。当域实体和需要显示的内容之间存在阻抗时,这是很好的。
它在模拟屏幕时也很方便,我可以从屏幕向后工作到DisplayModel,因为我知道我将从域实体映射我需要的东西。我很喜欢我的视图是可混合的,所以设计人员可以打开视图,它看起来非常接近运行时视图。
所以在某些情况下,我将有我的实体模型(用户/UserGroup),我的DisplayModel(用户),和我的ViewModel (UserViewModel)。
正如在其他回答中提到的,我倾向于将数据业务排除在ViewModel之外。我将创建一个存储库或某种类型的服务,并将其注入到我的ViewModels中。所以在我的ViewModel中,我可能会调用var user = UserRepository.GetUsers();
,然后将结果映射到我的DisplayModel。或UerRepository.InsertOrUpdate(user);
UserRepository.Save();
无关的,AutoMapper是一个伟大的工具,以及Caliburn Micro或MVVM-Light。