学习 Entity Framework( 第 4 天): 理解 Entity Framework Core 和 EF Core 中的 Code First Migrations





5.00/5 (18投票s)
Entity Framework Core、Code First 迁移和 EF Core 中的数据注解
目录
简介
在之前的文章(数据访问方法、Code First 方法和WebAPI)中,我们学习了大量关于 Entity Framework 及其实际实现的内容。本文旨在解释 Entity Framework Core 的概念。我们将逐步探讨 Entity Framework Core 的主题。我们将探索使用 EF Core 的 Code First 方法,并学习数据注解。我们还将涵盖本文中的 Code First 迁移以及如何填充数据库的理解。我将使用 Visual Studio 2017 进行教程。对于数据库,我们将使用 SQL Server。如果没有安装 SQL Server,可以使用本地数据库。
系列信息
我们将通过五篇文章系列详细学习 Entity Framework 的主题。除了最后一篇将涵盖 Entity Framework 的理论、历史和用途外,所有文章都将以教程形式呈现。以下是本系列的主题:
- 学习 Entity Framework(第 1 天):.NET 中 Entity Framework 的数据访问方法
- 学习 Entity Framework(第 2 天):.NET 中 Entity Framework 的 Code First 迁移
- 学习 Entity Framework(第 3 天):.NET 中 ASP.NET WebAPI 2.0 的 Code First 迁移
- 学习 Entity Framework(
第 4 天): 理解 Entity Framework Core 和 EF Core 中的 Code First Migrations - 学习 Entity Framework(第 5 天):Entity Framework(理论篇)
Entity Framework Core
EF Core 也是一个 ORM,但它不是 Entity Framework 6 的升级,也不应被视为升级。相反,Entity Framework Core 是 Entity Framework 的一个轻量级、可扩展且跨平台的版本。因此,它带来了许多新功能和对 Entity Framework 6 的改进,目前版本为 2。对于 ASP.NET Core 1,应使用版本 1,对于 ASP.NET Core 2,建议使用版本 2。Entity Framework 不支持 Entity Framework 6 的所有功能。Entity Framework Core 推荐用于不需要 Entity Framework 6 提供的大量功能的新应用程序,或用于面向 .NET Core 的应用程序。它带有一组提供程序,可以与各种数据库一起使用。当然,它可以与 Microsoft SQL Server 一起使用,也可以与 SQLite、Postgres、SQL Server Compact Edition、MySQL 和 IBM DB2 一起使用。还有一个用于测试目的的内存提供程序。EF Core 既可以用于 Code First 方法(将从代码创建数据库),也可以用于 Database First 方法(如果数据库已经存在,则很方便)。以下部分将展示 Entity Framework Core 在控制台应用程序中的基本实现,当然,它也可以用于任何需要与数据库交互的 .NET 应用程序,如 MVC、Web API、Windows 应用程序。我们将从将 EF Core 添加到我们的项目开始。我们还将研究迁移,这是一种在底层数据存储的不同版本之间迁移的方法。我们还将了解如何使用代码将数据填充到数据库中。让我们通过介绍 Entity Framework Core 来深入探讨。
EF Core 的应用场景
Entity Framework Core 运行在 .NET Core 上,而 .NET Core 可以在很多地方运行。它运行在完整的 .NET Framework 中,即任何 4.5.1 或更高版本,而 .NET Core 本身可以运行在 CoreCLR(即运行时)上,CoreCLR 不仅可以在 Windows 上本机运行,还可以在 Mac 和 Linux 上运行。你可以在通用 Windows 平台或 UWP for Windows 10 上使用 EF Core,因此它可以在任何可以运行 Windows 10 的设备或 PC 上运行。尽管如此,这并不一定意味着你应该在所有这些场景中使用 Entity Framework Core,这是一个非常重要的要记住的点,因为 Entity Framework Core 是一套全新的 API。它没有你可能习惯的 Entity Framework 6 的所有功能,虽然其中一些功能将在 EF Core 的未来版本中提供,但有一些功能永远不会成为 Entity Framework Core 的一部分,因此了解这一点很重要,并且你可能不想用 Entity Framework Core 开始每一个新项目。请确保 Entity Framework Core 具有你需要的功能。如果你想面向跨平台或 UWP,你必须使用 Entity Framework Core,但对于 .NET 应用程序,你仍然可以使用 Entity Framework 6,事实上,对于肯定会留在 Windows 上的 ASP.NET Core 应用程序。换句话说,在 Windows Server 上,你仍然可以使用完整的 .NET 和 Entity Framework 6 构建独立的 API,只需让你的 ASP.NET Core 应用程序与基于 Entity Framework 6 的库进行通信。
使用 Entity Framework Core 的 Code First 方法
Entity Framework 6 和 Entity Framework Core 中的数据访问方法是相同的,除了 EF Core 提供的一些新功能。伴随相关包而来的是一些微小的差异和实现技术。让我们逐步了解 EF Core 在 Code First 方法中的实际应用。在实际实现过程中,我们将涵盖更多主题,例如数据注解和其他迁移技术。
添加实体
- 如前所述,对于 Code First 方法,应用程序应具有最终将生成数据库表的实体。因此,创建一个控制台应用程序,以 .NET Framework 4.6.2 为基础。将应用程序命名为 `EmployeeManagement`,解决方案命名为 `EFCore`。
- 添加两个实体类,一个名为 `Department`,另一个名为 `Employee`。`department` 和 `employee` 之间将存在一对多关系。例如,一个 `department` 可以拥有多个 `employee`,而一个 `employee` 将与任何一个 `department` 关联。
部门
代码
namespace EmployeeManagement { public class Department { public int DepartmentId { get; set; } public string DepartmentName { get; set; } public string DepartmentDescription { get; set; } } }
员工
代码
namespace EmployeeManagement { public class Employee { public int EmployeeId { get; set; } public string EmployeeName { get; set; } public int DepartmentId { get; set; } public virtual Department Departments { get; set; } } }
- 部门实体包含部门名称和描述属性,而 `employee` 实体包含 `EmployeeId`、`Name` 和 `DepartmentId` 属性,后者作为 `department` 的导航属性。在 `Department` 实体中添加一个 `Employees` 属性,表示部门中的员工集合,因为一个 `department` 可以拥有许多 `employee`。同样,在 `Employee` 类中添加一个 `Department` 类型的属性,该属性返回单个 `department` 而不是列表。
public virtual ICollection<Employee> Employees { get; set; }
数据注解
- 通过为类提供名为 `Id` 的 ID,此字段将自动被视为主键。例如,`DepartmentId`,即类名后跟 `ID` 也可以。如果我们将它命名不同,约定就不适用。但是我们可以应用 `System.ComponentModel.DataAnnotations` 中的键数据注解。就我个人而言,我仍然喜欢应用键注解,即使约定会确保此属性被视为主键,我觉得它使 `Entity` 类一目了然。但是,嗯,我对许多基于约定的方法也有同样的抱怨,所以这完全取决于你。以下是为要设置为主键的属性添加 `[Key]` 的方法。
- 同样,为 `EmployeeId` 添加键。例如,`Employee` 实体的主键。
- 另一件重要的事情是 ID 主键的生成。根据约定,整数或 GUID 数据类型的主键将在添加时自动生成其值。换句话说,我们的 ID 将是一个自增列。为了明确说明这一点,我们可以使用另一个注解,即 `System.ComponentModel.DataAnnotations.SchemaNamespace` 中的数据库生成注解。它有三个可能的值:
null
表示不生成,identity
表示添加时生成,以及computed
表示添加或更新时生成。
我们需要 `identity` 选项。添加 `Employee` 时将生成一个新键。此值如何生成取决于所使用的数据库提供程序。数据库提供程序可能会自动为某些属性类型设置值生成,而其他属性类型则需要你手动设置值的生成方式。在我们的案例中,我们将使用 SQL Server。所以我们准备就绪了。一个新的整数主键将自动生成,无需进一步设置。
- 我们希望表示 `Department` 和 `Employee` 之间的关系。如果我们回顾 `Department` 实体,我们已经定义了一个 `Employee` 集合,但我们希望从一个兴趣点导航到父部门的对象图。因此,我们需要一个属性来引用该父部门。我们需要声明外键属性是什么。同样,这里既有基于约定的方法,也有显式方法。根据约定,当在类型上发现导航属性时,将创建关系。如果导航属性指向的类型不能被当前数据库提供程序映射为标量类型,则该属性被视为导航属性。因此,如果我们添加一个 `Department` 类型的 `Department` 属性,这将被称为导航属性,并且将创建关系。通过约定发现的关系将始终指向主实体的主键。在这种情况下,它是 `Department` 的 `ID`。这将是我们的外键。不要求在 `dependent` 类上显式定义此外键属性。而 `dependent` 类,嗯,那就是我们的 `Employee` 类。但是建议这样做,所以我们添加一个。这就是基于约定的方法。如果我们不想遵循基于约定的方法,即外键将根据导航属性的类名后跟 `id` 命名,在我们的例子中是 `DepartmentId`,我们可以再次使用注解来实现。来自 `System.ComponentModel.DataAnnotations.Schema` 命名空间的外键注解。
- 实体类属性没有任何关于强制性或最大长度的数据注解。如果我们将实体类保持原样,我们的数据库列将允许应为非 `null` 的字段为 `null`。并且我们将使用 `max` 和 `varchar` 长度,而不是特定的最大大小。最好确保这些字段限制在最低可能级别应用。因此,在我们的案例中,那就是数据库本身。这确保了最佳的完整性。所以让我们应用这些属性。对于 `Employee`,`EmployeeName` 是必需的,`maxLength` 为 `50`。对于 `Department` 实体,我们也需要这些。我们将 `Name` 设置为必需,最大长度为 `50`。
代码
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace EmployeeManagement
{
public class Employee
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int EmployeeId { get; set; }
[Required]
[MaxLength(50)]
public string EmployeeName { get; set; }
public int DepartmentId { get; set; }
[ForeignKey("DepartmentId")]
public virtual Department Department { get; set; }
}
}
添加 DB Context
在本节中,我们将创建一个上下文来与我们的数据库交互,该上下文表示与数据库的一个会话,它可以用于查询和保存我们实体的实例。我们的实体类只是普通的类。我们不需要任何额外的依赖项来创建它们,但 `DBContext` 是 Entity Framework Core 的一部分。我们还需要一个提供程序。在我们的案例中,我们将使用 SQL Server 提供程序,这样我们就可以连接到 `LocalDB` 实例。
- 让我们打开 NuGet 对话框。右键单击项目并选择“管理 NuGet 包…”选项。
- 我们正在寻找 `Microsoft.EntityFrameworkCore.SqlServer` 包。如果安装它,Entity Framework Core 依赖项也将被添加,这样我们就拥有了目前所需的一切。
- 选择最新的稳定版本并点击安装。
- 接受许可协议。
正如你现在可以猜到的那样,当你在 ASP.NET Core 2 上并引用了 `Microsoft.AspNetCore.All` 包时,不需要执行此操作。该包包含了 Entity Framework Core 所需的引用。
- 现在让我们添加一个新类,`EmployeeManagementContext`。
- 让该类继承 `DBContext`。`DBContext` 可以在 `Microsoft.EntityFrameworkCore` 命名空间中找到。大型应用程序通常使用多个上下文。例如,如果我们要向应用程序添加某种报告模块,它将适合单独的上下文。无需将映射到数据库表的所有实体都放在同一个上下文中。多个上下文可以处理同一个数据库。在我们的案例中,我们只有两个实体,所以一个上下文就足够了。
- 在此上下文中,我们现在需要为我们的实体定义 `DbSet`。这样的 `DbSet` 可以用于查询和保存其实体类型的实例。针对 `DbSet` 的 LINQ 查询将被转换为针对数据库的查询。为我们拥有的每个实体类添加两个属性,返回实体的 `DbSet`。
代码
using Microsoft.EntityFrameworkCore; namespace EmployeeManagement { public class EmployeeManagementContext : DbContext { public DbSet<Employee> Employees { get; set; } public DbSet<Department> Departments { get; set; } } }
- 我们如何告知上下文它需要关联哪个数据库?嗯,这通过连接字符串实现。我们需要将此连接字符串提供给我们的 `DBContext`。换句话说,我们需要配置此 `DBContext`。这主要有两种方法。让我们再次打开 `EmployeeManagementContext`。第一种方法是通过重写 `DBContext` 上的 `OnConfigure` 方法。这有一个 `optionsBuilder` 作为参数。`optionsBuilder` 为我们提供了一个方法——`UseSqlServer`。这告诉 `DBContext` 它正在用于连接到 SQL Server 数据库,并且我们可以在这里提供连接字符串。这就是一种方法。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer("connectionstring"); base.OnConfiguring(optionsBuilder); }
- 但是让我们看看另一种方法——通过构造函数。所以,让我们注释掉 `OnConfiguring`,并查看 `DBContext` 的定义。`DBContext` 公开了一个接受 `DBContext` 选项的构造函数。
- 所以,让我们添加一个调用此构造函数重载的构造函数。这允许我们做的事情,也是在重写 `OnConfigure` 方法时不可能做到的,是我们可以在注册 `DBContext` 时提供选项。这是一种更合乎逻辑的方法。
public EmployeeManagementContext (DbContextOptions<EmployeeManagementContext> options) : base (options) { }
- 现在,为了获取此上下文类的实例,让我们添加一个名为 `Initialize` 的新类,并添加一个负责返回上下文实例的 `static` 方法。这些选项上的 `GetContext()` 方法重载,所以我们现在可以使用 `UseSqlServer`。它来自 `Microsoft.EntityFrameworkCore` 命名空间,所以让我们添加该 `using` 语句。在此方法中,我们可以传入连接字符串。所以,让我们现在添加一个变量来保存此连接字符串。下一个合乎逻辑的问题是,该连接字符串会是什么样子?我们将使用 local DB,因为它随 Visual Studio 自动安装。但是如果你的网络中有完整的 SQL Server 安装,它也能工作。只需确保相应地更改连接字符串。名称 `(localdb)\MSSQLLocalDB` 是默认实例名称,但根据你在安装时输入的内容,它在你的计算机上可能会有所不同。所以,如果你不确定,请查看 SQL Server 对象资源管理器窗口。如果你在计算机上看不到它,可以在“视图”菜单项下找到它。
public class Initialize { public static EmployeeManagementContext GetContext() { var connectionString = @"Server=(localdb)\mssqllocaldb; Database=EmployeeManagementDB;Trusted_Connection=True;"; DbContextOptionsBuilder<EmployeeManagementContext> options = new DbContextOptionsBuilder<EmployeeManagementContext>(); options.UseSqlServer(connectionString); return new EmployeeManagementContext(options.Options); } }
- 调用 `GetContext()` 方法,在 Program.cs 中创建上下文类的实例。理想情况下,当我们运行应用程序并创建上下文类实例时,数据库应该已在 local db 中准备就绪。
代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EmployeeManagement { class Program { static void Main(string[] args) { var context = Initialize.GetContext(); context.EnsureSeedDataForContext(); } } }
- 这是一种针对新数据库的 Code First 方法,因此如果数据库尚不存在,则应该生成它。让我们再次打开 `EmployeeManagementContext` 以确保这种情况发生。对于通过依赖注入请求实例时将使用的构造函数,我们在数据库对象上调用 `EnsureCreated()`。此数据库是一个在 `DBContext` 上定义的对象。如果数据库已经存在,则不会发生任何事情,但如果不存在,则此调用会确保它被有效创建。
上下文构造函数
public EmployeeManagementContext (DbContextOptions<EmployeeManagementContext> options) : base (options){ Database.Migrate(); }
- 运行应用程序。一旦应用程序运行且 Program.cs 的 `main` 方法执行 `GetContext()` 方法,让我们再次打开 SQL 对象资源管理器窗口。让我们从 `MSSQLLocalDB` 实例刷新数据库列表。看起来我们的 `EmployeeManagementDB` 数据就在那里。让我们看看表。显然,已经创建了两个表,一个 `Departments` 表和一个 `Employee` 表,这是我们实体的复数形式名称。
- Departments 表有一个主键 `DepartmentId`,一个最大长度为 `50` 的 `DepartmentName`,它不能为 `null`。如果你查看 `Department` 实体,我们会看到列定义与我们 `Department` 实体上字段的定义匹配。让我们看看 `Employees` 表。它有一个 `DepartmentId`,这是一个外键,它有一个 `EmployeeName` 字段,它是必需的,因此不能为 `null`,最大长度为 `50`。所以这与我们的 `Employee` 实体匹配。我们应用于实体类属性的属性因此被考虑在内。到目前为止,一切顺利。但这只是一种做法。
如果我们这样工作,我们通过调用 `Database.EnsureCreated()` 确保数据库被创建。但是如果我们这样做,嗯,我们忘记了一些东西。就像代码演变一样,数据库也会演变。让我们研究一下迁移,看看我们如何改进我们目前所做的工作以及如何处理不断演变的数据库。
EF Core 中的 Code First 迁移
就像我们的代码会演变一样,数据库也会演变。一段时间后可能会添加新表,现有表可能会被删除或修改。迁移允许我们提供代码将数据库从一个版本更改为另一个版本。它们是几乎所有应用程序的重要组成部分,所以让我们深入研究一下。我们将要做的是,我们将使用迁移来创建初始数据库版本,即版本 1。因此,我们将用这种新的更好的方法替换我们在之前的演示中所做的工作。原因是,通过这样做,我们将拥有从完全没有数据库开始的代码,而不是必须提供一个已经存在的数据库。然后,我们将添加另一个迁移以迁移到新版本,即版本 2。为了实现这样的功能,我们首先需要创建数据库的初始快照。在 Entity Framework Core 世界中,这是通过工具实现的,所以我们必须首先添加这些工具。这些工具本质上只是另一组依赖项,它们添加了我们可以执行的命令。
- 让我们添加 `Microsoft.EntityFrameworkCore.Tools` 包。
- 然后,我们必须创建数据库和架构的初始快照或迁移。为此,我们必须能够执行我们刚刚启用的命令之一。执行这些命令,嗯,你可以在包管理器控制台中完成。如果你当前看不到它,可以通过“工具”、“NuGet 包管理器”、“包管理器控制台”获取。
- 我们正在寻找的命令是 Add-Migration 命令。它需要一个我们将要添加的迁移的名称。所以,假设我们想将其命名为 `EmployeeManagementInitialMigration`。
- 它给我们一个错误,说它无法创建 `EmployeeManagementContext` 类型的对象,并要求我们添加 `IDesignTimeContextFactory` 的实现。所以,让我们创建一个名为 `DesignTimeContextFactory` 的新类,它继承自我们上下文类的 `IDesignTimeDbContextFactory`,并添加 `CreateDbContext` 方法,该方法创建 `optionsBuilder` 并返回带这些选项作为参数的上下文类实例。
代码
using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Design; using Microsoft.Extensions.Configuration; namespace EmployeeManagement { public class DesignTimeDbContextFactory : IDesignTimeDbContextFactory<EmployeeManagementContext> { public EmployeeManagementContext CreateDbContext(string[] args) { var optionsBuilder = new DbContextOptionsBuilder<EmployeeManagementContext>(); optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb; Database=EmployeeManagementDB;Trusted_Connection=True;"); return new EmployeeManagementContext(optionsBuilder.Options); } } }
- 现在运行 Add-Migration 命令,即 `Add-Migration EmployeeManagementInitialMigration`
如果我们现在看我们的解决方案,我们会看到有一个新的 Migrations 文件夹。它包含两个文件。其中一个是我们当前上下文模型的快照。
我们来看看。
- 这包含了我们通过实体定义的当前模型,包括我们提供的数据注解。我们可以找到我们的 `Department` 实体和 `Employee` 实体。在文件末尾,还有 `Department` 和 `Employee` 之间的关系。
- 我们看到的第二个文件是 `EmployeeManagement InitialMigration`。这是我们刚刚给迁移命名的名称。它包含了迁移生成器构建此版本数据库所需的代码,包括 Up(从当前版本到新版本)和 Down(从此版本到上一个版本)。如果我们查看 Up,我们会看到两个 `CreateTable` 语句和一个 `CreateIndex` 语句。这意味着它从完全没有数据库开始,并且此迁移包含了构建初始数据库的代码。如果我们查看 Down,我们会看到为了得到一个空数据库应该发生什么——两个 `DropTable` 语句。
如果添加新的迁移,将创建像这样的新文件,通过按顺序执行它们,我们的数据库可以与我们的代码一起演进。顺便说一句,你不需要运行 **Add-Migration** 命令来生成这些文件。我们可以手动编写它们。对于一两个或三个表来说,这可能仍然可行。但这绝对不是你想要为大型数据库做的事情。所以,这些工具非常有用。到目前为止,一切顺利。
- 我们还有一件事要做。我们必须确保迁移有效地应用于我们的数据库。为此还有另一个命令。它叫做 update-database 命令。如果我们执行此命令,迁移将应用于我们当前的数据库。我将向你展示如何从代码中执行此操作,而不是从命令中执行。
- 让我们再次打开上下文。我们可以将 `Database.EnsureCreated()` 替换为 `Database.Migrate()`。这将执行迁移,如果还没有数据库,它将创建数据库。这就是我们需要做的所有事情。但正如所说,我们正在替换我们在上一段中做的工作,因为,嗯,大多数应用程序确实需要迁移。对于这些应用程序,如果你有机会,最好从没有数据库开始。
public EmployeeManagementContext (DbContextOptions<EmployeeManagementContext> options) : base (options) { Database.Migrate(); }
- 所以,我们要做的是先删除当前数据库。如果我们不这样做,此调用将尝试应用迁移。例如,创建 `Departments` 和 `Employees` 表,但这将失败,因为它们已经存在。在 SQL Server 对象资源管理器中,右键单击现有数据库并将其删除。
如果你确实想提供一个现有数据库,你可以遵循我们刚刚介绍的相同流程,但要删除第一个迁移文件。然而,一般来说,这不是一个好情况,除非你的应用程序必须从现有数据库开始。让我们试一试。
- 运行应用程序。
- 让我们看看我们的 `localDB` 服务器。刷新数据库列表。我们的数据库再次创建,但通过这种方式工作而不是我们以前的方式,我们确保了我们的数据库可以从完全不存在迁移到其初始版本以及之后的未来版本。比我们在前面部分中所做的方法更好。让我们看看数据库本身。
它现在包含一个额外的表——`_EFMigrationsHistory`。让我们看看里面有什么。Entity Framework Core 在数据库中使用此表来跟踪已应用于数据库的迁移。
这确保了 `Database.Migrate()` 调用,或者替代地,命令行中的 Update-Database 调用不会一遍又一遍地尝试执行相同的迁移。
- 让我们继续添加新的迁移。一个 `Employee` 似乎没有工资。我们可能故意忽略了这一点,因为这允许我们研究额外的迁移。所以让我们添加 `Salary` 属性。
代码
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EmployeeManagement { public class Employee { [Key] [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int EmployeeId { get; set; } [Required] [MaxLength(50)] public string EmployeeName { get; set; } public int DepartmentId { get; set; } public int Salary { get; set; } [ForeignKey("DepartmentId")] public virtual Department Department { get; set; } } }
- 然后,让我们再次执行 `Add-Migration` 命令,以便为我们生成文件。我们将此迁移命名为 `EmployeeManagementAddSalaryToEmployee`。
我们的 Migrations 文件夹现在包含一个新文件。
如果查看此文件,我们会发现 `Up` 方法包含添加 `Salary` 列的代码,而 `Down` 方法包含再次删除该列的代码。
- 再次运行应用程序。
- 让我们快速查看数据库。`Employees` 现在确实包含 `Salary` 列。
- 我们来看看 `EFMigrationHistory` 表。确实,它也包含了新的迁移。这就是我们如何使用迁移将数据库从一个版本迁移到另一个版本。但是,如果我们查看这些表中的数据,我们会发现那里还没有任何东西。没有 `Employees`,没有 `Department`。为了开始添加数据,我们应该为数据库填充种子数据。让我们在下一节中看看如何做到这一点。
数据库种子数据
我们的数据库中仍然没有数据。有一些数据进行测试会很好。这个原则,即为数据库提供起始数据,被称为数据库种子数据。它通常用于提供主数据。
我们了解了在 EF 6 版本中如何操作。在这里,我们将讨论另一种填充数据库的方法。
- 我们将在上下文中编写一个扩展方法。所以,让我们从那个扩展方法开始。让我们添加一个新类,`EmployeeManagementContextExtensions`。让我们将其设置为 `static`。
- 让我们为其添加一个 `static` 方法,`EnsureSeedDataForContext`。该方法有一个 `EmployeeManagementContext` 类型的参数,名为 `context`,并用 `this` 修饰,这告诉编译器它扩展了 `EmployeeManagementContext`。我们想要做的第一件事是检查数据库是否已经包含我们的示例数据。我们想要插入 `departments` 及其 `employees`。所以,让我们检查 `Departments` 表是否为空。一个 `employee` 不可能没有 `department` 而存在,所以这就足够了。如果它不为空,我们已经有数据了,我们不想插入额外的数据。否则,我们可以开始添加数据。我们首先创建一个名为“`Technology`”的 `Department`,并为该 `department` 提供三名员工(`Jack`、`Kim`、`Shen`)。我们不提供 ID,因为这些现在由数据库自动生成。然后我们希望将这些添加到上下文中。为此,我们可以使用 `Add` 方法或 `AddRange` 方法,如果我们的上下文中的 `Departments DBSet` 上有多个部门。从这一刻起,实体由上下文跟踪。但它们尚未插入。为此,我们必须在上下文上调用 `SaveChanges`。在上下文上调用 `SaveChanges` 将有效地在我们的数据库上执行语句。
代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EmployeeManagement { public static class EmployeeManagementContextExtensions { public static void EnsureSeedDataForContext (this EmployeeManagementContext context) { if (context.Departments.Any()) { return; } Department department = new Department { DepartmentName = "Technology", Employees = new List<Employee> { new Employee() {EmployeeName = "Jack"}, new Employee() {EmployeeName = "Kim"}, new Employee() {EmployeeName = "Shen"} } }; context.Departments.Add(department); Employee employee = new Employee { EmployeeName = "Akhil Mittal", DepartmentId = 1 }; context.Employees.Add(employee); context.SaveChanges(); } } }
这就是扩展方法的全部内容。然后我们需要执行这个扩展方法。
- 在 Program.cs 类中创建上下文实例后,调用扩展方法 `EnsureSeedDataForContext()`。然后运行应用程序。
代码
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EmployeeManagement { class Program { static void Main(string[] args) { var context = Initialize.GetContext(); context.EnsureSeedDataForContext(); } } }
- 让我们看看我们的数据库。`Departments` 表包含示例数据,`Employees` 表也包含示例数据。通过这些,我们现在了解了 Entity Framework Core 是什么,其最重要的概念,以及如何使用它们。
EF Core 总结
.NET Core 和 Entity Framework Core 确实是跨平台的,但它们在两个层面上是跨平台的。你可以在任何这些平台上运行使用 EF Core 的 .NET Core 应用程序,但你也可以在任何一个平台上创建、调试和构建它们,并且通过这个跨平台工具 Visual Studio Code 及其丰富的功能,加上它是开源的,我可以在任何一个平台上进行编码和调试。Visual Studio Code 只增强了我们使用 .NET Core 和 Entity Framework Core 的灵活性,但 EF Core 本身也很灵活。你还可以将这些应用程序部署到 Docker 并在 Docker 运行的任何地方运行它们。Entity Framework Core 是 Entity Framework 的一个轻量级、可扩展且跨平台的版本。它推荐用于不需要完整 Entity Framework 6 功能集的新应用程序和 .NET Core 应用程序。我们首先创建了实体类。我们可以在这些类上使用注解来定义主键和外键、必填字段等。然后这些注解将作为 `DBSet` 注册到 `DBContext` 上。该上下文表示与数据库的一次会话。它可以用于查询和保存我们实体的实例。从那一刻起,我们就可以通过 LINQ 访问我们的实体。我们还研究了另一个重要概念——迁移。就像我们的代码会演变一样,数据库也会演变。一段时间后可能会添加新表,现有表可能会被删除或更改。迁移允许我们提供代码将数据库从一个版本更改为另一个版本。最后,我们研究了填充数据库的选项,为其提供起始数据。在这里下载完整的免费电子书(深入研究 Microsoft .NET Entity Framework),了解 Entity Framework。
历史
- 2018年10月9日:初始版本