前言
本手册旨在指导读者一步一步使用 C# 和 WPF 技术,并结合 MVVM (Model-View-ViewModel) 设计模式,构建一个功能完善的图书管理系统。通过本实训,您将不仅掌握 MVVM 模式的核心思想和实践方法,还能深入了解 WPF 数据绑定、命令、用户控件等关键技术,并学会如何运用 SQLite 进行本地数据存储。
本教程力求详尽,包含详细的代码解析和编程步骤,旨在帮助学生和开发者轻松重现并理解整个项目的构建过程。
##登录界面
主界面
章节一:项目介绍与环境搭建
1.1 项目概述
本实训项目——图书管理系统,将涵盖以下核心功能:
- 图书管理:支持添加新图书、浏览图书列表、编辑图书信息以及删除图书。
- 库存管理:能够记录图书的库存数量,并在录入相同 ISBN 的图书时自动累加数量。
- 借阅管理:实现用户借阅图书的功能,系统将记录借阅者的信息和借阅日期。支持多用户借阅同一本书的不同副本(前提是库存充足),借阅者信息将以逗号分隔的形式展示。
- 归还管理:用户可以方便地归还已借阅的图书。
- 用户管理:提供基础的用户登录及用户切换功能。
- 图书搜索:用户可以根据书名、作者或 ISBN 快速检索图书。
通过完成此项目,您将能够:
- 深刻理解 MVVM 设计模式的原理及其在实际项目中的应用。
- 熟练掌握 WPF 中的数据绑定、命令(Commanding)、通知机制(INotifyPropertyChanged)等核心概念。
- 学会使用 SQLite 作为轻量级本地数据库,并进行数据持久化操作。
- 提升 C# 编程能力和 WPF 应用开发技能。
1.2 技术选型
为了构建这个图书管理系统,我们选择以下技术栈:
- 编程语言:C# (我们将利用其强大的面向对象特性和 .NET 生态系统)
- 用户界面框架:WPF (Windows Presentation Foundation) - 用于创建具有丰富用户体验的桌面应用程序。
- 设计模式:MVVM (Model-View-ViewModel) - 旨在分离用户界面(View)与业务逻辑和数据(Model),提高代码的可测试性、可维护性和可重用性。
- 数据库:SQLite - 一个轻量级的、基于文件的关系型数据库,非常适合桌面应用程序的本地数据存储。
- .NET 版本:本项目推荐使用 .NET Framework 4.7.2 或更高版本,或者 .NET Core 3.1 / .NET 5/6/7/8 及以上版本。请确保您的开发环境满足此要求。
1.3 开发环境准备
在开始项目之前,请确保您的开发环境中已安装以下软件:
- Visual Studio: 推荐使用 Visual Studio 2019 或更高版本(Community, Professional, or Enterprise Edition均可)。请在安装时确保勾选了 “.NET桌面开发” 工作负载,这将包含 WPF 和 C# 开发所需的工具和 SDK。
- .NET SDK: 根据您选择的 .NET 版本(Framework 或 Core/5+),确保已安装相应的 SDK。
- .NET Framework 通常随 Visual Studio 一同安装。
- .NET Core/5+ SDK 下载地址:https://dotnet.microsoft.com/download
- SQLite 支持: 为了在项目中使用 SQLite 数据库,我们需要相应的 NuGet 包。我们将在后续步骤中通过 NuGet 包管理器来安装它们,例如
Microsoft.EntityFrameworkCore.Sqlite
。
环境验证:
- 打开 Visual Studio,尝试创建一个新的 “WPF 应用程序” 项目,如果能够成功创建并运行一个空白窗口,则表明您的 WPF 开发环境已基本就绪。
1.4 创建项目与基本结构
现在,让我们开始创建我们的图书管理系统项目。
- 启动 Visual Studio。
- 在欢迎界面选择 “创建新项目”。
- 在“创建新项目”对话框中,从模板列表中选择 “WPF 应用程序” (如果您使用的是 .NET Core/5+,则选择 “WPF Application”)。您可以使用顶部的搜索框输入 “WPF” 来快速筛选。
- 确保选择的是 C# 语言的 WPF 模板。
- 点击 “下一步”。
- 配置新项目:
- 项目名称:输入
WPF-Books
(或者您喜欢的其他名称)。 - 位置:选择一个合适的文件夹来存放您的项目(例如
d:\00Learning\05C#\
)。 - 解决方案名称:可以保持与项目名称一致,或者自定义。
- 框架:根据您的环境选择合适的 .NET Framework 版本或 .NET Core/5+ 版本。
- 点击 “创建”。
Visual Studio 将会生成一个包含基本 WPF 应用程序结构的新项目。
推荐的项目文件夹结构:
为了更好地组织代码和资源,我们建议在项目中创建以下文件夹结构。这有助于保持项目的清晰度和可维护性,尤其是在项目规模逐渐增大时。
WPF-Books/
├── App.xaml # 应用程序定义文件,程序入口点
├── App.xaml.cs # App.xaml 的后台代码
├── MainWindow.xaml # 默认创建的主窗口 (我们稍后会重命名或替换为 BookView.xaml)
├── MainWindow.xaml.cs # MainWindow.xaml 的后台代码
├── Models/ # 存放数据模型类 (例如:Book.cs, User.cs)
├── ViewModels/ # 存放视图模型类 (例如:BookViewModel.cs, LoginViewModel.cs)
├── Views/ # 存放视图 XAML 文件 (例如:BookView.xaml, LoginView.xaml, AddBookView.xaml)
├── Converters/ # (可选) 存放值转换器类 (例如:BooleanToVisibilityConverter.cs)
├── DataAccess/ # (可选, 如果数据库访问逻辑复杂) 存放数据库上下文和访问类
├── Resources/ # (可选) 存放共享资源,如样式、模板、图片等
├── WPF-Books.csproj # 项目文件
└── WPF-Books.sln # 解决方案文件
创建文件夹步骤:
- 在 Visual Studio 的 “解决方案资源管理器” 中,右键点击
WPF-Books
项目。 - 选择 “添加” -> “新建文件夹”。
- 依次创建
Models
,ViewModels
,Views
,Converters
等文件夹。
在接下来的章节中,我们将逐步填充这些文件夹中的内容,构建起我们图书管理系统的各个模块。
章节二:MVVM 模式基础
2.1 什么是 MVVM?
MVVM (Model-View-ViewModel) 是一种专门为用户界面(UI)应用程序设计的软件架构模式。它是经典 MVC (Model-View-Controller) 和 MVP (Model-View-Presenter) 模式的演进,特别适用于像 WPF 和 UWP 这样的现代 UI 框架,因为这些框架通常提供了强大的数据绑定功能。
MVVM 模式的核心思想是将应用程序的三个主要部分进行解耦:
- Model (模型):代表应用程序的数据和业务逻辑。它不关心数据如何显示或如何与用户交互。在我们的图书管理系统中,
Book.cs
和User.cs
就是模型的例子,它们定义了图书和用户的属性。 - View (视图):负责用户界面的展示和用户输入。在 WPF 中,View 通常由 XAML 文件定义(例如
BookView.xaml
,LoginView.xaml
)。View 的主要职责是显示 ViewModel 提供的数据,并将用户的操作(如点击按钮、输入文本)通知给 ViewModel。 - ViewModel (视图模型):作为 View 和 Model 之间的桥梁。它从 Model 中获取数据,并将其转换为 View 可以直接绑定的格式(例如,将日期格式化为字符串,或者提供一个布尔值来控制某个控件的可见性)。ViewModel 还包含 View 的状态和行为逻辑,例如处理用户命令、执行数据验证等。ViewModel 不直接引用 View,而是通过数据绑定和命令与 View 通信。
图示 MVVM 结构:
+-----------------+ Data Binding +-----------------+
| View | <--------------------> | ViewModel |
| (XAML: Buttons, | (Properties, | (C#: Logic, |
| Lists, Text) | Commands) | State, Commands)|
+-----------------+ +-----------------+
| |
| User Actions (e.g., Click) | Interacts with
+----------------------------------------+ Model
|
v
+-----------------+
| Model |
| (C#: Data, |
| Business Logic)|
+-----------------+
2.2 MVVM 的核心组件与通信
让我们更详细地了解 MVVM 的每个组件以及它们如何协同工作:
-
Model (模型)
- 职责:包含应用程序的数据(例如,图书列表、用户信息)和业务规则(例如,如何计算价格、验证数据有效性)。
- 特点:通常是普通的 C# 类 (POCO - Plain Old CLR Object)。它不依赖于 View 或 ViewModel。
- 示例:在我们的项目中,
Book.cs
类定义了图书的属性(如标题、作者、ISBN),User.cs
类定义了用户的属性(如用户名、密码)。
-
View (视图)
- 职责:定义用户界面的结构、布局和外观。它负责向用户显示数据,并捕获用户的输入(如鼠标点击、键盘输入)。
- 特点:在 WPF 中,通常使用 XAML 声明式地定义 View。View 应该尽量保持“哑的 (dumb)”,即不包含复杂的逻辑。它通过数据绑定从 ViewModel 获取数据,并通过命令将用户操作传递给 ViewModel。
- 示例:
BookView.xaml
将用于显示图书列表和相关操作按钮;LoginView.xaml
将用于用户登录。
-
ViewModel (视图模型)
- 职责:这是 MVVM 模式的核心。它充当 View 和 Model 之间的中介。
- 暴露数据:ViewModel 从 Model 中获取数据,并将其转换为 View 可以轻松显示的格式。这可能涉及到数据转换、格式化或聚合。这些数据通常以公共属性的形式暴露给 View 进行绑定。
- 处理行为:ViewModel 暴露命令 (Commands),View 可以将用户的操作(如点击按钮)绑定到这些命令上。当命令被执行时,ViewModel 会执行相应的业务逻辑,可能会更新 Model 或自身的其他属性。
- 状态管理:ViewModel 维护 View 的状态(例如,当前选中的项、某个控件是否启用等)。
- 与 Model 交互:ViewModel 根据需要与 Model 交互,以读取或修改数据。
- 特点:ViewModel 不直接引用 View。它通过属性更改通知 (INotifyPropertyChanged) 来告知 View 数据的变化,View 则通过数据绑定自动更新。它通常包含对 Model 的引用或通过服务来访问 Model。
- 示例:
BookViewModel.cs
将包含图书列表 (ObservableCollection<Book>
)、用于添加/删除/借阅/归还图书的命令,以及处理这些操作的逻辑。LoginViewModel.cs
将处理用户登录逻辑。
- 职责:这是 MVVM 模式的核心。它充当 View 和 Model 之间的中介。
通信机制:
-
数据绑定 (Data Binding):这是 WPF 的一项核心功能,也是 MVVM 实现的关键。View 的元素(如
TextBox
的Text
属性,ListBox
的ItemsSource
属性)可以绑定到 ViewModel 的公共属性。当 ViewModel 的属性值发生变化时,如果 ViewModel 实现了INotifyPropertyChanged
接口并正确触发了PropertyChanged
事件,绑定的 View 元素会自动更新。反之,如果绑定是双向的 (TwoWay),View 中用户输入导致的变化也会更新到 ViewModel 的属性。 -
命令 (Commands):WPF 中的命令机制 (ICommand接口) 允许将用户界面操作(如按钮点击)与 ViewModel 中的方法解耦。View 将控件的
Command
属性绑定到 ViewModel 中实现了ICommand
接口的属性。ViewModel 中的命令对象封装了要执行的逻辑 (Execute
方法) 以及该逻辑是否可以执行的条件 (CanExecute
方法)。这使得 ViewModel 可以控制 UI 元素的启用/禁用状态,而无需直接操作 View。 -
属性更改通知 (INotifyPropertyChanged):为了使数据绑定能够响应 ViewModel 中数据的变化,ViewModel 类通常需要实现
INotifyPropertyChanged
接口。该接口只有一个事件:PropertyChanged
。当 ViewModel 的某个属性值发生变化时,它会触发此事件,并传递发生变化的属性的名称。监听此事件的 View(通过数据绑定机制)随后会更新其显示。
2.3 MVVM 的优势
采用 MVVM 模式可以带来诸多好处:
- 可测试性 (Testability):由于 ViewModel 不依赖于具体的 UI 元素 (View),因此可以轻松地对其进行单元测试。我们可以实例化 ViewModel,设置其属性,调用其命令,并验证其行为和状态,而无需创建和操作 UI。
- 关注点分离 (Separation of Concerns):MVVM 将 UI (View)、UI 逻辑和状态 (ViewModel) 以及业务逻辑和数据 (Model) 清晰地分离开来。每个部分都有明确的职责,降低了代码的复杂性。
- 可维护性 (Maintainability):代码结构清晰,职责分明,使得修改和维护变得更加容易。例如,修改 UI 布局 (View) 通常不会影响 ViewModel 的逻辑,反之亦然。
- 可重用性 (Reusability):ViewModel 可以在不同的 View 中重用(如果它们需要展示相似的数据和行为)。Model 也可以在应用程序的不同部分甚至不同应用程序中重用。
- 并行开发 (Parallel Development):UI 设计师可以专注于 View (XAML) 的设计和实现,而开发人员可以专注于 ViewModel 和 Model 的逻辑开发,两者可以并行进行,提高了开发效率。
- 设计时数据 (Design-Time Data):WPF 设计器可以更好地支持 MVVM。通过在 ViewModel 中提供设计时数据,UI 设计师可以在不运行应用程序的情况下,在设计器中看到界面的大致外观和数据填充效果。
2.4 MVVM 的劣势与注意事项
尽管 MVVM 带来了很多好处,但在某些情况下也可能存在一些挑战:
- 学习曲线:对于初学者来说,理解数据绑定、命令、属性通知等概念可能需要一些时间。
- 代码量增加:为了实现 ViewModel 和必要的通知机制,可能会引入一些额外的代码(“胶水代码”)。
- 简单 UI 的过度设计:对于非常简单的 UI 或一次性的小工具,严格遵循 MVVM 可能显得有些过度设计。
- 调试数据绑定问题:有时数据绑定不按预期工作,调试起来可能比直接操作 UI 元素更复杂一些。
在我们的图书管理系统中,MVVM 模式的优势将远大于其潜在的复杂性,它将帮助我们构建一个结构良好、易于扩展和维护的应用程序。
章节三:模型 (Model) 的设计与实现
模型(Model)是 MVVM 模式中的核心组成部分之一,它代表了应用程序的数据结构和业务逻辑。在我们的图书管理系统中,我们需要定义清晰的数据模型来表示图书和用户信息。
3.1 定义图书模型 (Book.cs
)
图书是本系统的核心实体。我们需要定义一个 Book
类来描述一本图书的各种属性。
-
在 解决方案资源管理器 中,右键点击
Models
文件夹。 -
选择 “添加” -> “类…”。
-
将类命名为
Book.cs
,然后点击 “添加”。 -
编辑
Book.cs
文件,添加以下属性:using System;
using System.ComponentModel.DataAnnotations; // 用于数据注解,例如 [Key]namespace WPF_Books.Models
{
public class Book
{
[Key] // 将 Id 属性标记为主键,Entity Framework Core 会将其识别为数据库表的主键
public int Id { get; set; }[Required(ErrorMessage = "书名是必填项")] // 标记为必填项,并提供错误消息 [StringLength(200, ErrorMessage = "书名长度不能超过200个字符")] public string Title { get; set; } = string.Empty; [Required(ErrorMessage = "作者是必填项")] [StringLength(100, ErrorMessage = "作者长度不能超过100个字符")] public string Author { get; set; } = string.Empty; [Required(ErrorMessage = "ISBN是必填项")] [StringLength(20, ErrorMessage = "ISBN长度不能超过20个字符")] // 例如:978-7-111-12579-1 public string ISBN { get; set; } = string.Empty; [Range(0, int.MaxValue, ErrorMessage = "数量不能为负数")] public int Quantity { get; set; } // 库存数量 public DateTime? PublishDate { get; set; } // 出版日期,可以为空 // 借阅信息 public string? Borrower { get; set; } // 借阅者用户名,可以为空,多个借阅者用逗号分隔 public DateTime? BorrowDate { get; set; } // 借阅日期,可以为空 public Book() { // 可以在构造函数中设置一些默认值,如果需要的话 // Title = "未知书名"; // Author = "未知作者"; // ISBN = "000-0-000-00000-0"; } }
}
代码解析:
using System.ComponentModel.DataAnnotations;
: 引入此命名空间是为了使用数据注解,如[Key]
和[Required]
。这些注解不仅可以用于数据验证,Entity Framework Core 也会利用它们来配置数据库表结构。public class Book
: 定义了一个公共类Book
。[Key] public int Id { get; set; }
:Id
属性是图书的唯一标识符。[Key]
注解告诉 Entity Framework Core 这个属性是数据库表的主键,并且通常会自动增长。Title
,Author
,ISBN
: 分别表示书名、作者和国际标准书号。它们都被声明为字符串类型,并初始化为string.Empty
以避免空引用异常。[Required(ErrorMessage = "...")]
: 表明这些字段是必填的。如果用户未提供值,ErrorMessage
将用于数据验证提示。[StringLength(maxLength, ErrorMessage = "...")]
: 限制了字符串的最大长度。
Quantity
: 表示图书的库存数量,为整数类型。[Range(0, int.MaxValue, ...)]
确保数量不会是负数。PublishDate
: 图书的出版日期,类型为DateTime?
(可空 DateTime),因为出版日期可能未知。Borrower
: 记录借阅者的用户名。类型为string?
(可空字符串),因为图书可能未被借出。如果允许多人借阅同一本书的不同副本,这里可以考虑存储一个借阅者列表或者用特定分隔符(如逗号)分隔的字符串。BorrowDate
: 记录借阅日期,类型为DateTime?
。- 构造函数
public Book()
: 提供了一个默认的无参构造函数。在这里可以为属性设置初始默认值,但在这个例子中我们主要依赖属性初始化器。
3.2 定义用户模型 (User.cs
)
接下来,我们需要一个用户模型来管理用户信息,主要用于登录和记录借阅者。
-
在 解决方案资源管理器 中,右键点击
Models
文件夹。 -
选择 “添加” -> “类…”。
-
将类命名为
User.cs
,然后点击 “添加”。 -
编辑
User.cs
文件,添加以下属性:using System.ComponentModel.DataAnnotations;
namespace WPF_Books.Models
{
public class User
{
[Key]
public int Id { get; set; }[Required(ErrorMessage = "用户名是必填项")] [StringLength(50, MinimumLength = 3, ErrorMessage = "用户名长度必须在3到50个字符之间")] public string Username { get; set; } = string.Empty; // 密码通常不直接存储明文,而是存储哈希值。这里为了简化,我们存储字符串。 // 在实际应用中,务必使用安全的密码哈希和加盐机制。 [Required(ErrorMessage = "密码是必填项")] [StringLength(100, MinimumLength = 6, ErrorMessage = "密码长度必须至少为6个字符")] public string PasswordHash { get; set; } = string.Empty; // 存储密码的哈希值 // 可以添加其他用户属性,例如: // public string Email { get; set; } // public DateTime RegistrationDate { get; set; } // public UserRole Role { get; set; } // 例如:管理员,普通用户 } // public enum UserRole // { // Admin, // Member // }
}
代码解析:
Id
: 用户唯一标识符,主键。Username
: 用户名,字符串类型,必填,并有长度限制。PasswordHash
: 用于存储用户密码的哈希值。非常重要:在实际生产应用中,绝不能存储明文密码。应该使用安全的哈希算法(如 Argon2, bcrypt, scrypt 或 PBKDF2)结合盐(salt)来存储密码的哈希值。本示例中为了简化,我们将其声明为字符串,但实际项目中需要实现密码哈希逻辑。- 注释中提到了可以添加的其他用户属性,如
Email
,RegistrationDate
,Role
等,可以根据实际需求进行扩展。
3.3 数据持久化方案:SQLite 与 Entity Framework Core
为了存储我们的图书和用户信息,我们需要一个数据持久化机制。SQLite 是一个轻量级的、基于文件的数据库,非常适合桌面应用程序,因为它不需要单独的数据库服务器进程,数据库本身就是一个文件。
Entity Framework Core (EF Core) 是一个现代的对象关系映射器 (ORM),它使得 .NET 开发者可以使用 .NET 对象来处理数据库,而无需编写大量的 SQL 数据访问代码。EF Core 支持多种数据库,包括 SQLite。
安装必要的 NuGet 包:
我们需要通过 NuGet 包管理器安装以下包到我们的 WPF-Books
项目中:
Microsoft.EntityFrameworkCore.Sqlite
: EF Core 的 SQLite 数据库提供程序。Microsoft.EntityFrameworkCore.Tools
: EF Core 的命令行工具,用于数据库迁移等操作(主要在开发时使用)。
安装步骤:
- 通过 NuGet 包管理器 UI:
- 在 解决方案资源管理器 中,右键点击
WPF-Books
项目或 “依赖项” 节点。 - 选择 “管理 NuGet 程序包…”。
- 在打开的 NuGet 包管理器窗口中,切换到 “浏览” 选项卡。
- 搜索
Microsoft.EntityFrameworkCore.Sqlite
,选择它,然后点击 “安装”。 - 同样地,搜索并安装
Microsoft.EntityFrameworkCore.Tools
。
- 在 解决方案资源管理器 中,右键点击
- 通过包管理器控制台:
-
打开 “工具” -> “NuGet 包管理器” -> “包管理器控制台”。
-
在控制台中,确保 “默认项目” 下拉列表选择了
WPF-Books
。 -
执行以下命令:
Install-Package Microsoft.EntityFrameworkCore.Sqlite
Install-Package Microsoft.EntityFrameworkCore.Tools
-
3.4 创建数据库上下文 (AppDbContext.cs
)
数据库上下文 (DbContext) 是 EF Core 与数据库交互的主要入口点。它代表了与数据库的一个会话,并允许我们查询和保存数据。我们需要创建一个继承自 DbContext
的类。
- (可选,但推荐) 在项目中创建一个名为
DataAccess
的文件夹,用于存放与数据访问相关的类。
- 右键点击
WPF-Books
项目 -> “添加” -> “新建文件夹”,命名为DataAccess
。
-
在
DataAccess
(或项目根目录,如果未创建该文件夹) 文件夹下,添加一个新类AppDbContext.cs
。using Microsoft.EntityFrameworkCore;
using WPF_Books.Models; // 引入模型命名空间
using System.IO; // 用于 Path.Combine
using System; // 用于 Environmentnamespace WPF_Books.DataAccess
{
public class AppDbContext : DbContext
{
public DbSet Books { get; set; }
public DbSet Users { get; set; }public string DbPath { get; } public AppDbContext() { // 获取应用程序的本地数据文件夹路径 // Environment.SpecialFolder.LocalApplicationData 为当前用户的应用程序数据文件夹 // string folder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); // DbPath = Path.Combine(folder, "library.db"); // 为了简单起见,我们将数据库文件放在应用程序的执行目录下 // 这种方式在开发和单用户场景下比较方便,但部署时可能需要考虑更合适的路径 string basePath = AppDomain.CurrentDomain.BaseDirectory; DbPath = Path.Combine(basePath, "library.db"); } // OnConfiguring 方法用于配置数据库连接和其他选项 protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { // 使用 SQLite 数据库,并指定连接字符串(即数据库文件的路径) optionsBuilder.UseSqlite($"Data Source={DbPath}"); } // OnModelCreating 方法用于进一步配置模型和它们如何映射到数据库 protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); // 可以在这里添加更复杂的模型配置,例如: // - 配置复合键 // - 配置索引 // - 配置表名或列名 // - 设置数据种子 (Seed Data) // 示例:确保 ISBN 是唯一的 (如果需要) // modelBuilder.Entity<Book>() // .HasIndex(b => b.ISBN) // .IsUnique(); // 示例:为 User 表的 Username 设置唯一索引 modelBuilder.Entity<User>() .HasIndex(u => u.Username) .IsUnique(); // 种子数据:在数据库创建时添加一些初始数据 modelBuilder.Entity<User>().HasData( new User { Id = 1, Username = "admin", PasswordHash = "adminpass" }, // 实际项目中密码应为哈希值 new User { Id = 2, Username = "user1", PasswordHash = "user1pass" } ); modelBuilder.Entity<Book>().HasData( new Book { Id = 1, Title = "C# 从入门到实践", Author = "张三", ISBN = "978-7-121-00001-1", Quantity = 5, PublishDate = new DateTime(2023, 1, 15) }, new Book { Id = 2, Title = "WPF 编程宝典", Author = "李四", ISBN = "978-7-121-00002-2", Quantity = 3, PublishDate = new DateTime(2022, 5, 20) }, new Book { Id = 3, Title = "算法导论", Author = "Thomas H. Cormen", ISBN = "978-7-111-12806-9", Quantity = 2, Borrower = "admin", BorrowDate = DateTime.Now.AddDays(-10) }, new Book { Id = 4, Title = "设计模式之禅", Author = "秦小波", ISBN = "978-7-111-2 设计模式", Quantity = 0, Borrower = "user1", BorrowDate = DateTime.Now.AddDays(-5) } // 假设这本书被借完了 ); } }
}
代码解析:
public DbSet<Book> Books { get; set; }
:DbSet<TEntity>
属性代表了数据库中的一个表。EF Core 会将Books
属性映射到数据库中的Books
表(表名默认与属性名相同)。public DbSet<User> Users { get; set; }
: 类似地,这代表了Users
表。DbPath
: 一个只读属性,用于存储数据库文件的完整路径。AppDbContext()
构造函数:- 确定数据库文件的路径。这里我们选择将
library.db
文件放在应用程序的基目录(执行文件所在的目录)下。AppDomain.CurrentDomain.BaseDirectory
获取此路径。 Path.Combine
用于安全地组合路径字符串。
- 确定数据库文件的路径。这里我们选择将
OnConfiguring(DbContextOptionsBuilder optionsBuilder)
:- 这个方法在
DbContext
实例被创建时调用,用于配置数据库连接等。 optionsBuilder.UseSqlite($"Data Source={DbPath}")
: 配置 EF Core 使用 SQLite,并将连接字符串设置为指向我们的library.db
文件。
- 这个方法在
OnModelCreating(ModelBuilder modelBuilder)
:- 此方法在
DbContext
首次初始化模型时调用,允许我们使用 Fluent API 进一步配置模型。这对于数据注解无法表达的复杂配置非常有用。 modelBuilder.Entity<User>().HasIndex(u => u.Username).IsUnique();
: 配置User
表的Username
列具有唯一索引,确保用户名不重复。modelBuilder.Entity<User>().HasData(...)
和modelBuilder.Entity<Book>().HasData(...)
: 这是 EF Core 的数据播种 (Data Seeding) 功能。它允许我们在数据库创建或迁移时,自动向表中插入初始数据。这对于提供一些默认用户、测试数据或基础配置非常有用。- 注意:种子数据中的密码
adminpass
和user1pass
是明文,仅用于演示。在实际应用中,应存储安全的哈希值。
- 注意:种子数据中的密码
- 此方法在
3.5 数据库初始化与迁移 (Migrations)
EF Core 使用“迁移 (Migrations)”来管理数据库模式的演变。当我们更改了模型(例如,添加新属性、修改数据类型、添加新实体)后,我们可以创建一个新的迁移,EF Core 会生成相应的代码来更新数据库模式以匹配模型。
首次创建数据库和模式:
-
打开包管理器控制台:在 Visual Studio 中,选择 “工具” -> “NuGet 包管理器” -> “包管理器控制台”。
-
添加迁移:在包管理器控制台中,确保默认项目是
WPF-Books
,然后运行以下命令来创建第一个迁移。我们将迁移命名为InitialCreate
。Add-Migration InitialCreate
执行此命令后,EF Core Tools 会检查您的 AppDbContext
和模型类,并生成一个新的迁移文件。这个文件通常位于项目中自动创建的 Migrations
文件夹下,其中包含了创建数据库表和应用种子数据的 C# 代码。
-
应用迁移到数据库:运行以下命令将迁移应用到数据库。如果数据库文件 (
library.db
) 尚不存在,EF Core 会创建它,并根据迁移文件中的指令创建表结构和插入种子数据。Update-Database
执行完毕后,您应该能在项目的输出目录(例如 bin\Debug\netX.X\
)下找到 library.db
文件。您可以使用 SQLite 浏览器(如 DB Browser for SQLite)打开它,查看表结构和种子数据是否已正确创建。
后续模型更改和迁移:
如果将来您修改了 Book.cs
或 User.cs
模型(例如,添加了一个新属性),您需要重复以下步骤:
-
添加新的迁移:
Add-Migration GiveYourMigrationAName (例如:AddBookPublisher)
-
更新数据库:
Update-Database
通过这种方式,EF Core 迁移可以帮助我们以受控和版本化的方式管理数据库模式的变更。
至此,我们已经完成了数据模型的设计、数据库上下文的创建以及数据库的初始化。在下一章节中,我们将开始设计和实现 ViewModel。
章节四:ViewModel 设计与实现
ViewModel 是 MVVM 模式的核心,它充当 View 和 Model 之间的桥梁。在本章节中,我们将设计并实现应用程序所需的 ViewModel,包括一个基础的 ObservableObject
类用于属性变更通知,一个 RelayCommand
类用于处理用户操作,以及具体的 LoginViewModel
和 BookViewModel
。
4.1 实现 ObservableObject
(属性变更通知基类)
为了让数据绑定能够正确工作,当 ViewModel 中的属性发生变化时,需要通知 View 进行更新。这通常通过实现 INotifyPropertyChanged
接口来完成。我们可以创建一个基类 ObservableObject
来封装这个逻辑,使得其他 ViewModel 可以继承它。
-
在 解决方案资源管理器 中,右键点击
ViewModels
文件夹。 -
选择 “添加” -> “类…”。
-
将类命名为
ObservableObject.cs
,然后点击 “添加”。 -
编辑
ObservableObject.cs
文件,实现INotifyPropertyChanged
接口:using System.ComponentModel;
using System.Runtime.CompilerServices;namespace WPF_Books.ViewModels
{
public class ObservableObject : INotifyPropertyChanged
{
public event PropertyChangedEventHandler? PropertyChanged;protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } }
}
代码解析:
using System.ComponentModel;
: 引入INotifyPropertyChanged
接口所在的命名空间。using System.Runtime.CompilerServices;
: 引入CallerMemberNameAttribute
特性所在的命名空间。public class ObservableObject : INotifyPropertyChanged
:ObservableObject
类实现了INotifyPro