练习 - 设置迁移
- 10 分钟
在本单元中,你将创建将映射到本地 SQLite 数据库中的表的 C# 实体类。 EF Core 迁移功能会通过这些实体生成表。
通过迁移,能够对数据库架构进行增量更新。
获取项目文件
请从获取项目文件开始。 你在获取项目文件的方式上有一些选择:
- 使用 GitHub Codespaces
- 克隆 GitHub 存储库
如果已安装兼容的容器运行时,也可以使用开发容器扩展,借助预安装的工具在容器中打开存储库。
使用 GitHub Codespaces
Codespace 是托管在云中的 IDE。 如果使用的是 GitHub Codespaces,请在浏览器中转到存储库。 选择“代码”,然后在 分支中创建新的 codespace。
克隆 GitHub 存储库
如果使用的不是 GitHub Codespaces,则可克隆项目 GitHub 存储库,然后在 Visual Studio Code 中以文件夹的形式打开文件。
打开命令终端,然后使用命令提示符从 GitHub 克隆项目:
git clone https://github.com/MicrosoftDocs/mslearn-persist-data-ef-core
转到 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 中, 注册到 ASP.NET Core 依赖项注入系统。
- Controllers/PizzaController.cs 是一个 值,它公开了 HTTP POST、GET、PUT 和 DELETE 谓词的终结点。 这些谓词在
PizzaService
上调用相应的 CRUD 方法。PizzaService
注入到PizzaController
构造函数中。 - Models 文件夹包含 和
PizzaService
使用的模型。PizzaController
- 实体模型 Pizza.cs、Topping.cs 和 Sauce.cs 具有以下关系:
- 一个披萨可能有一种或多种配料。
- 一种配料可用于一个或多个披萨。
- 一个披萨可能有一种酱汁,但一种酱汁可能用于许多披萨。
生成应用
若要在 Visual Studio Code 中生成应用,请执行以下操作:
右键单击“资源管理器”窗格中的 ContosoPizza 目录,然后选择“在集成终端中打开”。
此时会打开范围为 ContosoPizza 目录的终端窗格。
使用以下命令生成应用:
dotnet build
代码应生成,无警告或错误。
添加 NuGet 包和 EF Core 工具
本模块中使用的数据库引擎是 SQLite。 SQLite 是基于文件的轻型数据库引擎。 适合用于开发和测试,也适用于小规模生产部署。
注意
如前所述,EF Core 中的数据库提供程序可插入。 SQLite 适合在本模块使用,因为属轻型且跨平台的特性。 可以使用同一代码来处理不同的数据库引擎,例如 SQL Server 和 PostgreSQL。 你甚至可以在同一个应用中使用多个数据库引擎。
在开始之前,请添加所需的包:
在终端窗格中运行以下命令:
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
此命令将添加包含 EF Core SQLite 数据库提供程序及其所有依赖项的 NuGet 包,包括常见的 EF Core 服务。
接下来运行以下命令:
dotnet add package Microsoft.EntityFrameworkCore.Design
此命令添加 EF Core 工具所需的包。
若要完成此操作,请运行此命令:
dotnet tool install --global dotnet-ef
此命令将安装
dotnet ef
,这是用于创建迁移和基架的工具。提示
如果已安装
dotnet ef
,则可以通过运行dotnet tool update --global dotnet-ef
来对其进行更新。
搭建模型和 DbContext 的基架
现在请添加并配置 DbContext
实现。
DbContext
是一个网关,可以通过该网关与数据库进行交互。
右键单击 ContosoPizza 目录,然后添加名为 Data 的新文件夹。
在 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>
属性对应于要在数据库中创建的表。 - 表名称将匹配
DbSet<T>
类中的PizzaContext
属性名称。 根据需要,可以覆盖此行为。 - 实例化时,
PizzaContext
会公开Pizzas
、Toppings
和Sauces
属性。 对这些属性公开的集合所做的更改将传播到数据库。
- 构造函数接受类型为
在 Program.cs 中,将 替换为以下代码:
builder.Services.AddSqlite<PizzaContext>("Data Source=ContosoPizza.db");
前面的代码:
- 向 ASP.NET Core 依赖项注入系统注册
PizzaContext
。 - 指定
PizzaContext
使用 SQLite 数据库提供程序。 - 定义指向本地文件 ContosoPizza.db 的 SQLite 连接字符串。
注意
SQLite 使用本地数据库文件,因此可以对连接字符串进行硬编码。 对于 PostgreSQL 和 SQL Server 等网络数据库,应始终安全地存储连接字符串。 对于本地开发,请使用机密管理器。 对于生产部署,请考虑使用 Azure Key Vault 这样的服务。
- 向 ASP.NET Core 依赖项注入系统注册
同样在 Program.cs 中,将 替换为以下代码。
using ContosoPizza.Data;
该代码能解析上述步骤中的依赖关系。
保存所有更改。 GitHub Codespaces 会自动保存更改。
通过运行
dotnet build
来在终端中生成应用。 生成应成功完成,且没有任何警告或错误。
创建并运行迁移
接下来要创建用于创建初始数据库的迁移。
在范围限定为 ContosoPizza 项目文件夹的终端中运行以下命令,以生成用于创建数据库表的迁移:
dotnet ef migrations add InitialCreate --context PizzaContext
在上述命令中:
- 迁移命名为:InitialCreate。
-
--context
选项指定 ContosoPizza 项目中的类的名称,该名称派生自DbContext
。
Migrations 项目根中显示新的 ContosoPizza 目录。 该目录包含 <timestamp>_InitialCreate.cs 文件,该文件描述了要转换为数据定义语言 (DDL) 更改脚本的数据库更改。
运行以下命令,应用 InitialCreate 迁移:
dotnet ef database update --context PizzaContext
此命令应用迁移。 ContosoPizza.db 不存在,因此此命令在项目目录中创建迁移。
提示
所有平台都支持
dotnet ef
工具。 在 Windows 上的 Visual Studio 中,可以在集成的“程序包管理器控制台”窗口中使用Add-Migration
和Update-Database
PowerShell cmdlet。
检查数据库
EF Core 为应用创建了一个数据库。 接下来,让我们使用 SQLite 扩展了解数据库中的内容。
在“资源管理器”窗格中右键单击 ContosoPizza.db 文件,然后选择“打开数据库”。
“资源管理器”窗格中会显示一个 SQLite 资源管理器文件夹。
选择“SQLite 资源管理器”文件夹以展开节点及其所有子节点。 右键单击“ContosoPizza.db”,然后选择“显示表 'sqlite_master'”以查看迁移创建的完整数据库架构和约束。
- 已创建对应于各个实体的表。
- 表名取自
DbSet
上的PizzaContext
属性的名称。 - 已将名为
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]
特性对该属性进行批注或将其配置为OnModelCreating
的DbContext
方法中的键。
更改模型和更新数据库架构
Contoso Pizza 的经理向你提出了一些新要求,因此你需要更改实体模型。 在以下步骤中,你将使用映射特性(有时也称为“数据注释”)修改模型。
在 Models\Pizza.cs 中,进行以下更改:
- 为
using
添加System.ComponentModel.DataAnnotations
指令。 - 在
[Required]
属性之前添加一个Name
特性以将属性标记为必需。 - 在
[MaxLength(100)]
属性之前添加一个Name
特性以指定最大字符串长度 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; } }
- 为
在 Models\Sauce.cs 中,进行以下更改:
- 为
using
添加System.ComponentModel.DataAnnotations
指令。 - 在
[Required]
属性之前添加一个Name
特性以将属性标记为必需。 - 在
[MaxLength(100)]
属性之前添加一个Name
特性以指定最大字符串长度 100。 - 添加名为
bool
的IsVegan
属性。
更新后的 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; } }
- 为
在 Models\Topping.cs 中,进行以下更改:
为
using
和System.ComponentModel.DataAnnotations
添加System.Text.Json.Serialization
指令。在
[Required]
属性之前添加一个Name
特性以将属性标记为必需。在
[MaxLength(100)]
属性之前添加一个Name
特性以指定最大字符串长度 100。在
decimal
属性后直接添加名为Calories
的Name
属性。具有类型为
Pizzas
的ICollection<Pizza>?
属性。 此更改使Pizza
-Topping
成为多对多关系。将
[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; } }
保存所有更改并运行
dotnet build
。运行以下命令,生成用于创建数据库表的迁移:
dotnet ef migrations add ModelRevisions --context PizzaContext
此命令创建名为 ModelRevisions 的迁移。
注意
你会看到以下消息:操作已搭建基架,可能导致数据丢失。请查看迁移的准确性。 出现此消息是因为你将关系从
Pizza
更改为Topping
,从一对多变为多对多,这需要删除现有外键列。 因为数据库中还没有任何数据,所以此更改不会有问题。 但在一般情况下,最好在显示此警告时检查生成的迁移,以确保迁移不会删除或截断任何数据。运行以下命令,应用 ModelRevisions 迁移:
dotnet ef database update --context PizzaContext
在“Sqlite 资源管理器”文件夹的标题栏中,选择“刷新数据库”按钮。
在“Sqlite 资源管理器”窗格中右键单击 ContosoPizza.db。 选择“显示表 'sqlite_master'”以查看完整的数据库架构和约束。
重要
Sqlite 扩展会重复使用打开的“SQLite”选项卡。
- 已创建
PizzaTopping
联接表,用于表示披萨和配料之间的多对多关系。 - 已将新字段添加到
Toppings
和Sauces
。-
Calories
定义为text
列,因为 SQLite 没有匹配的decimal
类型。 - 同样,
IsVegan
定义为integer
列。 SQLite 没有定义bool
类型。 - 在这两种情况下,EF Core 管理转换。
-
- 每个表中的
Name
列都标记为not null
,但 SQLite 没有MaxLength
约束。
提示
EF Core 数据库提供程序会将模型架构映射到特定数据库的功能。 虽然 SQLite 没有为
MaxLength
实现相应的约束,但 SQL Server 和 PostgreSQL 这样的其他数据库会实现这一点。- 已创建
在“Sqlite 资源管理器”文件夹中右键单击 表,然后选择“显示表”。 该表包含应用于数据库的所有迁移的列表。 因为已运行两个迁移,所以有两个条目:一个条目对应于 InitialCreate 迁移,另一个条目对应于 ModelRevisions 迁移。
注意
本练习使用了映射属性(数据注释)将模型映射到数据库。 除了使用映射属性外,还可以选择使用 Fluent API 来配置模型。 这两种方法都有效,但一些开发人员会在两者间权衡选择。
你已使用迁移来定义和更新数据库架构。 在下一个单元中,你将完成 PizzaService
中处理数据的方法。