阅读本文大概须要 11 分钟。数据库
原文:https://bit.ly/2UMiDLb
做者:Jon P Smith
翻译:王亮
声明:我翻译技术文章不是逐句翻译的,而是根据我本身的理解来表述的。其中可能会去除一些本人实在不知道如何组织但又不影响理解的句子。编程
本文将为你详细描绘 EF Core 从数据库中读取数据的“幕后”视图。我将揭开两种数据库读取方式的面纱:一个是普通的查询,另外一个是使用 AsNoTracking 方法的非跟踪查询。我还将经过一个实验来演示我是如何解决个人一个客户遇到的性能问题。服务器
我假设你对 EF Core 已经有了必定的认识,但在深刻学习以前,咱们先来了解一下如何使用 EF Core,以确保咱们已经掌握了一些基本知识。这是一个“深刻研究”的课题,因此我准备大量的技术细节,但愿个人描述方式你能理解。性能
本文是“深刻理解 EF Core”系列中的第一篇。如下是本系列文章列表:单元测试
提示:若是你已经对 EF Core 有必定的认识,那么你能够跳过这一节,这部分只是一个如何读取数据库的例子。学习
为了能让你更好地理解,我先描述一个数据库结构,而后再给出一个简单的数据库读取示例。下面是一些基本表的结构和它们之间的关系。测试
这些表被映射到具备相似名称的类,例如 Book、BookAuthor、Author,这些类的属性名称与表的字段名称相同。因为篇幅有限,我不打算展开来说这些类,但您能够在个人 GitHub 仓库[1]中查看这些类。spa
EF Core 读取数据库须要下面五部分:翻译
下面的单元测试代码来自个人 GitHub 创库[2],展现了一个简单的示例,它从现有数据库中读取 4 个 Book 实体及其关联的 BookAuthor 和 Authors 实体。3d
[Fact] public void TestBookCountAuthorsOk() { //SETUP var options = SqliteInMemory.CreateOptions<EfCoreContext>(); //code to set up the database with four books, two with the same Author using (var context = new EfCoreContext(options)) { //ATTEMPT var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList(); //VERIFY books.Count.ShouldEqual(4); books.SelectMany(x => x.AuthorsLink.Select(y => y.Author)) .Distinct().Count().ShouldEqual(3); } }
如今,若是咱们将单元测试代码对应到上面的 5 部分,结果是这样的:
SqliteInMemory.CreateOptions
方法,它使用个人一个 NuGet 包 EfCore.TestSupport 建立了一个内存数据库(内存中的数据库对于单元测试很是有用,由于你能够为这个测试创建一个新的空数据库)。.Books
表示您但愿访问 Books 表。全部这一切查询出来是一个结果集,其中有普通属性,像 Books 的 Title 属性;有关联实体类的导航属性,像 Books 的 AuthorsLink 属性。
这个示例称为查询或读取,也是四种数据库访问类型之一,即 CRUD(新增、读取、更新和删除)。我将在下一篇文章中介绍新增和更新。
当你查询数据库时,EF Core 会将数据库返回的数据转换为实体类并填充导航属性的值。在本节中,咱们将研究两种类型的查询步骤——普通查询(即没有 AsNoTracking 方法,也称为读写查询)和添加了 AsNoTracking 方法的非跟踪查询(称为只读查询)。
咱们先来看一下最初 LINQ 语句是如何转换成数据库相应的查询命令而后返回数据的。对于咱们将要看到的两种类型的查询来讲,这是很常见的操做。关于查询的第一部分,请参见下图。
有一些很是复杂的代码将你的 LINQ 转换为数据库查询命令,但这些内部细节咱们没必要关心。若是你的 LINQ 不能被翻译,你会从 EF Core 获得一个异常消息,其中包含相似“不能被翻译”的描述词语。此外,当数据返回时,像 Value Converters[4] 这样的特性可能会调整数据。
本节展现了查询的第一部分,其中 LINQ 被转换为数据库命令并返回全部正确的值。如今咱们来看查询的第二部分,在这里 EF Core 获取返回值并将它们转换为实体类的实例,并填充导航属性。咱们将分别看看两种类型的查询。
普通查询读取数据的方式能够修改数据并更新到数据库,这就是我将其称为读写查询的缘由。它不会自动更新数据(请参阅下一篇文章,了解如何写入数据库)。若是你要更新数据,你的查询必须是读写查询。
我在介绍中给出的示例执行的是一个普通读写查询,读取带有 AuthorsLink 实例的示例。下面是该示例的查询部分的代码:
var books = context.Books .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
而后 EF Core 经过三个步骤将这些值转换并填充含有导航属性的实体类。下图显示了这三个步骤以及生成的实体类及其导航属性的实体类。
让咱们来分析一下这三个步骤:
非跟踪查询,即便用 AsNoTracking 方法的查询,是一个只读查询。这意味着,当 SaveChanges 方法被调用时,你读取的任何内容都不会被写入数据库。非跟踪查询的查询效率更高,在下一节中,我将介绍非跟踪查询以及与普通查询的其余区别。
在前文的示例以后,我修改了查询代码,添加了下面的 AsNoTracking 方法(请看第 2 行):
var books = context.Books .AsNoTracking() .Include(r => r.AuthorsLink) .ThenInclude(r => r.Author) .ToList();
这里的 LINQ 查询只有上面的普通查询的前两个步骤(没有第三个步骤)。下图显示了 AsNoTracking 查询的步骤。
步骤以下:
如今让咱们比较这两种查询比较明显的区别。
非跟踪查询查询的性能更好。使用非跟踪查询查询的主要缘由是性能。非跟踪查询查询表现为:
非跟踪查询修补关联关系时只连接查询中的实体。在普通查询中,我已经说过修补关联关系时链接的是查询中的实体和当前跟踪的实体,可是非跟踪查询只修补查询中的实体关系。
非跟踪查询并不老是表明数据库关系。这两种类型查询之间的关系修补的另外一个区别是,非跟踪查询关系修补更快,它不须要标识的解析。这能够为数据库中的同一行生成多个实例——见上图右下角蓝色的 Author 实体和注释。若是只是向用户显示数据,那么这种差别并不重要,可是若是具备业务逻辑,那么多个实例不能正确反映数据的结构,就可能会有问题。
关联关系修补的步骤是很是智能的,特别是在普通查询中。下面我想向你展现我是如何利用关系修补的特性来解决一个客户项目中的性能问题的。
我曾在一家公司工做,那里的许多数据处理都是层次化结构的,即数据具备一系列深度不肯定的关联关系。问题是我必须先解析整个层次结构,而后才能呈现这些数据。我最初是经过贪婪的方式加载前两个层级,而后显式地加载更深的层级来实现这一点的。它能够工做,可是性能很是慢,而且数据库因大量单数据库访问而超载。
这不得不让我思考解决办法,若是普通查询的关系修补那么智能的话,它能帮助我提升查询的性能吗?它能够!让我给你举一个公司员工的例子。下图显示了咱们想要加载的公司的层次结构。
你能够接龙式地使用 .Include(x => x.WorksForMe).ThenInclude(x => x.WorksForMe)… 等等来加载所需的层级信息,但结果是一个 .Include(x => x.WorksForMe) 就够了。由于 EF Core 的关系修补为你作了剩下的事情,这一点很惊奇,但也颇有用。
例如,若是我想查询角色为 Development 的全部员工(每一个员工都有一个名为 WhatTheyDo 的属性和名为 Role 的属性,该 Role 包含他们工做的部门),我能够这样编写代码:
var devDept = context.Employees .Include(x => x.WorksFromMe) .Where(x => x.WhatTheyDo.HasFlag(Roles.Development)) .ToList();
这将建立一个查询,用于加载角色为 Development 的全部员工,而且在员工实体类上修补与 WorksFoMe 导航属性(集合)和 Manager 导航属性(单个)的关系。经过只执行一个查询,既提升了查询花费的时间,又减小了数据库服务器上的负载。
你已经看到了两种类型的查询,我称之为 a)普通的读写查询,和 b) 非跟踪的只读查询。对于每一种查询类型,我都向你展现了 EF Core “幕后”是如何读取数据并展现的。他们工做方式的不一样也表现出他们的优点和劣势。
非跟踪查询是只读查询的解决方案,由于它比普通读写查询更快。可是您应该记住关系修补的机制,它能够在数据库只有一个关系的状况下建立类的多个实例。
普通的读写查询是查询跟踪实体的解决方案,这意味着你能够在建立、更新和删除数据时使用它们。普通的读写查询确实会占用更多的时间和内存资源,可是有一些有用的特性,好比自动连接到其余被跟踪的实体类实例。
我但愿这篇文章对您有用。祝你编程快乐!
[1]. https://bit.ly/2MXK3ZY
[2]. https://bit.ly/2Yza7QQ
[3]. https://bit.ly/2Y0UORO
[4]. https://bit.ly/2YEyg8j