使用 Entity Framework 的基本 WPF – MVVM 项目教程






4.92/5 (19投票s)
我决定创建自己的示例项目,该项目连接到命名服务器(我本地机器上的 SQL 2008 R2 服务器 – 也适用于 SQL Server 2012 Express)。
引言
我一直在寻找一个使用实体框架(Entity Framework,简称 EF)的基础 WPF – MVVM 示例项目。我找到了一些将 EF 与 WCF 结合的项目,甚至找到了一个直接使用 EF 的 WPF – MVVM 项目,但它连接的是 LocalDB,而且并不基础。当我尝试将连接字符串修改为指向一个命名的 SQL Server 时,项目就出错了。在未能找到任何使用实体框架的基础 WPF-MVVM 示例项目后,我决定创建自己的示例项目,该项目连接到一个命名的服务器(我本地机器上的 SQL 2008 R2 服务器——同样也适用于 SQL Server 2012 Express)。本文的目标读者最好具有 TSQL 的 SQL Server 编程、C# 编程、Visual Studio 2010(或更高版本)以及 .NET Framework 4.0(或更高版本)的背景知识。
此示例项目是一个三层项目,包含数据层、业务层和表示层,由以下部分组成:两个组合框(combobox)、一个文本框(textbox)(在 MainWindow.xaml 中),一个包含两个表(Author 和 Book)的 ADO.NET 实体模型,以及一个 ViewModel – MainWindowViewModel.cs。该示例是在 Windows 7 工作站上使用 Visual Studio 2012 创建的,但也可以在 VS2010 中创建。
项目启动时,MainWindowViewModel
将填充 Author 表实体,该实体是第一个组合框(为简便起见,称之为 combo1)的数据源。当从 combo1 中选择一个 Author 项时,第二个组合框(称之为 combo2)将填充来自 Book 表实体的相关行数据。当从 combo2 中选择一个 Book 项时,该书的 Description 字段内容将显示在文本框中。
首先,我在一个命名的 SQL Server 上的数据库(任何数据库)中创建了两个数据表,这个 SQL Server 恰好位于我的 Win7 工作站上——SQL Server 2008 R2(此项目也适用于 SQL Server 2012 Express)。这两个表是 Author(主表)和 Book(明细表)。这两个表通过主外键(PK-FK)约束相关联。
以下是创建表的 SQL 代码——这将是项目的模型(Model)部分。
create table Author (AuthorId int primary key, AuthorName nvarchar(50))
create table Book (BookId int primary key, AuthorId int, Title nvarchar(50), Description nvarchar(200), Price money)
alter table Book add constraint FK_Book_Author foreign key (AuthorId) references Author (AuthorId)
insert into Author values (1, 'Gambardella, Matthew')
insert into Author values (2, 'Ralls, Kim')
insert into Author values (3, 'Corets, Eva')
insert into Book values (1, 1, 'XML Developers Guide', 'An in-depth look at creating applications with XML.', 4.95)
insert into Book values (2, 1, 'Foods of the world.', 'Simply a book about food.', 5.95)
insert into Book values (3, 1, 'Cars', 'A book about cars.', 8.95)
insert into Book values (4, 2, 'Scarecrows', 'This be could horror or agriculture.', 4.15)
insert into Book values (5, 3, 'Book of blue', 'First in a series of books about colors', 6.30)
insert into Book values (6, 3, 'EF', 'Some tips and trics on Entity Frameworks', 3.45)
接下来,我从 Visual Studio 创建一个新的(标准的)WPF 项目。我将我的项目命名为 Wpf_EF_Mvvm_sample(对于任何想直接复制代码的人——项目可以任意命名——只需更改代码中的命名空间即可)。然后,我首先添加一个 ADO.NET 实体模型(EF5 或 EF6 均可,我都试过)。这个实体模型将作为 Model-View-ViewModel 模式中的模型。我将 .edmx 文件命名为 "AuthorBook"。我使用向导“从数据库生成”,并选择包含我创建的两个表的数据库所在的服务器。我将实体上下文命名为 "AuthorBookEntities"。点击下一步,选择 Author 和 Book 这两个表。然后我将模型名称(在选择表的同一对话框下方)更改为 AuthorBookModel,并点击确定。实体模型现在已经生成。这就是数据层。这个实体模型将使用 DBContext。
接下来,我向项目添加一个 MainWindowViewModel.cs
类。我直接将其添加到项目的根文件夹中(与 MainWindow.xaml 相同的文件夹)。这样做的目的是为了保持项目的简单性。
在 MainWindowViewModel
中,我添加了 using System.ComponentModel
指令,以便使用我们将要实现的 INotifyPropertyChanged
接口,这样我们就可以用属性来执行操作。我还添加了 using System.Runtime.Compilerservices
指令,以便使用 [CallerMemberName]
标签,该标签用于 INotifyPropertyChanged
实现的 NotifyPropertyChanged
方法中。这是一个非常好的特性,因为它省去了在 MainWindowViewModel
的属性中调用 NotifyPropertyChanged()
时包含属性名称的麻烦(以前是 NotifyPropertyChanged("somePropertyName");
)。
项目的所有源代码都将包含在 MainWindowViewModel.cs 中。这就是业务层(Business tier 或 Business layer)。以下是源代码。
using System;
using System.Collections.Generic;
using System.Linq;
using System.ComponentModel;
using System.Runtime.CompilerServices;
namespace Wpf_EF_Mvvm_sample
{
class MainWindowViewModel : INotifyPropertyChanged
{
AuthorBookEntities ctx = new AuthorBookEntities();
public MainWindowViewModel()
{
FillAuthors();
}
private void FillAuthors()
{
var q = (from a in ctx.Authors
select a).ToList();
this.Authors = q;
}
private List<Author> _authors;
public List<Author> Authors
{
get
{
return _authors;
}
set
{
_authors = value;
NotifyPropertyChanged();
}
}
private Author _selectedAuthor;
public Author SelectedAuthor
{
get
{
return _selectedAuthor;
}
set
{
_selectedAuthor = value;
NotifyPropertyChanged();
FillBook();
}
}
private void FillBook()
{
Author author = this.SelectedAuthor;
var q = (from book in ctx.Books
orderby book.Title
where book.AuthorId == author.AuthorId
select book).ToList();
this.Books = q;
}
private List<Book> _books;
public List<Book> Books
{
get
{
return _books;
}
set
{
_books = value;
NotifyPropertyChanged();
}
}
private Book _selectedBook;
public Book SelectedBook
{
get
{
return _selectedBook;
}
set
{
_selectedBook = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] String propertyName = "")
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}
VS2012 的另一个不错的功能(在其众多优秀功能中)是——你可以将鼠标光标放在 MainWindowViewModel
类文件顶部的任何 using 语句上,然后右键单击。从下拉菜单的顶部选择第二个选项“组织 Using”,然后选择“移除未使用的 Using”。这有助于清理杂乱的代码。
在 MainWindowViewModel
类中,我首先创建了一个上下文对象。
AuthorBookEntities ctx = new AuthorBookEntities();
然后在构造函数中,我使用 FillAuthors()
方法加载了第一个实体/列表(Authors)。请注意这里的 LINQ——它需要是一个列表,因为 DBSet 不能被控件直接使用,并且这是列表属性 Authors 的数据源——combo1 的 ItemSource
属性将在 XAML 中绑定到该属性。combo1 的 SelectedItem
属性(在 XAML 中)将绑定到 MainWindowViewModel
中的 SelectedAuthor
属性。当这个属性被调用时,FillBook 方法将被调用,与所选作者相关的书籍将填充 Books 列表属性,而 combo2 的 ItemSource
属性将绑定到该列表属性。
现在 combo1 和 combo2 已经填充了数据。真正酷的部分是当从 combo2 中选择一个项目时,它的 SelectedItem
属性绑定到了 SelectedBook
属性。SelectedBook
属性将只包含 Books 列表中的一行数据。文本框的 Text
属性将绑定到 SelectedBook.Description
属性,所以当从 combo2 中选择一个项目时,所选书籍行的 Description 字段内容将自动出现在文本框中。注意:使用这种技术可能会对 Book 表的字段名产生轻微的依赖。解决这种情况的一个方法是创建一个方法来检索所选 Book 对象的 Description 值,然后为文本框创建一个属性以进行绑定,该属性将被传递 Description 值。
MainWindowViewModel
是业务层。
这个项目的第三层,即表示层,将是 MainWindow.xaml,也就是视图(View)。MainWindow.xaml 将包含所有与 MainWindowViewModel
中属性绑定的 XAML。但在我们连接 XAML 之前,我们需要先编译目前已有的代码,以便创建项目程序集,从而创建一个可供 MainWindow.xaml 使用的数据上下文(DataContext)。
请注意,项目可以在没有表示层的情况下进行编译。这是“关注点分离”的一个例子,这也是 MVVM 模式的核心概念。在 WinForms 中,你会将大量代码附加到窗体上控件的事件中。如果你不小心从窗体上删除/移除了一个控件——项目将无法编译。这是 WinForms 编程中紧密耦合范式的一个例子。MVVM 模式通过将代码与 UI 分离,并使用绑定(Binding)和命令(Commands)在 UI 和代码(业务层)之间进行交互,解决了这个问题。
编译项目后,我们就拥有了一个 MainWindow(视图)可以绑定的程序集。首先,我添加一个对 MainWindowViewModel
所在位置的引用(即根文件夹)。我们将下面这行代码添加到 MainWindow.xaml 的头部区域。
xmlns:vm="clr-namespace:Wpf_EF_Mvvm_sample"
然后我们添加数据上下文。
<Window.DataContext>
<vm:MainWindowViewModel />
</Window.DataContext>
这也可以在后台代码(MainWindow.xaml.cs)中完成。我在这里只是为了保持纯粹,让 MainWindow.xaml.cs 的后台代码不包含任何非默认生成的代码。
以下是表示层的 XAML,也就是视图(这个三层项目的第三层)。
<Window x:Class="Wpf_EF_Mvvm_sample.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:Wpf_EF_Mvvm_sample" Title="MainWindow" Height="350" Width="525"> <Window.DataContext> <vm:MainWindowViewModel /> </Window.DataContext> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="180" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="25" /> <RowDefinition Height="*" /> </Grid.RowDefinitions> <ComboBox DisplayMemberPath="AuthorName" ItemsSource="{Binding Authors}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="1" Name="combo1" SelectedItem="{Binding SelectedAuthor}" /> <ComboBox DisplayMemberPath="Title" ItemsSource="{Binding Books}" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="1" Name="combo2" SelectedItem="{Binding SelectedBook}" /> <TextBlock Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" Name="tbDesc" Text="{Binding SelectedBook.Description}"/> </Grid> </Window>
结论
我展示了一个包含数据层、业务层和表示层的基础三层项目,也就是模型(Model)、视图模型(ViewModel)和视图(View)。模型由一个 ADO.NET 实体模型构成,视图模型由 MainWindowViewModel
类构成,而视图则由 MainWindow.xaml 构成。希望这个示例项目能为那些寻求一个易于理解和遵循的、使用实体框架的 WPF-MVVM 项目的人提供一些启发。