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