第1章 Entity Framework Core 简介

发布时间 2023-12-27 10:38:01作者: 生活的倒影

第 1 部分 入门

数据无处不在,每年以 PB 的速度增长,其中很多数据存储在数据库中。数以百万计的应用程序也在那里——2021 年初,有 12 亿个网站——其中大部分需要访问数据库中的数据。而且我还没有开始考虑物联网。因此,领先的研究和咨询公司 Gartner 表示,到 2021 年全球 IT 支出将达到 3.7 万亿美元 (http://mng.bz/gonl),这不足为奇。

对您来说,好消息是您的技能将很受欢迎。但坏消息是,快速开发应用程序的压力是无情的。本书介绍了一种可用于快速编写数据库访问代码的工具:Microsoft 的 Entity Framework Core (EF Core)。 EF Core 提供了一种面向对象的方式来访问 .NET 环境中的关系和非关系 (NoSQL) 数据库。 EF Core 和其他 .NET Core 库的酷炫之处在于它们可以在 Windows、Linux 和 Apple 平台上运行,而且速度很快。

在第 1 部分中,我让您直接进入代码。在第 1 章中,您将构建一个超级简单的控制台应用程序,到第 5 章结束时,您将构建一个相当复杂的销售书籍的 Web 应用程序。第 2 章和第 3 章分别解释了将数据读取和写入关系数据库,第 4 章介绍了编写业务逻辑。在第 5 章中,您将使用 Microsoft 的 ASP.NET Core Web 框架来构建示例图书销售网站。第 6 章通过一系列解决数据库问题的有用技术(例如在数据库中快速复制数据的方法)扩展您对 EF Core 内部工作原理的了解。

在第 1 部分中你将有很多内容要学习,虽然我跳过了一些主题,而且主要是依靠大量 EF Core 的默认设置。尽管如此,第 1 部分应该让您很好地理解 EF Core 可以做什么,后面的部分通过额外的 EF Core 功能增加您的知识,关于如何配置 EF Core 的更多详细信息,以及专门介绍性能调整等特定领域的章节。

第 1 章 Entity Framework Core 简介

本章要点

  • 了解 EF Core 应用程序的剖析
  • 使用 EF Core 访问和更新数据库
  • 探索实际的 EF Core 应用程序
  • 决定是否在应用程序中使用 EF Core

Entity Framework Core 或 EF Core 是一个库,软件开发人员可以使用它来访问数据库。构建此类库的方法有很多,但 EF Core 被设计为对象关系映射器 (O/RM)。 O/RM 通过在两个世界之间进行映射来工作:具有自己的 API 的关系数据库,以及类和软件代码的面向对象软件世界。 EF Core 的主要优势是允许软件开发人员使用您可能比 SQL 更熟悉的语言快速编写数据库访问代码。

EF Core 支持多平台:它可以在 Windows、Linux 和 Apple 上运行。它作为 .NET Core 计划的一部分执行此操作——因此是 EF Core 名称的核心部分。

.NET 5 涵盖了桌面、Web、云、移动、游戏、物联网 (IoT) 和人工智能 (AI) 的整个范围,但本书侧重于 EF Core。

EF Core 不是实体框架的第一个版本;现有的非核心实体框架库称为 EF6.x。 EF Core 从这些早期版本(4 到 6.x)的反馈中内置了多年的经验。它保留了与 EF6.x 相同类型的接口,但在底层进行了重大更改,例如处理非关系数据库的能力,而 EF6.x 并非旨在这样做。在 EF Core 出现之前,我曾在许多应用程序中使用过 EF5 和 EF6,这让我看到了 EF Core 在功能和性能方面相对于 EF6.x 的显着改进。

本书适用于已经在使用 EF Core 的软件开发人员、从未使用过 Entity Framework 的开发人员,以及想要迁移到 EF Core 的经验丰富的 EF6.x 开发人员。我假设您熟悉使用 C# 进行 .NET 开发,并且至少对什么是关系数据库有所了解。我不假设您知道如何编写结构化查询语言 (SQL),这是大多数关系数据库使用的语言,因为 EF Core 可以为您完成大部分工作。但我确实展示了 EF Core 生成的 SQL,因为它可以帮助您了解正在发生的事情;使用某些 EF Core 高级功能需要您具备 SQL 知识,但本书提供了大量图表来帮助您。

提示:如果您不太了解 SQL 并想了解更多信息,我建议使用 W3Schools 在线资源:https://www.w3schools.com/sql/sql_intro.asp。 SQL 命令集非常庞大,而 EF Core 查询仅使用一小部分(例如 SELECT、WHERE 和 INNER JOIN),因此资源是一个很好的起点。

本章通过使用调用 EF Core 库的小型应用程序向您介绍 EF Core。您将深入了解 EF Core 如何解释软件命令和访问数据库。概述 EF Core 内部发生的事情将有助于您阅读本书的其余部分。

1.1 你将从本书中学到什么

本书向您介绍了 EF Core,从基础知识开始,逐步介绍 EF Core 的一些更复杂的部分。要充分利用本书,您应该熟悉使用 C# 开发应用程序,包括创建项目和加载 NuGet 包。你将学习

  • 使用 EF Core 访问数据库的基础知识
  • 如何在 ASP.NET Core Web 应用程序中使用 EF Core
  • 您可以通过多种方式配置 EF Core 以完全按照您的需要工作
  • 您可能想要使用的一些更深层次的数据库功能
  • 随着应用程序的增长,如何处理数据库布局的变化
  • 如何提高数据库代码的性能
  • 最重要的是,如何确保您的代码正常工作在本书中,我构建了简单但功能齐全的应用程序,以便您可以看到 EF Core 在实际情况下的工作情况。所有这些应用程序都可以通过示例存储库获得,其中还包括我作为合同开发人员和在我自己的项目中学到的许多技巧和技术。

1.2 我的 Entity Framework 的“高光时刻”

在我们进入细节之前,让我告诉您我在使用 Entity Framework 时的一个决定性时刻,它让我走上了拥抱 EF 的道路。在时隔 21 年后,是我的妻子让我重新开始编程(这本身就是一个故事!)。

我的妻子 Honora Smith 博士是南安普顿大学的数学讲师,专门研究医疗保健系统的建模,尤其关注医疗设施的位置。我曾与她一起构建了多个应用程序来为英国国家卫生服务局进行地理建模和可视化,并在南非工作以优化 HIV/AIDS 检测。

2013 年初,我决定构建一个专门用于医疗保健建模的 Web 应用程序。我使用了 ASP.NET MVC4 和 EF5,它们刚刚问世并支持处理地理数据的 SQL 空间类型。项目进展顺利,但工作很辛苦。我知道前端会很难;这是一个使用 Backbone.js 的单页应用程序,但令我惊讶的是我花了多长时间来完成服务器端工作。

我应用了良好的软件实践,并确保数据库和业务逻辑与问题空间相匹配——建模和优化卫生设施的位置。这很好,但我花了过多的时间编写代码来将数据库条目和业务逻辑转换为适合向用户显示的形式。此外,我使用存储库/工作单元模式来隐藏 EF5 代码,而且我不得不不断调整区域以使存储库正常工作。

在项目结束时,我总是回顾过去并问:“我可以做得更好吗?”作为一名软件架构师,我一直在寻找 (a) 运行良好,(b) 重复且应该自动化,或 (c) 存在持续问题的部分。这一次,名单如下:

  • 运行良好——ServiceLayer 是我应用程序中的一个层,它将应用程序的较低层与 ASP.NET MVC4 前端隔离/适应,运行良好。(我将在第 2 章中介绍这种分层体系结构。
  • 重复 — 我使用 ViewModel 类(也称为数据传输对象 (DTO) )来表示我需要向用户显示的数据。使用 ViewModel/DTO 效果很好,但是编写代码以将数据库表复制到 ViewModel/DTO 是重复且无聊的。(我也在第2章中谈到了ViewModels/DTO)。
  • 持续存在问题——存储库/工作单元模式对我不起作用。在整个项目中不断出现问题。(我将在第 13 章中介绍存储库模式和替代方案)。

作为审查的结果,我构建了一个名为 GenericServices (https://github.com/JonPSmith/GenericServices) 的库以与 EF6.x 一起使用。该库自动在数据库类和 ViewModels/DTO 之间复制数据,并且不再需要存储库/工作单元模式。它似乎运行良好,但为了对 GenericServices 进行压力测试,我决定在 Microsoft 的一个示例数据库上构建一个前端:AdventureWorks 2012 Lite 数据库。我在 10 天内借助前端 UI 库构建了整个应用程序!

1.3 针对现有 EF6.x 开发人员的一些话

为了节省时间,如果您还没有使用过 Entity Framework 6.x,则可以跳过此部分。

如果您了解 EF6.x,那么您将会熟悉 EF Core 的大部分内容。为了帮助您快速浏览本书,我添加了 EF6 注释。

请在整本书中注意 EF6 这样的注释。他们指出了 EF Core 与 EF6.x 不同的地方。另外请务必查看每章末尾的摘要,其中指出了本章中 EF6 和 EF Core 之间最大的变化。

我还会从我的 EF Core 学习之旅中给你一个提示。我很了解 EF6.x,但当我开始使用 EF Core 时,这些知识就成了一个问题。我当时使用 EF6.x 方法来解决问题,但没有注意到 EF Core 有解决问题的新方法。在大多数情况下,这些方法是相似的,但在某些领域,它们并非如此。

作为现有的 EF6.x 开发人员,我对您的建议是将 EF Core 视为某人为模仿 EF6.x 而编写的新库,但要了解它以不同的方式工作。这样,您就会对 EF Core 中新的和不同的做事方式保持警惕。

1.4 EF 核心概述

可以将 EF Core 用作在关系数据库与类和软件代码的 .NET 环境之间映射的 O/RM。表 1.1 显示了 EF Core 如何映射关系数据库和 .NET 软件的两个世界。

表 1.1 数据库与 .NET 软件之间的 EF Core 映射

关系数据库

.NET 软件

Table

.NET class

Table columns

Class 属性/字段

Rows

.NET 集合中的元素 — 例如, List

主键:唯一行

唯一的类实例

外键:定义关系

对另一个类的引用

SQL——例如,Where

.NET LINQ——例如,Where(p => ...

1.4.1 O/RM 的缺点

制作好的 O/RM 很复杂。尽管 EF6.x 或 EF Core 看起来很容易使用,但有时 EF Core 的“魔力”会让您大吃一惊。在我们深入了解 EF Core 的工作原理之前,让我提一下需要注意的两个问题。

第一个问题是对象关系不匹配。数据库服务器和面向对象的软件使用不同的原则;数据库使用主键来定义行是唯一的,而 .NET 类实例在默认情况下被认为是唯一的。 EF Core 为您处理大部分不匹配的情况,但您的 .NET 类获得主键和外键,这是仅数据库需要的额外数据。您的纯软件版本的类不需要这些额外的属性,但数据库需要。

第二个问题是一个O/RM——尤其是像EF Core这样全面的O/RM——是与第一个问题相反的。 EF Core 很好地“隐藏”了数据库,以至于您有时会忘记下面的数据库。此问题可能导致您编写的代码在 C# 中运行良好但不适用于数据库。一个示例是通过组合类中的 FirstName 和 LastName 属性,使表达式 body 属性返回一个人的全名,例如

public string FullName => $"{FirstName} {LastName}";

表达式主体属性(例如刚刚显示的那个)在 C# 中是正确的做法,但如果您尝试对该属性进行筛选或排序,则同一属性会引发异常,因为 EF Core 需要表中的 FullName 列,以便它可以在数据库级别应用 SQL WHERE 或 ORDER 命令。

这就是我在本章中花时间展示 EF Core 如何在内部工作以及它生成的 SQL 的原因。您对 EF Core 的功能了解得越多,您就越有能力编写良好的 EF Core 代码,而且——更重要的是——您将知道当您的代码不起作用时该怎么做。

注意:在整本书中,我使用“让它工作,但如果需要的话准备好让它更快”的方法来使用 EF Core。 EF Core 允许我快速开发,但我知道由于 EF Core 或我对它的不当使用,我的数据库访问代码的性能可能不足以满足特定的业务需求。第 5 章介绍了如何隔离您的 EF Core,以便您可以对其进行调优以将副作用降到最低,第 15 章介绍了如何查找和改进速度不够快的数据库代码。

1.5 什么是 NoSQL ?

谈及关系数据库就不得不提非关系数据库,也就是俗称的 NoSQL 。关系数据库和非关系数据库都在现代应用程序中发挥作用。我在同一应用程序中同时使用了 SQL Server(关系数据库)和 Azure Tables(非关系数据库)来处理两种业务需求。

EF Core 处理关系数据库和非关系数据库——与 EF6.x 不同,EF6.x 仅围绕关系数据库设计。本书涵盖的大多数 EF Core 命令都适用于这两种类型的数据库,但关系数据库和 NoSQL 数据库之间在数据库级别存在一些差异,因此省略了一些更复杂的数据库命令以提高可扩展性和性能。

EF Core 3.0 为 Azure NoSQL 数据库添加了一个名为 Cosmos DB 的数据库提供程序,我在第 16 章中介绍了它。在那一章中,我指出了关系数据库和 Cosmos DB 之间的区别;我对我的发现感到惊讶。既然 EF Core 已经被修改以处理 NoSQL 数据库,我预计将会编写更多的 NoSQL 数据库提供程序。

注意:与 SQL 数据库相比,Cosmos DB 和其他 NoSQL 数据库具有许多优势。例如,在世界各地拥有多个 NoSQL 数据库副本要容易得多,这使用户可以更快地访问,而且如果一个数据中心出现故障,其他副本可以接管负载。但是与 SQL 数据库相比,NoSQL 数据库也有一些局限性;阅读第 16 章,深入分析 Cosmos DB 的优势和局限性。

1.6 您的第一个 EF Core 应用程序

在本章中,你将从一个简单的示例开始,以便我们可以专注于 EF Core 正在执行的操作,而不是代码正在执行的操作。对于此示例,您将使用一个名为 MyFirstEfCoreApp 的小型控制台应用程序,该应用程序访问一个简单的数据库。MyFirstEfCoreApp 应用程序的工作是在提供的数据库中列出和更新书籍。图 1.1 显示了控制台输出。

图1.1 您将使用控制台应用程序的输出来查看EF Core的工作原理

这个应用程序不会因为它的界面或复杂性而赢得任何奖项,但它是一个很好的开始,特别是因为我想向您展示 EF Core 内部是如何工作的,以帮助您了解本书后面的内容。

您可以从 Git repo 的 Chapter01 分支下载此示例应用程序,网址为 http://mng.bz/XdlG。您可以查看代码并运行应用程序。为此,您需要软件开发工具。

1.6.1 需要安装的内容

您可以使用两种主要的开发工具来开发 .NET Core 应用程序:Visual Studio 2017(VS 2017)或 Visual Studio Code(VSCode)。我描述了在您的第一个应用程序中使用 VS 2017,因为它对于 .NET 开发的新手来说稍微容易一些。

您需要从 www.visualstudio.com 安装 Visual Studio 2017(VS 2017)。有许多版本,包括免费社区版本,但您需要阅读许可证以确保符合资格;请参见 www.visualstudio.com/vs/community/

安装 VS 2017 时,请确保包含 .NET 核心跨平台开发功能,该功能位于安装工作负载阶段的其他工具集部分下。这将在系统上安装 .NET Core。然后,您就可以构建 .NET Core 应用程序。请参考 http://mng.bz/2x0T 了解更多信息。

如果你想使用 VS Code,它是免费的,你可以从 https://code.visualstudio.com 下载它。您将需要在您的系统上进行更多设置,例如在您的计算机上安装最新的 .NET Core SDK 和 localdb SQL Server。正如我所说,如果您不熟悉在 Microsoft 系统中进行编码,我建议您使用 Windows 上的 Visual Studio,因为它为您设置了很多东西。

一个版本的 Visual Studio 运行在 Apple Macintosh 机器上,而多个版本的 VS Code 运行在 Windows、Mac 和 Linux 中。如果要运行任何应用程序或单元测试,您的系统上必须有一个 SQL Server 实例。您可能需要更改应用程序和单元测试项目的连接字符串中的服务器名称。

您可以使用 Visual Studio 的内置测试资源管理器(可从“测试”菜单访问)运行单元测试。如果你使用的是 VS Code,测试运行器也是内置的,但你需要在 VS Code tasks.json 文档中设置构建和测试任务,它允许你通过 Task > Test 命令运行所有测试.

1.6.2 使用 EF Core 创建自己的 .NET Core 控制台应用程序

我知道许多开发人员喜欢创建自己的应用程序,因为自己构建代码意味着您确切地知道涉及的内容。本节详细介绍如何使用 Visual Studio 创建 .NET 控制台应用程序 MyFirstEfCoreApp。

创建 .NET Core 控制台应用程序

Visual Studio 有大量教程,您可以在 http://mng.bz/e56z 找到创建 C# 控制台应用程序的示例。

提示:您可以通过从主菜单中选择“Project”>“MyFirstEfCoreApp 属性”来了解您的应用程序使用的 .NET 版本;应用程序选项卡显示目标框架。某些版本的 EF Core 需要特定版本的 .NET Core。

将 EF Core 库添加到您的应用程序

您可以通过多种方式安装 NuGet 库。更直观的方法是使用 NuGet 包管理器;您可以在 http://mng.bz/pVeG 找到教程。对于此应用程序,您需要应用程序要访问的数据库的 EF Core 包。在本例中,您选择 Microsoft.EntityFrameworkCore.SqlServer NuGet 包,因为它将使用您在安装 Visual Studio 时安装的开发 SQL Server。

您需要查看的另一件事是您将要安装的 NuGet 包的版本号。 EF Core 的构建使得每个主要版本都有自己的编号。例如,版本号 5.1.3 表示 EF Core 主要版本 5、次要版本 1 和补丁(错误修复)版本 3。通常,您需要在不同的项目中加载不同的 EF Core 包。例如,您可以在数据层中加载 Microsoft.EntityFrameworkCore,在 Web 应用程序中加载 Microsoft.EntityFrameworkCore.SqlServer。如果您需要这样做,您应该尝试使用项目属性中列出的具有相同 Major.Minor.Patch 的 NuGet 包。如果未找到匹配项,请确保 NuGet Major.Minor 版本与您的项目版本匹配。

从 Git 存储库下载并运行示例应用程序
有两个选项可用于在 Git 存储库中下载和运行 MyFirstEfCoreApp 控制台应用程序:Visual Studio 或 VS Code。您可以在 http://mng.bz/OE0n 找到另一个Visual Studio教程“从存储库打开项目”。与本书相关的存储库是 http://mng.bz/XdlG
请务必选择正确的分支。Git 存储库具有允许在不同版本的代码之间切换的分支。在这本书中,我创建了三个主要分支:master,它包含第 1 部分(第 1-6 章)的代码;第 2 部分,包含第 2 部分(第 7-11 章)的代码;第 3 部分,其中包含第 3 部分的代码(第 12-17 章)。
默认情况下,存储库将在主分支中打开,因此不习惯 Git 的人可以立即开始使用。每个分支中的自述文档都有关于您需要安装的内容和可以运行的内容的更多信息。

1.7 MyFirstEfCoreApp 将访问的数据库

EF Core 与访问数据库有关,但该数据库从何而来? EF Core 为您提供了两种选择:EF Core 可以为您创建它,即所谓的代码优先方法,或者您可以提供您在 EF Core 之外构建的现有数据库,即所谓的数据库优先方法。本书的第一部分使用代码优先,因为这是许多开发人员使用的方法。

EF6: 在 EF6 中,您可以使用 EDMX/数据库设计器以可视化方式设计您的数据库,这一选项称为设计优先。 EF Core 不以任何形式支持这种设计优先的方法,也没有添加它的计划。

在本章中,我们不会学习如何创建数据库。为了允许 MyFirstEfCoreApp 应用程序运行,代码将创建数据库并添加测试数据(如果不存在现有数据库)。

注意:在我的代码中,我使用用于单元测试的基本 EF Core 命令来创建数据库,因为它简单快捷。第 5 章介绍了如何让 EF Core 正确创建数据库,第 9 章介绍了整个问题

创建和更改数据库结构的过程,称为数据库模式。

对于这个 MyFirstEfCoreApp 应用程序示例,我创建了一个简单的数据库,如图 1.2 所示,只有两个表:

  •  包含图书信息的 Books 表
  •  包含每本书作者的作者表

高级说明:在此示例中,我让 EF Core 使用其默认配置设置命名表。Books 表名称来自图 1.5 中所示的 DbSet Books 属性。作者表名称在图 1.5 中没有 DbSet 属性,因此 EF Core 使用类的名称。

 

图1.2 具有两个表的示例关系数据库:Books和Author

图1.3显示了数据库的内容。它只有四本书,前两本都是同一作者马丁·福勒。

图1.3 数据库的内容,显示了四本书,其中两本书的作者相同

1.8 设置 MyFirstEfCoreApp 应用程序

创建并设置 .NET 控制台应用程序后,现在可以开始编写 EF Core 代码。在创建任何数据库访问代码之前,您需要编写两个基本部分:

  • 希望 EF Core 映射到数据库中的表的类
  • 应用程序的 DbContext,这是用于配置和访问数据库的主要类

1.8.1 映射到数据库Book 和 Author的类

EF Core 将类映射到数据库表。因此,您需要创建一个类,该类将定义数据库表或匹配数据库表(如果您已有数据库)。存在许多规则和配置(在第 7 章和第 8 章中介绍),但图 1.4 给出了映射到数据库表的类的典型格式。

图1.4 左侧的.NET类Book映射到右侧名为Books的数据库表。这是构建应用程序的一种典型方式,其中包含映射到数据库表的多个类。

清单 1.1 显示了你将使用的另一个类:Author。此类与图 1.4 中的 Book 类具有相同的结构,其主键遵循 Id 的 EF Core 命名约定(请参阅第 7.3.5 节)。Book 类还具有 Author 类型的导航属性和与作者的主键匹配的名为 AuthorId 的 int 类型属性。这两个属性告知 EF Core 你想要从 Book 类链接到作者类,并且 AuthorId 属性应用作外键来链接数据库中的两个表。

清单1.1 MyFirstEfCoreApp中的Author类

public class Author
{
    public int AuthorId { get; set; }     //保存数据库中Author行的主键。注意Book类中的外键具有相同的名称。
    public string Name { get; set; } 
    public string WebUrl { get; set; }
}

1.8.2 应用程序的DbContext

应用程序的另一个重要部分是 DbContext,这是一个从 EF Core 的 DbContext 类继承的类。此类包含 EF Core 配置该数据库映射所需的信息,也是代码中用于访问数据库的类(请参阅第 1.9.2 节)。图 1.5 显示了 MyFirstEfCoreApp 控制台应用程序使用的应用程序的 DbContext,称为 AppDbContext。

 

图1.5 为MyFirstEfCoreApp控制台应用程序创建的应用程序DbContext的两个主要部分。首先,设置数据库选项,以定义要使用的数据库类型以及可以在哪里找到数据库。其次,DbSet<T>属性告诉EF Core哪些类应该映射到数据库。

在我们的小型示例应用程序中,所有关于建模的决定都是由 EF Core 完成的,它通过使用一组约定来解决问题。您有很多额外的方式来告诉 EF Core 数据库模型是什么,这些命令可能会变得很复杂。它需要第 7 章、第 8 章和第 10 章的一部分来涵盖您作为开发人员可用的所有选项。

此外,您使用标准方法在控制台应用程序中定义数据库访问:重写应用程序 DbContext 中的 OnConfiguring 方法并提供 EF Core 定义数据库类型和位置所需的所有信息。这种方式的缺点是它有一个固定的连接字符串,这给开发和单元测试带来了困难。

对于 ASP.NET Core Web 应用程序,这个问题更大,因为您想要访问本地数据库进行测试,而在生产中运行时则访问不同的托管数据库。在第 2 章中,当您开始构建 ASP.NET Core Web 应用程序时,您将使用一种不同的方法来更改数据库字符串(请参阅第 2.2.2 节)。

1.9 深入了解 EF Core

运行 MyFirstEfCoreApp 应用程序后,现在可以使用它来查看 EF Core 库的工作原理。重点不在于应用程序代码,而在于将数据读取和写入数据库时 EF Core 库内发生的情况。我的目标是为你提供 EF Core 如何访问数据库的心智模型。这个模型应该会有所帮助,因为你深入研究了本书其余部分描述的无数命令。

是否真的需要了解 EF Core 内部的工作方式才能使用它?
可以使用 EF Core 库,而无需费心了解其工作原理。但是,了解 EF Core 内部发生的情况将有助于你了解各种命令的工作方式。当您需要调试数据库访问代码时,您还将更好地武装。
以下页面包含大量说明和关系图,用于演示 EF Core 内部发生的情况。EF Core “隐藏”数据库,以便作为开发人员可以轻松编写数据库访问代码,这在实践中效果很好。但如前所述,了解 EF Core 的工作原理可以帮助你执行更复杂的操作,或者事情没有按预期方式工作。

1.9.1 数据库建模

在对数据库执行任何操作之前,EF Core 必须经历一个我称为数据库建模的过程。此建模是 EF Core 通过查看类和其他 EF Core 配置数据来确定数据库外观的方法。然后,EF Core 在所有数据库访问中使用生成的模型。

建模过程在您首次创建应用程序的 DbContext 时启动,在本例中称为 AppDbContext(如图 1.5 所示)。它有一个属性 DbSet,这是代码访问数据库的方式。

图 1.6 提供了建模过程的概述,可帮助你了解 EF Core 用于对数据库建模的过程。后面的章节将向您介绍一系列命令,这些命令允许您更精确地配置数据库,但现在,您将使用默认配置。

图 1.6 该图显示了 EF Core 将如何创建类映射到的数据库模型。首先,它查看通过 DbSet 属性定义的类;然后查找对其他类的所有引用。使用这些类,EF Core 可以计算出数据库的默认模型。但是,它会在应用程序的 DbContext 中运行 OnModelCreate 方法,您可以重写该方法以添加特定命令以按照所需方式配置数据库。

图 1.6 显示了 EF Core 在我们的 AppDbContext 上使用的建模步骤,这发生在您第一次创建 AppDbContext 的实例时。 (之后,模型被缓存,以便快速创建后续实例。)以下文本提供了对该过程的更详细描述:

  • EF Core 查看应用程序的 DbContext 并找到所有公共 DbSet 属性。根据这些数据,它定义了它找到的一个表的初始名称:Books。
  • EF Core 查看 DbSet 中引用的所有类并查看其属性以计算出列名、类型等。它还会查找类的特殊属性和/或提供额外建模信息的属性。
  • EF Core 查找 DbSet 类引用的任何类。在我们的示例中,Book 类引用了 Author 类,因此 EF Core 也会扫描该类。它对 Author 类的属性执行与在步骤 2 中对 Book 类执行的相同的搜索。它还采用类名 Author 作为表名。
  • 对于建模过程的最后输入,EF Core 在应用程序的 DbContext 中运行虚拟方法 OnModelCreating。在这个简单的应用程序中,您不会重写 OnModelCreating 方法,但如果您重写了,您可以通过流畅的 API 提供额外信息,以对建模进行更多配置。
  • EF Core 基于它收集的所有信息创建数据库的内部模型。此数据库模型已缓存,以便以后访问更快。然后该模型用于执行所有数据库访问。

你可能已经注意到图 1.6 没有显示数据库。这是因为当 EF Core 构建其内部模型时,它不会查看数据库。我强调这个事实是为了表明为您想要的数据库构建一个好的模型是多么重要;否则,如果 EF Core 认为数据库的样子与实际数据库的样子不匹配,就会出现问题。

在您的应用程序中,您可以使用 EF Core 创建数据库,在这种情况下不会出现不匹配的情况。即便如此,如果您想要一个良好且高效的数据库,那么在您的代码中构建您想要的数据库的良好表示是值得的,以便创建的数据库表现良好。用于创建、更新和管理数据库结构的选项是一个很大的话题,详见第 9 章。

1.9.2 从数据库中读取数据

现在,您已经可以访问数据库了。控制台应用程序有一个列表 (l) 命令,该命令读取数据库并在终端上打印信息。图 1.7 显示了运行控制台应用程序并键入 l 的结果。

图1.7 列出数据库内容时控制台应用的输出

下面的清单显示了被调用以列出所有书籍的代码,以及每个作者,输出到控制台。

清单 1.2 读取所有书籍并输出到控制台的代码

public static void ListAll()
{
    using (var db = new AppDbContext())  //创建应用进程的 DbContext,通过该上下文完成所有数据库访问。
    {
        foreach (var book in
        db.Books.AsNoTracking()  //阅读所有书籍。AsNoTracking 指示此访问是只读的。
            .Include(book => book.Author))  //包含会导致作者信息随每本书一起加载。有关详细信息,请参阅第 2 章。
        {
            var webUrl = book.Author.WebUrl == null ? "no web URL given -"
                : book.Author.WebUrl; Console.WriteLine( $"{book.Title} by {book.Author.Name}"); 
            Console.WriteLine("	" + "Published on " +  $"{book.PublishedOn:dd-MMM-yyyy}" +  $". {webUrl}");
        }
    }
}

EF Core 使用 Microsoft 的 .NET 语言集成查询 (LINQ) 来执行它想要完成的命令,并使用普通的 .NET 类来保存数据。清单 1.2 的查询不包含任何 LINQ 方法,但在本书的后面,您将看到大量的 LINQ 示例。

注意:学习 LINQ 对您来说至关重要,因为 EF Core 使用 LINQ 命令进行数据库访问。附录简要介绍了 LINQ。还提供大量在线资源;参见 http://mng.bz/YqBN

清单 1.2 中下面的两行代码导致了数据库访问。现在让我们看看 EF Core 如何使用该 LINQ 代码访问数据库并返回所需的书籍及其作者。图 1.8 沿着这些代码行进入 EF Core 库,通过数据库,然后返回。

图 1.8 EF Core 执行数据库查询时的内部视图

从数据库中读取数据的过程如下:

  • 查询 db.Books.AsNoTracking().Include(book => book.Author) 访问应用程序 DbContext 中的 DbSet 属性,并在末尾添加一个 .Include (book => book.Author) 来询问关系的作者部分也被加载。这由数据库提供程序转换为 SQL 命令以访问数据库。如果再次使用相同的数据库访问,生成的 SQL 将被缓存以避免重新转换的成本。

EF Core 试图尽可能高效地访问数据库。在这种情况下,它将需要读取的两个表 Books 和 Author 合并到一个大表中,以便它可以在一次数据库访问中完成这项工作。以下清单显示了由 EF Core 和数据库提供程序创建的 SQL。

清单 1.3 生成的读取书籍和作者的 SQL 命令

SELECT [b].[BookId],
[b].[AuthorId],
[b].[Description],
[b].[PublishedOn],
[b].[Title],
[a].[AuthorId],
[a].[Name],
[a].[WebUrl]
FROM [Books] AS [b]
INNER JOIN [Author] AS [a] ON
[b].[AuthorId] = [a].[AuthorId]

数据库提供程序读取数据后,EF Core 将数据放入以下过程:(a) 创建 .NET 类的实例,以及 (b) 使用数据库关系链接(称为外键)通过以下方式正确链接 .NET 类reference——称为关系fixup。由于我们添加了 AsNoTracking 方法,因此出于速度原因,关系修正使用简化的修正。

注意:我在第 6.1.2 节中讨论了 AsNoTracking 简化关系修正和普通关系修正之间的区别。

结果是一组 .NET 类实例,其中 Book 的 Author 属性链接到包含作者信息的 Author 类。在这个例子中,两本书有相同的作者,Martin Fowler,所以有两个 Author 类的实例,都包含关于 Martin Fowler 的相同信息。

因为代码包含命令 AsNoTracking,EF Core 知道禁止创建跟踪快照。跟踪快照用于发现数据更改,正如您将在 1.9.3 节编辑 WebUrl 数据库列的示例中看到的那样。因为这个查询是只读的,所以抑制跟踪快照可以使命令更快。

1.9.3 更新数据库

现在,你要使用 MyFirstEfCoreApp 中的第二个命令 update (u) 来更新《量子网络》一书的“作者”表中的 WebUrl 列。如图 1.9 所示,您首先列出所有书籍,以显示最后一本书没有设置作者 URL。然后运行命令 u,该命令会为上一本书《量子网络》请求新的作者 URL。你输入一个新的 URL httqs://entangled.moon(这是一个虚构的未来书籍,所以为什么不是一个虚构的 URL!),更新后,该命令再次列出所有书籍,显示作者的 URL 已更改(两个椭圆形显示前后 URL)。

图 1.9 此图显示了正在运行的更新。第一个命令是 l(列表),它在下一行显示每本书以及作者的姓名和 URL。然后按 u(更新),这将允许您更新上一本书作者的 URL。update 命令调用了 list 命令,以便您可以看到更新是否成功。

此处显示了用于更新“作者”表中的 WebUrl 列的代码,该表链接到标题为“量子网络”的书籍。

示例 1.4 更新《量子网络》一书作者WebURL的代码\

public static void ChangeWebUrl()
{
    Console.Write("New Quantum Networking WebUrl > "); 
    var newWebUrl = Console.ReadLine();  //从控制台读入新 URL
    using (var db = new AppDbContext())
    {
        var singleBook = db.Books
            .Include(book => book.Author)  //随书一起加载作者信息
            .Single(book => book.Title == "Quantum Networking");  //仅选择标题为“量子网络”的书籍
    
        singleBook.Author.WebUrl = newWebUrl;   //若要更新数据库,请更改读入的数据。
        //SaveChanges 告知 EF Core 检查对已读入的数据所做的任何更改,并将这些更改写出到数据库。
        db.SaveChanges();
        Console.WriteLine("... SavedChanges called.");
    }
 
    ListAll();  //列出所有书籍信息
}

图 1.10 显示了 EF Core 库内发生的情况并跟踪其进度。这个例子比前面的阅读例子要复杂得多,所以让我给你一些关于要寻找什么的指示。

图 1.10 此图显示了当您更新作者的 WebUrl 属性并要求 EF Core 将其写入数据库时,EF Core 在内部执行的操作。这个图相当复杂,但如果你从顶部开始,按照编号的文本,应该更容易理解。它从查询开始,以获得所需的书籍和作者。(请注意,在此过程中,存在跟踪快照;请参阅步骤 2。然后,当代码更新 WebUrl 并调用 SaveChanges 时,EF Core 会创建并执行正确的 SQL 命令,以更新正确行中的 WebUrl 列。

首先,位于图表顶部的读取阶段类似于读取示例,因此应该熟悉。在这种情况下,查询使用书籍的标题作为筛选器加载特定书籍。重要的变化是第 2 点:对数据进行跟踪快照。

此更改发生在关系图下半部分的更新阶段。在这里,可以看到 EF Core 如何将加载的数据与跟踪快照进行比较以查找更改。从此数据中,它看到只有 WebUrl 属性已更新,EF Core 创建一个 SQL 命令,以仅更新作者表正确行中的 WebUrl 列。

我已经描述了大部分步骤,但这里是作者的 WebUrl 列如何更新的逐一说明:

  1. 该应用程序使用 LINQ 查询查找带有作者信息的一本书。 EF Core 将 LINQ 查询转换为 SQL 命令以读取标题为 Quantum Networking 的行,返回 Book 和 Author 类的实例,并检查是否只找到一行。
  2. LINQ 查询不包括您在以前的读取版本中使用的 .AsNoTracking 方法,因此该查询被视为跟踪查询。因此,EF Core 会创建加载数据的跟踪快照。
  3. 然后,代码更改了本书作者类中的 WebUrl 属性。调用 SaveChanges 时,Detect Changes 阶段将从跟踪查询返回的所有类与跟踪快照进行比较。由此,它可以检测到发生了什么变化——在本例中,只有 Author 类的 WebUrl 属性,它的主键为 3。
  4. 当检测到更改时,EF Core 启动一个事务。每个数据库更新都是作为一个原子单元完成的:如果对数据库进行了多次更改,要么全部成功,要么全部失败。这个事实很重要,因为如果只应用部分更新,关系数据库可能会进入不良状态。
  5. 更新请求由数据库提供程序转换为执行更新的 SQL 命令。如果 SQL 命令成功,事务被提交,SaveChanges 方法返回;否则,会引发异常。

1.10 EF Core 的发展阶段

EF Core 和 .NET Core 自第一个版本发布以来已经走了很长一段路。随着时间的推移,微软一直在努力提高.NET Core的本机性能,同时添加更多功能,以至于.NET 5可以取代现有的.NET Framework 4.8。

图 1.11 显示了到目前为止 EF Core 主要版本的历史记录。EF 核心版本号遵循 NET 核心版本号。请注意,图顶部的版本是长期支持 (LTS) 版本,这意味着该版本在初始版本后三年内受支持。预计每年都会发布主要版本,LTS 每两年发布一次。

图 1.11 此图描述了 EF Core 的开发,它与 NET 开源开发人员平台的开发一起运行。突出显示 EF Core 5 版本,因为本书涵盖了 EF Core 5 之前的所有 EF Core 功能。

1.11 你应该在下一个项目中使用 EF Core 吗?

现在您已经快速了解了什么是 EF Core 及其工作原理,下一个问题是您是否应该开始在您的项目中使用 EF Core。对于计划切换到 EF Core 的任何人来说,关键问题是“EF Core 是否足以优于我目前使用的数据访问库,值得在我的下一个项目中使用它?”成本与学习和采用任何新库相关,尤其是像 EF Core 这样的复杂库,所以这个问题是有效的。这是我对 EF Core 和 .NET Core 的总体看法。

1.11.1 .NET 是未来的软件平台,而且速度很快!

随着时间的推移,微软一直在努力提高 .NET Core 的原生性能,同时添加更多功能。这种对性能的关注促使微软的 ASP.NET Core Web 应用程序从 ASP.NET MVC 的第 250 位上升到 ASP.NET Core 的第 10 到 40 位(取决于工作负载);参见 http://mng.bz/Gxaq。类似但较小的性能提升已添加到 EF Core。 微软确实表示 .NET 5 将接

替现有的 .NET Framework 4.8,但 COVID-19 的爆发稍微打乱了该计划,现在 .NET 6 将取代 .NET Framework 4.8。但是墙上的字迹很清楚:如果您正在开始一个新项目,并且 .NET 5 和 EF Core 具有您的项目所需的功能,那么迁移到 EF Core 意味着您不会落后。

1.11.2 开源开放交流

多年来,微软已经实现了自我转型。它的所有 .NET Core 工作都是开源的,有很多外部人员参与修复错误和添加新功能,因此您可以在需要时直接访问代码。

此外,关于 .NET Core 和其他产品中发生的事情的开放交流水平令人印象深刻。例如,EF Core 团队每周更新其工作,提供大量新版本的早期预览,并让所有人都可以使用每晚构建的 EF Core。团队认真对待反馈,所有工作和缺陷都显示在 EF Core 存储库的问题页面中。

1.11.3 多平台应用与开发

正如我在本章开头所说的,EF Core 具有多平台功能;您可以在 Windows、Linux 和 Apple 上开发和运行 EF Core 应用程序。这意味着您可以在便宜的 Linux 系统上运行基于 Microsoft 的应用程序。此外,开发不同的平台也是很有可能的。事实上,作为 EF Core 团队首席工程师之一的 Arthur Vickers 决定从 Windows 转移到 Linux 作为他的主要开发平台。您可以在 http://mng.bz/zxWa 上了解他的经历。

1.11.4 快速发展和良好的特性

我的日常工作是合同开发人员。在典型的数据驱动应用程序中,我编写了很多数据库访问代码,其中一些代码很复杂。有了 EF Core,我可以非常快速地编写数据访问代码,并且可以让访问代码易于理解,如果速度太慢也可以重构。这是我使用 EF Core 的主要原因。

同时,我需要一个具有很多功能的 O/RM,这样我就可以按照我想要的方式构建数据库,而不会在 EF Core 中遇到太多障碍。当然,有些事情被排除在外,例如构建 SQL 公用表表达式,但是如果我需要的话,一些原始 SQL 可以解决类似的问题。

1.11.5 支持良好

EF Core 有很好的文档 (https://docs.microsoft.com/en-us/ef/core/index),当然,你有这本书,它汇集了文档、更深入的解释和示例,以及模式和实践使你成为一个伟大的开发者。互联网上到处都是关于 EF Core 的博客,包括我的 https://www.thereformedprogrammer.net。对于问题和错误,总是有 Stack Overflow;参见 http://mng.bz/0mDx

支持的另一部分是开发工具。微软似乎已经通过提供对多平台的支持改变了重点,但它也创建了一个名为 VS Code 的免费跨平台开发环境。微软还向个人开发人员和小型企业免费提供其主要开发工具 Visual Studio(Windows 和 Mac)。

1.11.6 始终保持高性能

啊,数据库性能问题。听着,我并不是说 EF Core 会开箱即用,通过漂亮的 SQL 和快速的数据摄取产生极好的数据库访问性能。这就是您为快速开发数据访问代码而付出的代价; EF Core 中的所有“魔法”不如手工编码的 SQL,但您可能会惊讶于它的出色程度。请参阅第 15 章,其中我逐步调整了应用程序的性能。

但是您有很多选择来提高应用程序的性能。在我的应用程序中,我发现只有大约 5-10% 的查询是需要手动调整的关键查询。第 14 章和第 15 章专门介绍性能调优,这也是第 16 章的一部分。这些章节表明您可以做很多事情来提高 EF Core 数据库访问的性能。

但是对于某些数据库访问,您没有理由不能使用原始 SQL。这是很棒的事情:使用 EF Core 快速构建应用程序,然后通过 ADO.NET 或 Dapper 将 EF Core 无法提供良好性能的(少数)地方转换为原始 SQL 命令。

1.12 什么时候不应该使用 EF Core?

我显然是 EF Core 的拥护者,但我不会在客户端项目中使用它,除非使用它有意义。因此,让我们看看一些可能建议不要使用 EF Core 的情形。

第一个很明显:它是否支持您要使用的数据库?您可以在 https://docs.microsoft.com/en-us/ef/core/providers 找到支持的数据库列表。第二个因素是您需要的性能水平。例如,如果您正在编写小型 RESTful 服务或无服务器系统,我不确定引入整个 EF Core 是否值得;你可以使用一个快速但开发时间紧迫的库,因为没有太多的数据库访问可以写。但是,如果您有一个大型应用程序,有很多无聊的管理员访问权限和一些重要的面向客户的访问权限,那么混合方法可能适合您。(有关混合 EFCore/Dapper 应用程序的示例,请参阅第 15 章。)

此外,EF Core 并不擅长批量命令。通常,批量加载大量数据和删除表中的所有行等任务可以通过原始 SQL 更快地实现。但是几个 EF Core 批量 CRUD 扩展(一些是开源的,一些是付费的)可以提供帮助;尝试搜索 EF Core 批量加载以查找可能的库。

总结

  • EF Core 是一个对象关系映射器 (O/RM),它使用 Microsoft 的语言集成查询 (LINQ) 来定义数据库查询并将数据返回到 .NET 类的链接实例。
  • EF Core 旨在使编写代码以快速直观地访问数据库。这个 O/RM 有很多特性可以满足很多需求。
  • 你已经看到了 EF Core 内部发生的事情的各种例子。这些示例将帮助您了解后面章节中描述的 EF Core 命令可以做什么。
  • 考虑使用 EF Core 有很多充分的理由:它创建在大量经验之上,得到很好的支持,并且可以在多个平台上运行。

对于熟悉 EF6.x 的读者:

  • 在整本书中查找 EF6 注释。这些说明标记了 EF Core 方法和 EF6.x 方法之间的差异。此外,请查看每章末尾的摘要,这将向您指出该章中 EF Core 的主要更改。
  • 将 EF Core 视为某人为模仿 EF6.x 而编写但以不同方式工作的新库。这种心态将帮助您发现 EF Core 的改进,这些改进改变了您访问数据库的方式。
  • EF Core 不再支持早期形式的 EF 使用的 EDMX/数据库设计器方法。