练习 - 设置迁移

已完成

在本单元中,你将创建将映射到本地 SQLite 数据库中的表的 C# 实体类。 EF Core 迁移功能会通过这些实体生成表。

通过迁移,能够对数据库架构进行增量更新。

获取项目文件

请从获取项目文件开始。 你在获取项目文件的方式上有一些选择:

  • 使用 GitHub Codespaces
  • 克隆 GitHub 存储库

如果已安装兼容的容器运行时,也可以使用开发容器扩展,借助预安装的工具在容器中打开存储库。

使用 GitHub Codespaces

Codespace 是托管在云中的 IDE。 如果使用的是 GitHub Codespaces,请在浏览器中转到存储库。 选择“代码”,然后在 main 分支中创建新的 codespace。

克隆 GitHub 存储库

如果使用的不是 GitHub Codespaces,则可克隆项目 GitHub 存储库,然后在 Visual Studio Code 中以文件夹的形式打开文件。

  1. 打开命令终端,然后使用命令提示符从 GitHub 克隆项目:

    git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
    
  2. 转到 mslearn-persist-data-ef-core 文件夹,然后在 Visual Studio Code 中打开项目:

    cd mslearn-persist-data-ef-core
    code .
    

查看代码

有了可以使用的项目文件后,接下来让我们查看项目中的内容,并查看代码。

  • ASP.NET Core Web API 项目位于 ContosoPizza 目录中。 我们在此模块中引用的文件路径是相对于 ContosoPizza 目录的
  • Services/PizzaService.cs 是定义创建、读取、更新和删除 (CRUD) 方法的服务类。 所有方法当前均引发 System.NotImplementedException
  • 在 Program.cs 中,PizzaService 注册到 ASP.NET Core 依赖项注入系统。
  • Controllers/PizzaController.cs 是一个 ApiController 值,它公开了 HTTP POST、GET、PUT 和 DELETE 谓词的终结点。 这些谓词在 PizzaService 上调用相应的 CRUD 方法。 PizzaService 注入到 PizzaController 构造函数中。
  • Models 文件夹包含 PizzaServicePizzaController 使用的模型。
  • 实体模型 Pizza.cs、Topping.cs 和 Sauce.cs 具有以下关系:
    • 一个披萨可能有一种或多种配料。
    • 一种配料可用于一个或多个披萨。
    • 一个披萨可能有一种酱汁,但一种酱汁可能用于许多披萨。

生成应用

若要在 Visual Studio Code 中生成应用,请执行以下操作:

  1. 右键单击“资源管理器”窗格中的 ContosoPizza 目录,然后选择“在集成终端中打开”。

    此时会打开范围为 ContosoPizza 目录的终端窗格。

  2. 使用以下命令生成应用:

    dotnet build
    

    代码应生成,无警告或错误。

添加 NuGet 包和 EF Core 工具

本模块中使用的数据库引擎是 SQLite。 SQLite 是基于文件的轻型数据库引擎。 适合用于开发和测试,也适用于小规模生产部署。

注意

如前所述,EF Core 中的数据库提供程序可插入。 SQLite 适合在本模块使用,因为属轻型且跨平台的特性。 可以使用同一代码来处理不同的数据库引擎,例如 SQL Server 和 PostgreSQL。 你甚至可以在同一个应用中使用多个数据库引擎。

在开始之前,请添加所需的包:

  1. 在终端窗格中运行以下命令:

    dotnet add package Microsoft.EntityFrameworkCore.Sqlite
    

    此命令将添加包含 EF Core SQLite 数据库提供程序及其所有依赖项的 NuGet 包,包括常见的 EF Core 服务。

  2. 接下来运行以下命令:

    dotnet add package Microsoft.EntityFrameworkCore.Design
    

    此命令添加 EF Core 工具所需的包。

  3. 若要完成此操作,请运行此命令:

    dotnet tool install --global dotnet-ef
    

    此命令将安装 dotnet ef,这是用于创建迁移和基架的工具。

    提示

    如果已安装 dotnet ef,则可以通过运行 dotnet tool update --global dotnet-ef 来对其进行更新。

搭建模型和 DbContext 的基架

现在请添加并配置 DbContext 实现。 DbContext 是一个网关,可以通过该网关与数据库进行交互。

  1. 右键单击 ContosoPizza 目录,然后添加名为 Data 的新文件夹。

  2. 在 Data 文件夹中创建一个名为 PizzaContext.cs 的新文件。 将以下代码添加到空文件中:

    using Microsoft.EntityFrameworkCore;
    using ContosoPizza.Models;
    
    namespace ContosoPizza.Data;
    
    public class PizzaContext : DbContext
    {
        public PizzaContext (DbContextOptions<PizzaContext> options)
            : base(options)
        {
        }
    
        public DbSet<Pizza> Pizzas => Set<Pizza>();
        public DbSet<Topping> Toppings => Set<Topping>();
        public DbSet<Sauce> Sauces => Set<Sauce>();
    }
    

    在上述代码中:

    • 构造函数接受类型为 DbContextOptions<PizzaContext> 的参数。 该构造让外部代码能传入配置,因此可以在测试和生产代码之间共享相同的 DbContext,甚至可以与不同的提供程序一起使用。
    • DbSet<T> 属性对应于要在数据库中创建的表。
    • 表名称将匹配 PizzaContext 类中的 DbSet<T> 属性名称。 根据需要,可以覆盖此行为。
    • 实例化时,PizzaContext 会公开 PizzasToppingsSauces 属性。 对这些属性公开的集合所做的更改将传播到数据库。
  3. 在 Program.cs 中,将 // Add the PizzaContext 替换为以下代码:

    builder.Services.AddSqlite<PizzaContext>("Data Source=ContosoPizza.db");
    

    前面的代码:

    • 向 ASP.NET Core 依赖项注入系统注册 PizzaContext
    • 指定 PizzaContext 使用 SQLite 数据库提供程序。
    • 定义指向本地文件 ContosoPizza.db 的 SQLite 连接字符串。

    注意

    SQLite 使用本地数据库文件,因此可以对连接字符串进行硬编码。 对于 PostgreSQL 和 SQL Server 等网络数据库,应始终安全地存储连接字符串。 对于本地开发,请使用机密管理器。 对于生产部署,请考虑使用 Azure Key Vault 这样的服务。

  4. 同样在 Program.cs 中,将 // Additional using declarations 替换为以下代码。

    using ContosoPizza.Data;
    

    该代码能解析上述步骤中的依赖关系。

  5. 保存所有更改。 GitHub Codespaces 会自动保存更改。

  6. 通过运行 dotnet build 来在终端中生成应用。 生成应成功完成,且没有任何警告或错误。

创建并运行迁移

接下来要创建用于创建初始数据库的迁移。

  1. 在范围限定为 ContosoPizza 项目文件夹的终端中运行以下命令,以生成用于创建数据库表的迁移:

    dotnet ef migrations add InitialCreate --context PizzaContext
    

    在上述命令中:

    • 迁移命名为:InitialCreate
    • --context 选项指定 ContosoPizza 项目中的类的名称,该名称派生自 DbContext

    ContosoPizza 项目根中显示新的 Migrations 目录。 该目录包含 <timestamp>_InitialCreate.cs 文件,该文件描述了要转换为数据定义语言 (DDL) 更改脚本的数据库更改。

  2. 运行以下命令,应用 InitialCreate 迁移:

    dotnet ef database update --context PizzaContext
    

    此命令应用迁移。 ContosoPizza.db 不存在,因此此命令在项目目录中创建迁移。

    提示

    所有平台都支持 dotnet ef 工具。 在 Windows 上的 Visual Studio 中,可以在集成的“程序包管理器控制台”窗口中使用 Add-MigrationUpdate-Database PowerShell cmdlet。

检查数据库

EF Core 为应用创建了一个数据库。 接下来,让我们使用 SQLite 扩展了解数据库中的内容。

  1. 在“资源管理器”窗格中右键单击 ContosoPizza.db 文件,然后选择“打开数据库”。

    Visual Studio Code 资源管理器窗格中“打开数据库”菜单选项的屏幕截图。

    “资源管理器”窗格中会显示一个 SQLite 资源管理器文件夹

    屏幕截图显示“资源管理器”窗格中的 SQLite 资源管理器文件夹。

  2. 选择“SQLite 资源管理器”文件夹以展开节点及其所有子节点。 右键单击“ContosoPizza.db”,然后选择“显示表 'sqlite_master'”以查看迁移创建的完整数据库架构和约束。

    资源管理器窗格上展开的“SQLite 资源管理器”文件夹的屏幕截图。

    • 已创建对应于各个实体的表。
    • 表名取自 PizzaContext 上的 DbSet 属性的名称。
    • 已将名为 Id 的属性推断为自动递增的主键字段。
    • EF Core 的主键和外键约束命名约定分别为 PK_<primary key property>FK_<dependent entity>_<principal entity>_<foreign key property><dependent entity><principal entity> 占位符对应于实体类名称。

    备注

    与 ASP.NET Core MVC 类似,EF Core 采用的是“约定优于配置”方法。 EF Core 约定通过推断开发者的意图来缩短开发时间。 例如,EF Core 将名为 Id<entity name>Id 的属性推断为生成的表的主键。 如果选择不采用命名约定,则必须使用 [Key] 特性对该属性进行批注或将其配置为 DbContextOnModelCreating 方法中的键。

更改模型和更新数据库架构

Contoso Pizza 的经理向你提出了一些新要求,因此你需要更改实体模型。 在以下步骤中,你将使用映射特性(有时也称为“数据注释”)修改模型。

  1. 在 Models\Pizza.cs 中,进行以下更改:

    1. System.ComponentModel.DataAnnotations 添加 using 指令。
    2. Name 属性之前添加一个 [Required] 特性以将属性标记为必需。
    3. Name 属性之前添加一个 [MaxLength(100)] 特性以指定最大字符串长度 100。

    更新后的 Pizza.cs 文件应如以下代码所示:

    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Pizza
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public Sauce? Sauce { get; set; }
    
        public ICollection<Topping>? Toppings { get; set; }
    }
    
  2. 在 Models\Sauce.cs 中,进行以下更改:

    1. System.ComponentModel.DataAnnotations 添加 using 指令。
    2. Name 属性之前添加一个 [Required] 特性以将属性标记为必需。
    3. Name 属性之前添加一个 [MaxLength(100)] 特性以指定最大字符串长度 100。
    4. 添加名为 IsVeganbool 属性。

    更新后的 Sauce.cs 文件应如以下代码所示:

    using System.ComponentModel.DataAnnotations;
    
    namespace ContosoPizza.Models;
    
    public class Sauce
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public bool IsVegan { get; set; }
    }
    
  3. 在 Models\Topping.cs 中,进行以下更改:

    1. System.ComponentModel.DataAnnotationsSystem.Text.Json.Serialization 添加 using 指令。

    2. Name 属性之前添加一个 [Required] 特性以将属性标记为必需。

    3. Name 属性之前添加一个 [MaxLength(100)] 特性以指定最大字符串长度 100。

    4. Name 属性后直接添加名为 Caloriesdecimal 属性。

    5. 具有类型为 ICollection<Pizza>?Pizzas 属性。 此更改使 Pizza-Topping 成为多对多关系。

    6. [JsonIgnore] 特性添加到 Pizzas 属性。

      重要

      此特性可防止 Topping 实体在 Web API 代码将响应序列化为 JSON 时包含 Pizzas 属性。 如果不进行此更改,配料的序列化集合将包括使用配料的每个披萨的集合。 该集合中的每个披萨都将包含一个配料集合,每种配料又将包含一个披萨集合。 这种类型的无限循环称为“循环引用”,不能序列化。

    更新后的 Topping.cs 文件应如以下代码所示:

    using System.ComponentModel.DataAnnotations;
    using System.Text.Json.Serialization;
    
    namespace ContosoPizza.Models;
    
    public class Topping
    {
        public int Id { get; set; }
    
        [Required]
        [MaxLength(100)]
        public string? Name { get; set; }
    
        public decimal Calories { get; set; }
    
        [JsonIgnore]
        public ICollection<Pizza>? Pizzas { get; set; }
    }
    
  4. 保存所有更改并运行 dotnet build

  5. 运行以下命令,生成用于创建数据库表的迁移:

    dotnet ef migrations add ModelRevisions --context PizzaContext
    

    此命令创建名为 ModelRevisions 的迁移。

    注意

    你会看到以下消息:操作已搭建基架,可能导致数据丢失。请查看迁移的准确性。 出现此消息是因为你将关系从 Pizza 更改为 Topping,从一对多变为多对多,这需要删除现有外键列。 因为数据库中还没有任何数据,所以此更改不会有问题。 但在一般情况下,最好在显示此警告时检查生成的迁移,以确保迁移不会删除或截断任何数据。

  6. 运行以下命令,应用 ModelRevisions 迁移:

    dotnet ef database update --context PizzaContext
    
  7. 在“Sqlite 资源管理器”文件夹的标题栏中,选择“刷新数据库”按钮。

    Sqlite 资源管理器标题栏中“刷新数据库”按钮的屏幕截图。

  8. 在“Sqlite 资源管理器”窗格中右键单击 ContosoPizza.db。 选择“显示表 'sqlite_master'”以查看完整的数据库架构和约束。

    重要

    Sqlite 扩展会重复使用打开的“SQLite”选项卡。

    • 已创建 PizzaTopping 联接表,用于表示披萨和配料之间的多对多关系。
    • 已将新字段添加到 ToppingsSauces
      • Calories 定义为 text 列,因为 SQLite 没有匹配的 decimal 类型。
      • 同样,IsVegan 定义为 integer 列。 SQLite 没有定义 bool 类型。
      • 在这两种情况下,EF Core 管理转换。
    • 每个表中的 Name 列都标记为 not null,但 SQLite 没有 MaxLength 约束。

    提示

    EF Core 数据库提供程序会将模型架构映射到特定数据库的功能。 虽然 SQLite 没有为 MaxLength 实现相应的约束,但 SQL Server 和 PostgreSQL 这样的其他数据库会实现这一点。

  9. 在“Sqlite 资源管理器”文件夹中右键单击 _EFMigrationsHistory 表,然后选择“显示表”。 该表包含应用于数据库的所有迁移的列表。 因为已运行两个迁移,所以有两个条目:一个条目对应于 InitialCreate 迁移,另一个条目对应于 ModelRevisions 迁移

注意

本练习使用了映射属性(数据注释)将模型映射到数据库。 除了使用映射属性外,还可以选择使用 Fluent API 来配置模型。 这两种方法都有效,但一些开发人员会在两者间权衡选择。

你已使用迁移来定义和更新数据库架构。 在下一个单元中,你将完成 PizzaService 中处理数据的方法。

知识检查

1.

在实体类中,主键的属性命名约定是什么?