WPF-Books图书管理系统

前言

本手册旨在指导读者一步一步使用 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 开发环境准备

在开始项目之前,请确保您的开发环境中已安装以下软件:

  1. Visual Studio: 推荐使用 Visual Studio 2019 或更高版本(Community, Professional, or Enterprise Edition均可)。请在安装时确保勾选了 “.NET桌面开发” 工作负载,这将包含 WPF 和 C# 开发所需的工具和 SDK。
  1. .NET SDK: 根据您选择的 .NET 版本(Framework 或 Core/5+),确保已安装相应的 SDK。
  1. SQLite 支持: 为了在项目中使用 SQLite 数据库,我们需要相应的 NuGet 包。我们将在后续步骤中通过 NuGet 包管理器来安装它们,例如 Microsoft.EntityFrameworkCore.Sqlite

环境验证

  • 打开 Visual Studio,尝试创建一个新的 “WPF 应用程序” 项目,如果能够成功创建并运行一个空白窗口,则表明您的 WPF 开发环境已基本就绪。

1.4 创建项目与基本结构

现在,让我们开始创建我们的图书管理系统项目。

  1. 启动 Visual Studio
  2. 在欢迎界面选择 “创建新项目”
  3. 在“创建新项目”对话框中,从模板列表中选择 “WPF 应用程序” (如果您使用的是 .NET Core/5+,则选择 “WPF Application”)。您可以使用顶部的搜索框输入 “WPF” 来快速筛选。
  • 确保选择的是 C# 语言的 WPF 模板。
  1. 点击 “下一步”
  2. 配置新项目
  • 项目名称:输入 WPF-Books (或者您喜欢的其他名称)。
  • 位置:选择一个合适的文件夹来存放您的项目(例如 d:\00Learning\05C#\)。
  • 解决方案名称:可以保持与项目名称一致,或者自定义。
  • 框架:根据您的环境选择合适的 .NET Framework 版本或 .NET Core/5+ 版本。
  1. 点击 “创建”

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           # 解决方案文件

创建文件夹步骤

  1. 在 Visual Studio 的 “解决方案资源管理器” 中,右键点击 WPF-Books 项目。
  2. 选择 “添加” -> “新建文件夹”
  3. 依次创建 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.csUser.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 将处理用户登录逻辑。

通信机制:

  1. 数据绑定 (Data Binding):这是 WPF 的一项核心功能,也是 MVVM 实现的关键。View 的元素(如 TextBoxText 属性,ListBoxItemsSource 属性)可以绑定到 ViewModel 的公共属性。当 ViewModel 的属性值发生变化时,如果 ViewModel 实现了 INotifyPropertyChanged 接口并正确触发了 PropertyChanged 事件,绑定的 View 元素会自动更新。反之,如果绑定是双向的 (TwoWay),View 中用户输入导致的变化也会更新到 ViewModel 的属性。

  2. 命令 (Commands):WPF 中的命令机制 (ICommand接口) 允许将用户界面操作(如按钮点击)与 ViewModel 中的方法解耦。View 将控件的 Command 属性绑定到 ViewModel 中实现了 ICommand 接口的属性。ViewModel 中的命令对象封装了要执行的逻辑 (Execute 方法) 以及该逻辑是否可以执行的条件 (CanExecute 方法)。这使得 ViewModel 可以控制 UI 元素的启用/禁用状态,而无需直接操作 View。

  3. 属性更改通知 (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 类来描述一本图书的各种属性。

  1. 解决方案资源管理器 中,右键点击 Models 文件夹。

  2. 选择 “添加” -> “类…”

  3. 将类命名为 Book.cs,然后点击 “添加”

  4. 编辑 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)

接下来,我们需要一个用户模型来管理用户信息,主要用于登录和记录借阅者。

  1. 解决方案资源管理器 中,右键点击 Models 文件夹。

  2. 选择 “添加” -> “类…”

  3. 将类命名为 User.cs,然后点击 “添加”

  4. 编辑 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 项目中:

  1. Microsoft.EntityFrameworkCore.Sqlite: EF Core 的 SQLite 数据库提供程序。
  2. Microsoft.EntityFrameworkCore.Tools: EF Core 的命令行工具,用于数据库迁移等操作(主要在开发时使用)。

安装步骤

  • 通过 NuGet 包管理器 UI:
    1. 解决方案资源管理器 中,右键点击 WPF-Books 项目或 “依赖项” 节点。
    2. 选择 “管理 NuGet 程序包…”
    3. 在打开的 NuGet 包管理器窗口中,切换到 “浏览” 选项卡。
    4. 搜索 Microsoft.EntityFrameworkCore.Sqlite,选择它,然后点击 “安装”
    5. 同样地,搜索并安装 Microsoft.EntityFrameworkCore.Tools
  • 通过包管理器控制台:
    1. 打开 “工具” -> “NuGet 包管理器” -> “包管理器控制台”

    2. 在控制台中,确保 “默认项目” 下拉列表选择了 WPF-Books

    3. 执行以下命令:

      Install-Package Microsoft.EntityFrameworkCore.Sqlite
      Install-Package Microsoft.EntityFrameworkCore.Tools

3.4 创建数据库上下文 (AppDbContext.cs)

数据库上下文 (DbContext) 是 EF Core 与数据库交互的主要入口点。它代表了与数据库的一个会话,并允许我们查询和保存数据。我们需要创建一个继承自 DbContext 的类。

  1. (可选,但推荐) 在项目中创建一个名为 DataAccess 的文件夹,用于存放与数据访问相关的类。
  • 右键点击 WPF-Books 项目 -> “添加” -> “新建文件夹”,命名为 DataAccess
  1. DataAccess (或项目根目录,如果未创建该文件夹) 文件夹下,添加一个新类 AppDbContext.cs

    using Microsoft.EntityFrameworkCore;
    using WPF_Books.Models; // 引入模型命名空间
    using System.IO; // 用于 Path.Combine
    using System; // 用于 Environment

    namespace 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) 功能。它允许我们在数据库创建或迁移时,自动向表中插入初始数据。这对于提供一些默认用户、测试数据或基础配置非常有用。
      • 注意:种子数据中的密码 adminpassuser1pass 是明文,仅用于演示。在实际应用中,应存储安全的哈希值。

3.5 数据库初始化与迁移 (Migrations)

EF Core 使用“迁移 (Migrations)”来管理数据库模式的演变。当我们更改了模型(例如,添加新属性、修改数据类型、添加新实体)后,我们可以创建一个新的迁移,EF Core 会生成相应的代码来更新数据库模式以匹配模型。

首次创建数据库和模式

  1. 打开包管理器控制台:在 Visual Studio 中,选择 “工具” -> “NuGet 包管理器” -> “包管理器控制台”

  2. 添加迁移:在包管理器控制台中,确保默认项目是 WPF-Books,然后运行以下命令来创建第一个迁移。我们将迁移命名为 InitialCreate

    Add-Migration InitialCreate

执行此命令后,EF Core Tools 会检查您的 AppDbContext 和模型类,并生成一个新的迁移文件。这个文件通常位于项目中自动创建的 Migrations 文件夹下,其中包含了创建数据库表和应用种子数据的 C# 代码。

  1. 应用迁移到数据库:运行以下命令将迁移应用到数据库。如果数据库文件 (library.db) 尚不存在,EF Core 会创建它,并根据迁移文件中的指令创建表结构和插入种子数据。

    Update-Database

执行完毕后,您应该能在项目的输出目录(例如 bin\Debug\netX.X\)下找到 library.db 文件。您可以使用 SQLite 浏览器(如 DB Browser for SQLite)打开它,查看表结构和种子数据是否已正确创建。

后续模型更改和迁移

如果将来您修改了 Book.csUser.cs 模型(例如,添加了一个新属性),您需要重复以下步骤:

  1. 添加新的迁移

    Add-Migration GiveYourMigrationAName (例如:AddBookPublisher)

  2. 更新数据库

    Update-Database

通过这种方式,EF Core 迁移可以帮助我们以受控和版本化的方式管理数据库模式的变更。

至此,我们已经完成了数据模型的设计、数据库上下文的创建以及数据库的初始化。在下一章节中,我们将开始设计和实现 ViewModel。

章节四:ViewModel 设计与实现

ViewModel 是 MVVM 模式的核心,它充当 View 和 Model 之间的桥梁。在本章节中,我们将设计并实现应用程序所需的 ViewModel,包括一个基础的 ObservableObject 类用于属性变更通知,一个 RelayCommand 类用于处理用户操作,以及具体的 LoginViewModelBookViewModel

4.1 实现 ObservableObject (属性变更通知基类)

为了让数据绑定能够正确工作,当 ViewModel 中的属性发生变化时,需要通知 View 进行更新。这通常通过实现 INotifyPropertyChanged 接口来完成。我们可以创建一个基类 ObservableObject 来封装这个逻辑,使得其他 ViewModel 可以继承它。

  1. 解决方案资源管理器 中,右键点击 ViewModels 文件夹。

  2. 选择 “添加” -> “类…”

  3. 将类命名为 ObservableObject.cs,然后点击 “添加”

  4. 编辑 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值