Loading

ASP.NET Core 实战-12.使用 Entity Framework Core 保存数据

介绍Entity Framework Core

数据库访问代码在 Web 应用程序中无处不在。 无论您是构建电子商务应用程序、博客还是 Next Big Thing™,您都可能需要与数据库进行交互。

不幸的是,从应用程序代码与数据库交互通常是一件麻烦事,您可以采用许多不同的方法。 例如,从数据库读取数据这样简单的事情需要处理网络连接、编写 SQL 语句和处理可变结果数据。 .NET 生态系统拥有一整套可用于此目的的库,从低级 ADO.NET 库到高级抽象,如 EF Core。

在本节中,我将描述 EF Core 是什么以及它旨在解决的问题。 我将介绍使用 EF Core 等抽象背后的动机,以及它如何帮助弥合应用程序代码和数据库之间的差距。 作为其中的一部分,我将介绍您在应用程序中使用它时将做出的一些权衡,这将帮助您确定它是否适合您的目的。 最后,我们将看一个示例 EF Core 映射,从应用程序代码到数据库,以了解 EF Core 的主要概念。

什么是 EF Core?

EF Core 是一个库,它提供了一种面向对象的方式来访问数据库。 它充当对象关系映射器 (ORM),为您与数据库通信并将数据库响应映射到 .NET 类和对象,如图 12.1 所示。

图 12.1 EF Core 将 .NET 类和对象映射到数据库概念,例如表和行。
image

定义 使用对象关系映射器 (ORM),您可以使用面向对象的概念(例如类和对象)来操作数据库,方法是将它们映射到数据库概念(例如表和列)。

EF Core 基于但不同于现有的实体框架库(目前最高版本 6.x)。 它是作为 .NET Core 推动跨平台工作的一部分而构建的,但考虑到了其他目标。 特别是,EF Core 团队希望制作一个可与各种数据库一起使用的高性能库。

有许多不同类型的数据库,但可能最常用的系列是关系数据库,使用结构化查询语言 (SQL) 访问。这是 EF Core 的基础; 它可以映射 Microsoft SQL Server、MySQL、Postgres 和许多其他关系数据库。 它甚至还有一个很酷的内存功能,您可以在测试时使用它来创建临时数据库。 EF Core 使用提供者模型,以便以后可以在其他关系数据库可用时插入对它们的支持。

这涵盖了 EF Core 是什么,但没有深入探讨您为什么要使用它。 为什么不使用传统的 ADO.NET 库直接访问数据库? 使用 EF Core 的大多数论点通常都可以应用于 ORM,那么 ORM 的优势是什么?

为什么要使用对象关系映射器?

ORM 带来的最大优势之一是开发应用程序的速度。 您可以留在熟悉的面向对象 .NET 领域,通常无需直接操作数据库或编写自定义 SQL。

假设您有一个电子商务站点,并且您想从数据库中加载产品的详细信息。 使用低级数据库访问代码,您必须打开与数据库的连接,使用正确的表名和列名编写必要的 SQL,通过连接读取数据,创建 POCO 来保存数据,并手动设置 对象的属性,随时将数据转换为正确的格式。 听起来很痛苦,对吧?

诸如 EF Core 之类的 ORM 会为您处理大部分工作。 它处理与数据库的连接、生成 SQL 并将数据映射回您的 POCO 对象。 您只需要提供一个描述您要检索的数据的 LINQ 查询。

ORM 充当数据库的高级抽象,因此它们可以显着减少与数据库交互所需编写的管道代码量。 在最基本的层面上,它们负责将 SQL 语句映射到对象,反之亦然,但大多数 ORM 更进一步,并提供了额外的特性。

像 EF Core 这样的 ORM 会跟踪它们从数据库中检索到的任何对象的哪些属性发生了变化。 这使您可以通过从数据库表映射对象来从数据库加载对象,在 .NET 代码中对其进行修改,然后要求 ORM 更新数据库中的关联记录。 ORM 将计算出哪些属性已更改并为相应的列发出更新语句,从而为您节省大量工作。

与软件开发中经常发生的情况一样,使用 ORM 也有其缺点。ORM 的最大优势之一也是它们的致命弱点——它们对您隐藏了数据库。 有时,这种高层次的抽象可能会导致您的应用程序中出现问题的数据库查询模式。 一个经典的例子是 N+1 问题,其中应该是单个数据库请求变成了对数据库表中每一行的单独请求。

另一个常见的缺点是性能。 ORM 是对多个概念的抽象,因此与您在应用程序中手工制作每条数据访问相比,它们本质上会做更多的工作。 大多数 ORM,包括 EF Core,都会牺牲一定程度的性能来简化开发。

也就是说,如果您意识到 ORM 的缺陷,您通常可以大大简化与数据库交互所需的代码。 与任何事情一样,如果抽象对您有用,请使用它; 否则,不要。 如果您只有最低的数据库访问要求,或者您需要获得最佳性能,那么像 EF Core 这样的 ORM 可能不适合。

另一种方法是两全其美:使用 ORM 来快速开发大部分应用程序,然后回退到 ADO.NET 等较低级别的 API 来处理那些被证明是瓶颈的领域。 您的应用程序。这样,您可以使用 EF Core 获得足够好的性能,用性能换取开发时间,并且只优化那些需要它的区域。

即使您决定在应用程序中使用 ORM,也有许多可用于 .NET 的不同 ORM,EF Core 就是其中之一。 EF Core 是否适合您将取决于您需要的功能以及您愿意为获得这些功能而做出的权衡。 下一部分将 EF Core 与 Microsoft 的其他产品实体框架进行比较,但您可以考虑许多其他替代方案,例如 Dapper 和 NHibernate,每个都有自己的权衡取舍。

什么时候应该选择 EF Core?

Microsoft 将 EF Core 设计为对 2008 年发布的成熟 Entity Framework 6.x (EF 6.x) ORM 的重新构想。经过十年的发展,EF 6.x 是一个稳定且功能丰富的 ORM。

相比之下,EF Core 是一个相对较新的项目。 EF Core 的 API 设计为接近 EF 6.x 的 API(尽管它们并不完全相同),但核心组件已被完全重写。 您应该将 EF Core 视为与 EF 6.x 不同的版本; 直接从 EF 6.x 升级到 EF Core 并非易事。

Microsoft 支持 EF Core 和 EF 6.x,两者都将看到持续改进,那么您应该选择哪一个? 你需要考虑很多事情:

  • 跨平台 - EF Core 5.0 面向 .NET Standard,因此它可用于面向 .NET Core 3.0 或更高版本的跨平台应用程序。 从 6.3 版开始,EF 6.x 也是跨平台的,在 .NET 5.0 上运行时存在一些限制,例如没有设计器支持。
  • 数据库提供程序 - EF 6.x 和 EF Core 都允许您使用可插入的提供程序连接到各种数据库类型。 EF Core 有越来越多的提供程序,但 EF 6.x 的提供程序并不多,尤其是如果您想在 .NET 5.0 上运行 EF 6.x。 如果您正在使用的数据库没有提供程序,那就有点麻烦了!
  • 性能——EF 6.x 的性能在其记录中一直是个黑标,因此 EF Core 旨在纠正这一点。 EF Core 旨在快速和轻量级,显着优于 EF 6.x。 但它不太可能达到更轻量级的 ORM 的性能,例如 Dapper 或手工 SQL 语句。
  • 功能 - 功能是您会发现 EF 6.x 和 EF Core 之间最大差异的地方,尽管 EF Core 5.0 的这种差异比以往任何时候都小。 EF Core 现在具有 EF 6.x 所没有的许多功能(批处理语句、客户端密钥生成、用于测试的内存数据库)。 与 EF 6.x 相比,EF Core 仍然缺少一些功能,例如存储过程映射和每个具体类型的表 (TPC),但由于 EF Core 正在积极开发中,这些功能正在等待实施。 2 相比之下,EF 6.x 可能只会看到增量改进和错误修复,而不是主要功能添加。

这些权衡和限制是否对您来说是一个问题,很大程度上取决于您的特定应用程序。 考虑到这些限制,启动一个新的应用程序比稍后尝试解决它们要容易得多。

如果你正在开发一个新的 ASP.NET Core 应用程序,你想使用 ORM 进行快速开发,并且你不需要任何不可用的功能,那么 EF Core 是一个很好的竞争者。 ASP.NET Core 的各种其他子系统也开箱即用地支持它。 例如,在第 14 章中,您将了解如何使用 EF Core 和 ASP.NET Core 身份验证系统来管理应用程序中的用户。

在深入了解在应用程序中使用 EF Core 的细节之前,我将描述我们将用作本章案例研究的应用程序。 我们将介绍应用程序和数据库的详细信息以及如何使用 EF Core 在两者之间进行通信。

将数据库映射到您的应用程序代码

EF Core 专注于应用程序和数据库之间的通信,因此为了展示它,我们需要一个应用程序。 本章以一个简单的烹饪应用程序为例,它列出了食谱并让您查看食谱的成分,如图 12.2 所示。用户可以浏览食谱、添加新食谱、编辑食谱和删除旧食谱。

这显然是一个简单的应用程序,但它包含了您需要的所有数据库交互,其中包括两个实体:Recipe 和 Ingredient。

图 12.2 烹饪应用程序列出了食谱。 您可以查看、更新和删除食谱,或创建新食谱。
image

定义 实体是由 EF Core 映射到数据库的 .NET 类。这些是您定义的类,通常作为 POCO 类,可以通过使用 EF Core 映射到数据库表来保存和加载。

与 EF Core 交互时,您将主要使用 POCO 实体和从 DbContext EF Core 类继承的数据库上下文。 实体类是数据库中表的面向对象的表示; 它们代表您要存储在数据库中的数据。 您可以在应用程序中使用 DbContext 来配置 EF Core 并在运行时访问数据库。

注意 您的应用程序中可能有多个 DbContext,甚至可以将它们配置为与不同的数据库集成。

当您的应用程序首次使用 EF Core 时,EF Core 会根据应用程序的 DbContext 上的 DbSet<T> 属性和实体类本身创建数据库的内部表示,如图 12.3 所示。

图 12.3 EF Core 通过探索代码中的类型来创建应用程序数据模型的内部模型。 它添加了应用程序的 DbContext 上的 DbSet<> 属性中引用的所有类型,以及任何链接类型。
image

对于您的食谱应用,EF Core 将构建 Recipe 类的模型,因为它在 AppDbContext 上作为 DbSet<Recipe> 公开。 此外,EF Core 将遍历 Recipe 上的所有属性,查找它不知道的类型,并将它们添加到其内部模型中。 在您的应用程序中,Recipe 上的成分集合将成分实体公开为 ICollection<Ingredient>,因此 EF Core 对实体进行适当建模。

每个实体都映射到数据库中的一个表,但 EF Core 还映射实体之间的关系。 每个菜谱可以有很多成分,但每个成分(有名称、数量和单位)属于一个菜谱,所以这是一个多对一的关系。 EF Core 使用该知识正确建模等效的多对一数据库结构。

注意 两种不同的食谱,比如鱼派和柠檬鸡,可能使用名称和数量相同的成分,例如一个柠檬的汁,但它们从根本上是两个不同的实例。 如果您将柠檬鸡食谱更新为使用两个柠檬,您不会希望此更改自动更新鱼饼以使用两个柠檬!

EF Core 在与数据库交互时使用它构建的内部模型。 这可确保它构建正确的 SQL 来创建、读取、更新和删除实体。

是的,是时候写一些代码了! 在下一部分中,您将开始构建食谱应用程序。 您将了解如何将 EF Core 添加到 ASP.NET Core 应用程序、配置数据库提供程序以及设计应用程序的数据模型。

将 EF Core 添加到应用程序

在本节中,我们将重点介绍在您的 ASP.NET Core 配方应用中安装和配置 EF Core。 您将学习如何安装所需的 NuGet 包以及如何为您的应用程序构建数据模型。 当我们在本章中讨论 EF Core 时,我不会大体上介绍如何创建应用程序——我创建了一个简单的 Razor Pages 应用程序作为基础,没什么特别的。

示例应用程序中与 EF Core 的交互发生在服务层中,该服务层封装了 Razor Pages 框架之外的所有数据访问,如图 12.4 所示。这使您的关注点分开并使您的服务可测试。

将 EF Core 添加到应用程序是一个多步骤过程:

1 选择数据库提供商; 例如,Postgres、SQLite 或 MS SQL Server。
2 安装 EF Core NuGet 包。
3 设计应用程序的 DbContext 和构成数据模型的实体。
4 将应用程序的 DbContext 注册到 ASP.NET Core DI 容器。

5 使用 EF Core 生成描述数据模型的迁移。
6 将迁移应用到数据库以更新数据库的架构。

图 12.4 通过使用 EF Core 从数据库加载数据来处理请求。 与 EF Core 的交互仅限于 RecipeService - Razor 页面不直接访问 EF Core。
image

这似乎已经有点令人生畏了,但是我们将在本节中介绍第 1-4 步,在第 12.3 节中介绍第 5-6 步,所以不会花很长时间。 鉴于本章的空间限制,我将在我展示的代码中坚持使用 EF Core 的默认约定。 EF Core 的可定制性远比最初出现的要高,但我鼓励您尽可能坚持默认设置。 从长远来看,这将使您的生活更轻松。

设置 EF Core 的第一步是决定要与哪个数据库进行交互。 客户或您公司的政策可能会向您规定这一点,但仍然值得考虑一下选择。

选择数据库提供程序并安装 EF Core

EF Core 使用提供程序模型支持一系列数据库。 EF Core 的模块化特性意味着您可以使用相同的高级 API 对不同的底层数据库进行编程,并且 EF Core 知道如何生成必要的特定于实现的代码和 SQL 语句。

当您启动应用程序时,您可能已经想到了一个数据库,并且您会很高兴知道 EF Core 已经涵盖了大多数流行的数据库。 添加对给定数据库的支持涉及将正确的 NuGet 包添加到 .csproj 文件。 例如,

  • PostgreSQL——Npgsql.EntityFrameworkCore.PostgreSQL
  • Microsoft SQL Server——Microsoft.EntityFrameworkCore.SqlServer
  • MySQL—MySql.Data.EntityFrameworkCore
  • SQLite—Microsoft.EntityFrameworkCore.SQLite

一些数据库提供程序包由 Microsoft 维护,一些由开源社区维护,还有一些可能需要付费许可证(例如,Oracle 提供程序),因此请务必检查您的要求。

以与任何其他库相同的方式将数据库提供程序安装到应用程序中:通过将 NuGet 包添加到项目的 .csproj 文件并从命令行运行 dotnet restore(或让 Visual Studio 自动为您恢复)。

EF Core 本质上是模块化的,因此您需要安装多个包。 我将 SQL Server 数据库提供程序与 LocalDB 一起用于配方应用程序,因此我将使用 SQL Server 包:

  • Microsoft.EntityFrameworkCore.SqlServer——这是在运行时使用 EF Core 的主要数据库提供程序包。 它还包含对主 EF Core NuGet 包的引用。
  • Microsoft.EntityFrameworkCore.Design——它包含 EF Core 的共享设计时组件。

清单 12.1 显示了添加 EF Core 包后配方应用的 .csproj 文件。请记住,您将 NuGet 包添加为 PackageReference 元素。

清单 12.1 将 EF Core 安装到 ASP.NET Core 应用程序中

<Project Sdk="Microsoft.NET.Sdk.Web">
    <PropertyGroup>
        <TargetFramework>net5.0</TargetFramework>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference
                          Include="Microsoft.EntityFrameworkCore.SqlServer"
                          Version="5.0.0" />
        <PackageReference
                          Include="Microsoft.EntityFrameworkCore.Design"
                          Version="5.0.0" />
    </ItemGroup>
</Project>

安装和恢复这些包后,您就拥有了开始为应用程序构建数据模型所需的一切。 在下一节中,我们将为您的食谱应用创建实体类和 DbContext。

构建数据模型

在第 12.1.4 节中,我概述了 EF Core 如何从 DbContext 和实体模型构建数据库的内部模型。 除了这种发现机制之外,EF Core 在让您以您想要的方式定义实体方面非常灵活,例如 POCO 类。

一些 ORM 要求您的实体从特定的基类继承,或者您使用属性装饰模型以描述如何映射它们。 正如您在此清单中所见,EF Core 非常偏爱约定而不是配置方法,该清单显示了您的应用程序的配方和成分实体类。

清单 12.2 定义 EF Core 实体类

public class Recipe
{
    public int RecipeId { get; set; }
    public string Name { get; set; }
    public TimeSpan TimeToCook { get; set; }
    public bool IsDeleted { get; set; }
    public string Method { get; set; }
    public ICollection<Ingredient> Ingredients { get; set; }
}

public class Ingredient
{
    public int IngredientId { get; set; }
    public int RecipeId { get; set; }
    public string Name { get; set; }
    public decimal Quantity { get; set; }
    public string Unit { get; set; }
}

这些类符合 EF Core 用于构建其映射的数据库图片的某些默认约定。 例如,Recipe 类有一个 RecipeId 属性,而成分类有一个 IngredientId 属性。 EF Core 将这种 Id 后缀模式标识为指示表的主键。

定义 表的主键是在表中所有其他行中唯一标识该行的值。 它通常是 int 或 Guid。

此处可见的另一个约定是成分类的 RecipeId 属性。 EF Core 将其解释为指向 Recipe 类的外键。 当与Recipe 类上的ICollection<Ingredient> 一起考虑时,这表示多对一的关系,其中每个配方有许多成分,但每种成分只属于一个单一的配方,如图12.5 所示。

图 12.5 代码中的多对一关系被转换为表之间的外键关系。
image

定义 表上的外键指向不同表的主键,形成两行之间的链接。

这里还有许多其他约定,例如 EF Core 为数据库表和列假定的名称,或者它将用于每个属性的数据库列类型,但我不打算在这里讨论它们。 EF Core 文档包含有关所有约定的详细信息,以及如何为您的应用程序自定义它们:https://docs.microsoft.com/ef/core/modeling/。

提示 您还可以使用 DataAnnotations 属性来装饰您的实体类,控制列命名或字符串长度等内容。 EF Core 将使用这些属性来覆盖默认约定。

除了实体之外,您还可以为应用程序定义 DbContext。 这是应用程序中 EF Core 的核心,用于所有数据库调用。 创建一个自定义 DbContext,在本例中称为 AppDbContext,并从 DbContext 基类派生,如下所示。 这会公开 DbSet<Recipe>,以便 EF Core 可以发现和映射 Recipe 实体。 您可以通过这种方式为应用程序中的每个顶级实体公开多个 DbSet<> 实例。

清单 12.3 定义应用程序 DbContext

public class AppDbContext : DbContext
{
    //构造函数选项对象,包含连接字符串等详细信息
    public AppDbContext(DbContextOptions<AppDbContext> options)
        : base(options) { }
    //您将使用 Recipes 属性来查询数据库。
    public DbSet<Recipe> Recipes { get; set; }
}

您的应用程序的 AppDbContext 很简单,包含根实体的列表,但您可以在更复杂的应用程序中使用它做更多的事情。 如果需要,您可以完全自定义 EF Core 将实体映射到数据库的方式,但对于此应用程序,您将使用默认值。

注意 您没有在 AppDbContext 上列出成分,但它将由 EF Core 建模,因为它在配方中公开。 您仍然可以访问数据库中的成分对象,但您必须通过食谱实体的成分属性导航才能这样做,如您将在第 12.4 节中看到的那样。

对于这个简单的示例,您的数据模型由以下三个类组成:AppDbContextRecipeIngredient。 这两个实体将映射到表,它们的列映射到属性,您将使用 AppDbContext 来访问它们。

数据模型已经完成,但您还没有准备好使用它。 您的 ASP.NET Core 应用程序不知道如何创建您的 AppDbContext,并且您的 AppDbContext 需要一个连接字符串,以便它可以与数据库通信。 在下一节中,我们将解决这两个问题,并完成在您的 ASP.NET Core 应用程序中设置 EF Core。

注册数据上下文

与 ASP.Net Core 中的任何其他服务一样,您应该将 AppDbContext 注册到 DI 容器。 注册上下文时,您还可以配置数据库提供程序并设置连接字符串,以便 EF Core 知道如何与数据库通信。

您在 Startup.csConfigureServices 方法中注册 AppDbContext。EF Core 为此提供了一个通用的 AddDbContext<T> 扩展方法,该方法采用 DbContextOptionsBuilder 实例的配置函数。 此构建器可用于设置 EF Core 的大量内部属性,并允许您根据需要完全替换 EF Core 的内部服务。

再次,您的应用程序的配置既漂亮又简单,如下面的清单所示。 您使用 Microsoft.EntityFrameworkCore.SqlServer 包提供的 UseSqlServer 扩展方法设置数据库提供程序,并将连接字符串传递给它。

清单 12.4 向 DI 容器注册一个 DbContext

public void ConfigureServices(IServiceCollection services)
{
    //连接字符串取自配置,来自 ConnectionStrings 部分。
    var connString = Configuration
        .GetConnectionString("DefaultConnection");
    //将应用程序的 DbContext 用作通用参数来注册它。
    services.AddDbContext<AppDbContext>(
        //在 DbContext 的自定义选项中指定数据库提供程序。
        options => options.UseSqlServer(connString));
    // 添加其他服务.
}

注意 如果您使用不同的数据库提供程序,例如 SQLite 提供程序,则在注册 AppDbContext 时,您需要在选项对象上调用适当的 Use* 方法。

正如我在前一章中讨论的那样,连接字符串是一个典型的秘密,因此从配置中加载它是有意义的。 在运行时,将使用当前环境的正确配置字符串,因此您可以在本地开发和生产环境中使用不同的数据库。

提示 您可以通过其他方式配置您的 AppDbContext 并提供连接字符串,例如使用 OnConfiguring 方法,但我建议 ASP.NET Core 网站使用此处显示的方法。

您现在有一个在 DI 容器中注册的 DbContextAppDbContext,以及一个与您的数据库对应的数据模型。 就代码而言,您已准备好开始使用 EF Core,但您还没有数据库! 在下一部分中,您将了解如何轻松使用 .NET CLI 来确保您的数据库与您的 EF Core 数据模型保持同步。

使用迁移管理更改

在本节中,您将学习如何使用迁移生成 SQL 语句,以使数据库的架构与应用程序的数据模型保持同步。 您将学习如何创建初始迁移并使用它来创建数据库。 然后,您将更新您的数据模型,创建第二个迁移,并使用它来更新数据库模式。

管理数据库的模式更改(例如当您需要添加新表或新列时)非常困难。 您的应用程序代码明确绑定到特定版本的数据库,您需要确保两者始终保持同步。

定义 模式是指数据在数据库中的组织方式,包括表、列以及它们之间的关系等。

部署应用程序时,您通常可以删除旧代码/可执行文件并用新代码替换它——工作完成。 如果您需要回滚更改,请删除该新代码并部署旧版本的应用程序。

数据库的困难在于它们包含数据! 这意味着不可能在每次部署时都把它吹走并创建一个新数据库。

一个常见的最佳实践是显式地对数据库的模式和应用程序的代码进行版本控制。 您可以通过多种方式执行此操作,但通常需要存储数据库的先前模式和新模式之间的差异,通常作为 SQL 脚本。 然后,您可以使用 DbUp 和 FluentMigrator 等库来跟踪已应用的脚本并确保您的数据库架构是最新的。 或者,您可以使用外部工具为您进行管理。

EF Core 提供了自己的架构管理版本,称为迁移。 迁移提供了一种在 EF Core 数据模型更改时管理对数据库架构的更改的方法。 迁移是应用程序中的 C# 代码文件,它定义了数据模型如何更改——添加了哪些列、新实体等。 迁移记录了您的数据库架构如何随着时间的推移而演变为应用程序的一部分,因此架构始终与您的应用程序的数据模型保持同步。

您可以使用命令行工具从迁移中创建新数据库,或者通过向现有数据库应用新迁移来更新现有数据库。 您甚至可以回滚迁移,这会将数据库更新到以前的模式。

警告 应用迁移会修改数据库,因此您必须始终注意数据丢失。 如果您使用迁移从数据库中删除一个表,然后回滚迁移,则该表将被重新创建,但它之前包含的数据将永远消失!

在本节中,您将看到如何创建第一个迁移并使用它来创建数据库。然后您将更新您的数据模型,创建第二个迁移,并使用它来更新数据库架构。

创建您的第一个迁移

在创建迁移之前,您需要安装必要的工具。 有两种主要方法可以做到这一点:

  • 包管理器控制台 - 您可以在 Visual Studio 的包管理器控制台 (PMC) 中使用 PowerShell cmdlet。 您可以直接从 PMC 安装它们,也可以通过将 Microsoft.EntityFrameworkCore.Tools 包添加到您的项目来安装它们。
  • .NET 工具 - 可以从命令行运行并扩展 .NET SDK 的跨平台工具。 您可以通过运行 dotnet tool install --global dotnet-ef 为您的计算机全局安装这些工具。

在本书中,我将使用跨平台的 .NET 工具,但如果您熟悉 EF 6.x 或者更喜欢使用 Visual Studio PMC,那么您将执行的所有步骤都有等效的命令 5 你可以通过运行 dotnet ef 来检查 .NET 工具是否安装正确。 这应该会产生一个如图 12.6 所示的帮助屏幕。

图 12.6 运行 dotnet ef 命令检查 .NET EF Core 工具是否安装正确
image

提示 如果您在运行上述命令时收到“No executable found matching command ‘dotnet-ef’”消息,请确保您已使用 dotnet tool install --global dotnet-ef 安装了全局工具。 通常,您需要从已注册 AppDbContext 的项目文件夹中运行 dotnet ef 工具(而不是在解决方案文件夹级别)。

安装工具并配置数据库上下文后,您可以通过在 Web 项目文件夹中运行以下命令并提供迁移名称来创建第一个迁移 - 在本例中为 InitialSchema:

dotnet ef migrations add InitialSchema

此命令在项目的 Migrations 文件夹中创建三个文件:

  • Migration 文件——Timestamp_MigrationName.cs 格式的文件。 这描述了对数据库执行的操作,例如创建表或添加列。请注意,此处生成的命令是特定于数据库提供程序的,基于项目中配置的数据库提供程序。
  • Migration designer.cs 文件 - 该文件描述了 EF Core 在生成迁移时的数据模型的内部模型。
  • AppDbContextModelSnapshot.cs——这描述了 EF Core 当前的内部模型。当你添加另一个迁移时,这将被更新,因此它应该始终与当前的最新迁移相同。

EF Core 可以使用 AppDbContextModelSnapshot.cs 在创建新迁移时确定数据库的先前状态,而无需直接与数据库交互。

这三个文件封装了迁移过程,但添加迁移不会更新数据库本身的任何内容。 为此,您必须运行不同的命令才能将迁移应用到数据库。

提示 在运行以下命令之前,您可以并且应该查看 EF Core 生成的迁移文件以检查它将对您的数据库执行的操作。安全总比抱歉!

您可以通过以下三种方式之一应用迁移:

  • 使用 .NET 工具
  • 使用 Visual Studio PowerShell cmdlet
  • 在代码中,通过从 DI 容器获取 AppDbContext 的实例并调用 context.Database.Migrate()

哪个最适合您取决于您如何设计应用程序、如何更新生产数据库以及您的个人偏好。 我现在将使用 .NET 工具,但我将在 12.5 节中讨论其中的一些注意事项。

您可以通过运行将迁移应用到数据库

dotnet ef database update

从应用程序的项目文件夹中。 我不会详细介绍它是如何工作的,但是这个命令执行四个步骤:

1 构建您的应用程序。
2 加载在应用的 Startup 类中配置的服务,包括 AppDbContext
3 检查 AppDbContext 连接字符串中的数据库是否存在。 如果没有,它会创建它。
4 通过应用任何未应用的迁移来更新数据库。

如果一切配置正确,正如我在第 12.2 节中所展示的,那么运行此命令将为您设置一个闪亮的新数据库,如图 12.7 所示。

注意 如果您在运行这些命令时收到“未找到项目”的错误消息,请检查您是否在应用程序的项目文件夹中运行它们,而不是在顶级解决方案文件夹中。

图 12.7 将迁移应用到数据库将创建数据库(如果它不存在)并更新数据库以匹配 EF Core 的内部数据模型。 应用的迁移列表存储在 __EFMigrationsHistory 表中。
image

将迁移应用到数据库时,EF Core 会在数据库中创建必要的表并添加适当的列和键。 您可能还注意到 __EFMigrationsHistory 表。 EF Core 使用它来存储它应用于数据库的迁移的名称。 下次您运行 dotnet ef database update 时,EF Core 可以将此表与您的应用程序中的迁移列表进行比较,并且只会将新的迁移应用到您的数据库。

在下一节中,我们将了解这如何使更改数据模型和更新数据库模式变得容易,而无需从头开始重新创建数据库。

添加第二个迁移

大多数应用程序不可避免地会发展,无论是由于范围的扩大还是简单的维护。向实体添加属性、完全添加新实体以及删除过时的类——所有这些都是可能的。

EF Core 迁移使这变得简单。 想象一下,您决定通过在 Recipe 实体上公开 IsVegetarianIsVegan 属性来突出显示食谱应用程序中的素食和纯素食菜肴。 将您的实体更改为您想要的状态,生成迁移,并将其应用于数据库,如图 12.8 所示。

图 12.8 创建第二个迁移并使用命令行工具将其应用到数据库。
image

清单 12.5 向 Recipe 实体添加属性

public class Recipe
{
    public int RecipeId { get; set; }
    public string Name { get; set; }
    public TimeSpan TimeToCook { get; set; }
    public bool IsDeleted { get; set; }
    public string Method { get; set; }
    public bool IsVegetarian { get; set; }
    public bool IsVegan { get; set; }
    public ICollection<Ingredient> Ingredients { get; set; }
}

更改实体后,您需要更新 EF Core 的数据模型的内部表示。 您可以通过调用 dotnet ef migrations add 并为迁移提供名称以与第一次迁移完全相同的方式执行此操作:

dotnet ef migrations add ExtraRecipeFields

这会通过添加迁移文件及其 .designer.cs 快照文件并更新 AppDbContextModelSnapshot.cs 在您的项目中创建第二个迁移,如图 12.9 所示。

图 12.9 添加第二个迁移会添加一个新的迁移文件和一个迁移 Designer.cs 文件。它还会更新 AppDbContextModelSnapshot 以匹配新迁移的 Designer.cs 文件。
image

和以前一样,这会创建迁移文件,但不会修改数据库。 您可以通过运行应用迁移并更新数据库

dotnet ef database update

这会将应用程序中的迁移与数据库中的 __EFMigrationsHistory 表进行比较,以查看哪些迁移未完成,然后运行它们。 EF Core 将运行 20200511204457_ExtraRecipeFields 迁移,将 IsVegetarian 和 IsVegan 字段添加到数据库中,如图 12.10 所示。

图 12.10 将 ExtraRecipeFields 迁移应用到数据库将 IsVegetarian 和 IsVegan 字段添加到 Recipes 表。
image

使用迁移是确保您的数据库与源代码控制中的应用程序代码一起版本控制的好方法。 您可以轻松地查看应用程序的源代码以了解某个历史时间点,并重新创建应用程序在该时间点使用的数据库模式。

当您单独工作或部署到单个 Web 服务器时,迁移很容易使用,但即使在这些情况下,在决定如何管理数据库时也需要考虑一些重要的事情。 对于具有多个使用共享数据库的 Web 服务器的应用程序,或者对于容器化应用程序,您需要考虑的事情甚至更多。

这本书是关于 ASP.NET Core,而不是 EF Core,所以我不想过多地讨论数据库管理,但是第 12.5 节指出了在生产中使用迁移时需要牢记的一些事情。

在下一节中,我们将回到实质内容——定义我们的业务逻辑并在数据库上执行 CRUD 操作。

从数据库查询数据并将数据保存到数据库

让我们回顾一下您在创建配方应用程序时所处的位置:

  • 您为应用程序创建了一个简单的数据模型,由配方和成分组成。
  • 您为数据模型生成了迁移,以更新 EF Core 的实体内部模型。
  • 您将迁移应用到数据库,因此其架构与 EF Core 的模型相匹配。

在本节中,您将通过创建一个 RecipeService 来为您的应用程序构建业务逻辑。 这将处理查询数据库的食谱、创建新食谱和修改现有食谱。 由于这个应用程序只有一个简单的域,我将使用 RecipeService 来处理所有需求,但在您自己的应用程序中,您可能有多个服务协同提供业务逻辑。

注意 对于简单的应用程序,您可能会想将此逻辑移动到您的 Razor 页面中。 我鼓励你抵制这种冲动; 将业务逻辑提取到其他服务可以将 Razor 页面和 Web API 的以 HTTP 为中心的性质与底层业务逻辑分离。 这通常会使您的业务逻辑更容易测试和更可重用。

我们的数据库中还没有任何数据,所以我们最好先让您创建一个食谱。

创建记录

在本节中,您将构建让用户在应用程序中创建食谱的功能。 这将主要包含一个表单,用户可以使用该表单使用 Razor Tag Helpers 输入配方的所有详细信息,您在第 7 章和第 8 章中了解了该表单。该表单发布到 Create.cshtml Razor 页面,该页面使用模型 绑定和验证属性来确认请求是有效的,正如你在第 6 章中看到的那样。

如果请求有效,页面处理程序将调用 RecipeService 在数据库中创建新的 Recipe 对象。 由于 EF Core 是本章的主题,我将只关注这项服务,但如果您想了解所有内容如何组合在一起,您可以随时查看本书的源代码。

在这个应用程序中创建配方的业务逻辑很简单——没有逻辑! 将 Create.cshtml Razor Page 中提供的命令绑定模型映射到 Recipe 实体及其成分,将 Recipe 对象添加到 AppDbContext,并将其保存在数据库中,如图 12.11 所示。

图 12.11 调用 Create.cshtml Razor 页面并创建一个新实体。 Recipe 从 CreateRecipeCommand 绑定模型创建并添加到 DbContext。 EF Core 生成 SQL 以向数据库中的 Recipes 表添加新行。
image

在 EF Core 中创建实体涉及向映射表添加新行。 对于您的应用程序,每当您创建新配方时,您还会添加链接的成分实体。 EF Core 负责通过为数据库中的每个成分创建正确的 RecipeId 来正确链接所有这些。

此示例所需的大部分代码涉及从 CreateRecipeCommand 转换为 Recipe 实体 - 与 AppDbContext 的交互仅包含两个方法:Add()SaveChangesAsync()

清单 12.6 在数据库中创建一个 Recipe 实体

readonly AppDbContext _context; //使用 DI 将 AppDbContext 的一个实例注入到类构造函数中。
//CreateRecipeCommand 从 Razor Page 处理程序传入。
public async Task<int> CreateRecipe(CreateRecipeCommand cmd)
{
    //通过从命令对象映射到配方实体来创建配方。
    var recipe = new Recipe
    {
        Name = cmd.Name,
        TimeToCook = new TimeSpan(
            cmd.TimeToCookHrs, cmd.TimeToCookMins, 0),
        Method = cmd.Method,
        IsVegetarian = cmd.IsVegetarian,
        IsVegan = cmd.IsVegan,
        Ingredients = cmd.Ingredients?.Select(i =>
                                           //将每个 CreateIngredientCommand 映射到一个成分实体。
                                              new Ingredient
                                              {
                                                  Name = i.Name,
                                                  Quantity = i.Quantity,
                                                  Unit = i.Unit,
                                              }).ToList()
    };
    //告诉 EF Core 跟踪新实体。
    _context.Add(recipe);
    //告诉 EF Core 将实体写入数据库。 这使用命令的异步版本。
    await _context.SaveChangesAsync();
    //保存时,EF Core 会填充新配方上的 RecipeId 字段。
    return recipe.RecipeId;
}

与 EF Core 和数据库的所有交互都从 AppDbContext 实例开始,该实例通常通过构造函数进行 DI 注入。 创建一个新实体需要三个步骤:

1 创建RecipeIngredient 实体。
2 使用 _context.Add(entity) 将实体添加到 EF Core 的跟踪实体列表中。
3 通过调用 _context.SaveChangesAsync() 对数据库执行 SQL INSERT 语句,将必要的行添加到Recipe 和Ingredient表中。

提示 大多数涉及与数据库交互的 EF Core 命令都有同步和异步版本,例如 SaveChanges()SaveChangesAsync()。 通常,异步版本将允许您的应用程序处理更多并发连接,因此我倾向于在可以使用它们时偏爱它们。

如果 EF Core 尝试与您的数据库交互时出现问题(例如,您尚未运行迁移来更新数据库架构),它将引发异常。 我没有在这里展示它,但是在你的应用程序中处理这些是很重要的,这样当出现问题时你就不会向用户展示一个丑陋的错误页面。

假设一切顺利,EF Core 会更新您的实体的所有自动生成的 ID(RecipeId 上的 RecipeId,以及成分上的 RecipeIdIngredientId)。 将配方 ID 返回到 Razor 页面,以便它可以使用它; 例如,重定向到查看食谱页面。

至此,您已经使用 EF Core 创建了您的第一个实体。 在下一节中,我们将研究从数据库中加载这些实体,以便您可以在列表中查看它们。

加载记录列表

对于大多数意图和目的,加载单个记录与加载记录列表相同。 它们共享您在图 12.13 中看到的相同结构,但是在加载单个记录时,您通常会使用 Where 子句并执行将数据限制为单个实体的命令。

清单 12.8 显示了通过 ID 获取配方的代码,遵循与之前相同的基本模式(图 12.12)。 它使用 Where() LINQ 表达式将查询限制为单个配方,其中 RecipeId == id,并使用 Select 子句映射到 RecipeDetailViewModelSingleOrDefaultAsync() 子句将导致 EF Core 生成 SQL 查询,在数据库上执行它,并构建视图模型。

注意 如果前面的 Where 子句返回多条记录,SingleOrDefaultAsync() 将引发异常。

清单 12.8 在 RecipeService 中使用 EF Core 加载单个项目

public async Task<RecipeDetailViewModel> GetRecipeDetail(int id) //要加载的配方的 ID 作为参数传递。
{
    return await _context.Recipes //和以前一样,查询从 DbSet 属性开始。
        .Where(x => x.RecipeId == id) //将查询限制为具有提供的 id 的配方。
        .Select(x => new RecipeDetailViewModel  //将Recipe 映射到RecipeDetailViewModel。
                {
                    Id = x.RecipeId,
                    Name = x.Name,
                    Method = x.Method,
                    Ingredients = x.Ingredients
                        .Select(item => new RecipeDetailViewModel.Item
                                {
                                    Name = item.Name,
                                    Quantity = $"{item.Quantity} {item.Unit}"
                                })
                })
        .SingleOrDefaultAsync(); //执行查询并将数据映射到视图模型。
}

请注意,除了将 Recipe 映射到 RecipeDetailViewModel 之外,您还映射了 Recipe 的相关成分,就好像您直接在内存中处理对象一样。 这是使用 ORM 的优势之一 — 您可以轻松映射子对象并让 EF Core 决定如何最好地构建底层查询以获取数据。

注意 EF Core 默认将其运行的所有 SQL 语句记录为 LogLevel.Information 事件,因此您可以轻松查看针对数据库运行的查询。

我们的应用程序肯定正在成形; 您可以创建新食谱,在列表中查看它们,并深入查看各个食谱及其成分和方法。 不过,很快就会有人输入一个错字并想要更改他们的数据。 为此,您必须在 CRUD 中实现 U:update。

使用更改更新模型

当实体发生变化时更新它们通常是 CRUD 操作中最难的部分,因为有很多变量。 图 12.14 概述了此过程,因为它适用于您的食谱应用程序。

我不会在本书中处理关系方面,因为这通常是一个复杂的问题,你如何处理它取决于你的数据模型的细节。 相反,我将专注于更新 Recipe 实体本身的属性。

图 12.14 更新实体涉及三个步骤:使用 EF Core 读取实体,更新实体的属性,并在 DbContext 上调用 SaveChangesAsync() 以生成 SQL 以更新数据库中的正确行。
image

对于 Web 应用程序,当您更新实体时,通常会遵循图 12.14 中概述的步骤:

1 从数据库中读取实体。
2 修改实体的属性。
3 将更改保存到数据库。

您将把这三个步骤封装在一个名为 UpdateRecipeRecipeService 方法中。 此方法采用 UpdateRecipeCommand 参数并包含更改配方实体的代码。

注意 与创建命令一样,您无需直接修改 Razor 页面中的实体,确保您将 UI 关注点与业务逻辑分开。

下面的清单显示了 RecipeService.UpdateRecipe 方法,它更新了 Recipe 实体。 它执行我们之前定义的三个步骤来读取、修改和保存实体。 我已提取代码以将新值更新为辅助方法。

清单 12.9 在 RecipeService 中使用 EF Core 更新现有实体

public async Task UpdateRecipe(UpdateRecipeCommand cmd)
{
    //Find 由 Recipes 直接公开,并简化了通过 id 读取实体的过程。
    var recipe = await _context.Recipes.FindAsync(cmd.Id);
    //如果提供的 id 无效,recipe 将为空。
    if(recipe == null) {
        throw new Exception("Unable to find the recipe");
    }
    //在配方实体上设置新值。
    UpdateRecipe(recipe, cmd);
    //执行 SQL 以保存对数据库的更改。
    await _context.SaveChangesAsync();
}
//用于在配方实体上设置新属性的辅助方法
static void UpdateRecipe(Recipe recipe, UpdateRecipeCommand cmd)
{
    recipe.Name = cmd.Name;
    recipe.TimeToCook =
        new TimeSpan(cmd.TimeToCookHrs, cmd.TimeToCookMins, 0);
    recipe.Method = cmd.Method;
    recipe.IsVegetarian = cmd.IsVegetarian;
    recipe.IsVegan = cmd.IsVegan;
}

在此示例中,我使用 DbSet 公开的 FindAsync(id) 方法读取了 Recipe 实体。 这是一个简单的帮助方法,用于通过 ID 加载实体,在本例中为 RecipeId。 我本可以使用 LINQ 编写一个类似的查询

_context.Recipes.Where(r=>r.RecipeId == cmd.Id).FirstOrDefault();

使用 FindAsync()Find() 更具声明性和简洁性。

提示 Find 实际上有点复杂。 查找第一个检查以查看实体是否已在 EF Core 的 DbContext 中进行跟踪。 如果是(因为之前在此请求中加载了实体),则立即返回实体而不调用 DB。 如果实体被跟踪,这显然会更快,但如果你知道实体还没有被跟踪,它也可能会更慢。

您可能想知道当您调用 SaveChangesAsync() 时 EF Core 如何知道要更新哪些列。 最简单的方法是更新每一列——如果字段没有改变,那么再次写入相同的值也没关系。 但是 EF Core 比这要聪明一些。

EF Core 在内部跟踪它从数据库加载的任何实体的状态。 它创建所有实体属性值的快照,因此它可以跟踪哪些属性值已更改。 当您调用 SaveChanges() 时,EF Core 会将任何跟踪实体(在本例中为配方实体)的状态与跟踪快照进行比较。 任何已更改的属性都包含在发送到数据库的 UPDATE 语句中,而未更改的属性将被忽略。

有了更新食谱的能力,您的食谱应用程序就差不多完成了。 “但是等等,”我听到你哭了,“我们还没有处理 CRUD 中的 D——删除!” 这是真的,但实际上,我发现很少有你想删除数据的情况。

让我们考虑从应用程序中删除配方的要求,如图 12.15 所示。 您需要在食谱旁边添加一个(看起来很吓人的)删除按钮。 用户单击删除后,该配方在列表中不再可见,也无法查看。

图 12.15 从应用程序中删除食谱时所需的行为。 单击删除应返回应用程序的主列表视图,删除的配方不再可见。
image

您可以通过从数据库中删除配方来实现这一点,但数据的问题是一旦它消失了,它就消失了! 如果用户不小心删除了一条记录怎么办? 此外,从关系数据库中删除一行通常会对其他实体产生影响。例如,由于外键,您不能在不删除所有引用它的成分行的情况下从应用程序中的配方表中删除一行 对 Ingredient.RecipeId 的约束。

EF Core 可以使用 DbContext.Remove(entity) 命令轻松地为您处理这些真正的删除场景,但通常当您发现需要删除数据时,您的意思是“归档”它或从 UI 中隐藏它。 处理这种情况的常用方法是在您的实体上包含某种“此实体是否已删除”标志,例如我在 Recipe 实体中包含的 IsDeleted 标志:

public bool IsDeleted {get;set;}

如果采用这种方法,删除数据会突然变得更简单,因为它只不过是对实体的更新。 不再有丢失数据的问题,也不再存在参照完整性问题。

注意 我发现这种模式的主要例外是当您存储用户的个人识别信息时。 在这些情况下,您可能有义务(并且可能有法律义务)根据要求从您的数据库中清除他们的信息。

使用这种方法,您可以在 RecipeService 上创建一个更新 IsDeleted 标志的删除方法,如下面的清单所示。 此外,您应该确保在您的 RecipeService 中的所有其他方法中都有 Where() 子句,以确保您不能显示已删除的食谱,如清单 12.9 中的 GetRecipes() 方法所示。

清单 12.10 在 EF Core 中将实体标记为已删除

public async Task DeleteRecipe(int recipeId)
{
    //通过 id 获取 Recipe 实体。
    var recipe = await _context.Recipes.FindAsync(recipeId);
    //如果提供的 id 无效,recipe 将为空。
    if(recipe is null) {
        throw new Exception("Unable to find the recipe");
    }
    //将食谱标记为已删除。
    recipe.IsDeleted = true;
    //执行 SQL 以保存对数据库的更改。
    await _context.SaveChangesAsync();
}

这种方法满足了要求——它从应用程序的 UI 中删除了配方——但它简化了许多事情。 这种软删除方法并不适用于所有场景,但我发现它是我从事的项目中的一种常见模式。

关于 EF Core 的这一章快要结束了。 我们已经介绍了将 EF Core 添加到您的项目并使用它来简化数据访问的基础知识,但是随着您的应用程序变得越来越复杂,您可能需要了解有关 EF Core 的更多信息。 在本章的最后一节中,我想指出在您自己的应用程序中使用 EF Core 之前需要考虑的一些事项,以便您熟悉您在应用程序中将面临的一些问题。

以下主题对于开始使用 EF Core 不是必需的,但如果您构建生产就绪的应用程序,您很快就会遇到它们。 本节不是解决这些问题的说明性指南; 在您投入生产之前,更多的是要考虑的一组事情。

  • 列的脚手架——EF Core 通过允许大的或无限长度的字符串对字符串列等内容使用保守的值。 在实践中,您可能希望将这些和其他数据类型限制为合理的值。
  • 验证——您可以使用 DataAnnotations 验证属性装饰您的实体,但 EF Core 不会在保存到数据库之前自动验证值。 这与 EF 6.x 的行为不同,后者的验证是自动的。
  • 处理并发——EF Core 提供了几种处理并发的方法,其中多个用户尝试同时更新一个实体。 一种部分解决方案是在您的实体上使用时间戳列。
  • 同步与异步——EF Core 提供同步和异步命令来与数据库交互。 通常,异步对于 Web 应用程序来说更好,但是这个论点存在细微差别,因此不可能在所有情况下都推荐一种方法而不是另一种方法。

EF Core 是在编写数据访问代码时提高工作效率的好工具,但在使用数据库的某些方面不可避免地会很尴尬。 数据库管理问题是最棘手的问题之一。 这本书是关于 ASP.NET Core,而不是 EF Core,所以我不想过多地讨论数据库管理。 话虽如此,大多数 Web 应用程序都使用某种数据库,因此以下问题可能会在某些时候影响您:

  • 自动迁移——如果您将应用程序作为某种 DevOps 管道的一部分自动部署到生产环境中,您将不可避免地需要某种方式自动将迁移应用到数据库。您可以通过多种方式解决此问题,例如编写 .NET 工具脚本、在应用程序的启动代码中应用迁移或使用自定义工具。每种方法都有其优点和缺点。
  • 多个 Web 主机——一个具体的考虑因素是您是否有多个 Web 服务器托管您的应用程序,它们都指向同一个数据库。如果是这样,那么在应用程序的启动代码中应用迁移变得更加困难,因为您必须确保一次只有一个应用程序可以迁移数据库。
  • 进行向后兼容的模式更改——多网络主机方法的一个必然结果是,您经常会遇到这样的情况,即您的应用程序正在访问具有比应用程序认为的更新模式的数据库。这意味着您通常应该尽可能地使模式更改向后兼容。
  • 将迁移存储在不同的程序集中——在本章中,我将所有逻辑都包含在一个项目中,但在较大的应用程序中,数据访问通常位于与 Web 应用程序不同的项目中。对于具有这种结构的应用,在使用 .NET CLI 或 PowerShell cmdlet 时必须使用稍有不同的命令。
  • 种子数据——当您第一次创建数据库时,您通常希望它有一些初始种子数据,例如默认用户。 EF 6.x 有一种内置数据播种机制,而 EF Core 要求您自己显式地播种数据库。

您选择如何处理这些问题将取决于您对应用程序采用的基础架构和部署方法。 解决它们都不是特别有趣,但不幸的是它们是必需的。 不过,振作起来,它们都可以通过一种或另一种方式解决!

这将我们带到了关于 EF Core 的本章的结尾。 在下一章中,我们将看看 MVC 和 Razor Pages 中稍微高级一点的主题:过滤器管道,以及如何使用它来减少代码中的重复。

总结

  • EF Core 是一个对象关系映射器 (ORM),它允许您通过在应用程序中操作标准 POCO 类(称为实体)与数据库进行交互。这可以减少您提高生产力所需的 SQL 和数据库知识量。
  • EF Core 将实体类映射到表,将实体上的属性映射到表中的列,并将实体对象的实例映射到这些表中的行。 即使您使用 EF Core 来避免直接使用数据库,您也需要牢记此映射。
  • EF Core 使用数据库提供程序模型,使您可以更改底层数据库,而无需更改任何对象操作代码。 EF Core 为 Microsoft SQL Server、SQLite、PostgreSQL、MySQL 等提供了数据库提供程序。
  • EF Core 是跨平台的,并且对于 ORM 具有良好的性能,但它具有与 EF 6.x 不同的功能集。 尽管如此,对于所有超过 EF 6.x 的新应用程序,建议使用 EF Core。
  • EF Core 根据应用程序 DbContext 上的 DbSet<T> 属性存储应用程序中实体的内部表示形式以及它们如何映射到数据库。 EF Core 基于实体类本身和它们引用的任何其他实体构建模型。
  • 您可以通过添加 NuGet 数据库提供程序包将 EF Core 添加到您的应用程序中。您还应该安装 EF Core 的设计包。 这与 .NET 工具结合使用可生成迁移并将迁移应用到数据库。
  • EF Core 包含许多关于如何定义实体的约定,例如主键和外键。 您可以通过声明方式、使用 DataAnnotations 或使用流式 API 自定义实体的定义方式。
  • 您的应用程序使用 DbContext 与 EF Core 和数据库交互。您使用 AddDbContext<T> 将其注册到 DI 容器,定义数据库提供程序并提供连接字符串。 这使您的 DbContext 在整个应用程序的 DI 容器中可用。
  • EF Core 使用迁移来跟踪对实体定义的更改。 它们用于确保您的实体定义、EF Core 的内部模型和数据库架构都匹配。
  • 更改实体后,您可以使用 .NET 工具或使用 Visual Studio PowerShell cmdlet 创建迁移。
  • 要使用 .NET CLI 创建新迁移,请在项目文件夹中运行 dotnet ef migrations add NAME,其中 NAME 是您要为迁移指定的名称。 这会将您当前的 DbContext 快照与之前的版本进行比较,并生成必要的 SQL 语句来更新您的数据库。
  • 您可以使用 dotnet ef database update 将迁移应用到数据库。如果数据库尚不存在,这将创建数据库并应用任何未完成的迁移。
  • EF Core 在创建迁移时不会与数据库交互,只有在您显式更新数据库时才会与数据库交互,因此您仍然可以在离线时创建迁移。
  • 您可以通过创建新实体 e、在应用程序的数据上下文 _context 的实例上调用 _context.Add(e) 并调用 _context.SaveChangesAsync() 来将实体添加到 EF Core 数据库。 这会生成必要的 SQL INSERT 语句以将新行添加到数据库中。
  • 您可以使用应用程序 DbContext 上的 DbSet<T> 属性从数据库加载记录。 它们公开了 IQueryable 接口,因此您可以使用 LINQ 语句在数据库中的数据返回之前对其进行过滤和转换。
  • 更新实体包括三个步骤:从数据库中读取实体、修改实体以及将更改保存到数据库。 EF Core 将跟踪哪些属性已更改,以便优化它生成的 SQL。
  • 可以使用 Remove 方法删除 EF Core 中的实体,但应仔细考虑是否需要此功能。 通常,在实体上使用 IsDeleted 标志的软删除技术更安全且更易于实现。
  • 本章仅涵盖在应用程序中使用 EF Core 时必须考虑的部分问题。 在生产应用程序中使用它之前,您应该考虑为字段生成的数据类型、验证、如何处理并发、初始数据的播种、处理正在运行的应用程序上的迁移以及处理 Web 中的迁移。 农场场景。
posted @ 2023-01-20 10:29  F(x)_King  阅读(826)  评论(0编辑  收藏  举报