如何通过 LINQ Include 在一个查询中检索子数据以提高性能?
How to retrieve child data, via LINQ Include, in one query, to improve performance?
我正在使用 ASP.NET 4.5、MVC5、C#、LINQ、EF6、SQL Server 2012/SQL Azure。
我需要显着提高复杂查询的效率。本质上,该任务的目的是复制一个包含许多子记录的 "sample" 记录集。我目前正在通过 C# 和 LINQ 执行此操作。我怀疑我在多个 Foreach 块中重新查询数据库,因此导致对数据库的多次调用。虽然每个查询都很小,但调用次数却很少。它可能是200+。我相信他们称这是 "N+1" 问题。
以下布局给出了关系和查询的概念。
Table1-<Table1.1
-<Table1.2-<Table1.2.1
-<Table1.2.2-<Table1.2.2.1
-<Table1.2.2.2
我不想使用 "Foreach" 带回 "Table1.1" 等,而是想一次性带回所有相关数据,以尽量减少对数据库的调用次数。我知道我需要使用 "Include"。我已经达到:
db.Table1.Where(r=>r.Id=myId).Include(x=>x.Table1.2).Include(x=>x.Table1.2)
但是我不确定如何更改此语句以将数据恢复为 "Table1.2.2.2"。这是我的问题。
提前谢谢你。
编辑 1
我找到了初步答案。
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
不过我可能不需要中线,所以下面的可能没问题。
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
想法...
编辑2
我还需要下5层。我发现这个检索超时!这是因为 EF 对编译感到困惑,还是因为检索太复杂,或者两者兼而有之,我不确定。一个人可以使用的级别可能有多少限制 "Include" ?此外,我不确定通过指定孙子的路径,然后会自动检索父级,还是必须单独指定父级?
There is possibly a limit on how many levels one can use "Include"?
有!正如我解释 关于生成的 SQL 语句 -
- Number of columns in the
SELECT
clause is the sum of all columns in all involved tables
- 行数是包含的子集合中记录的总和
这可能是从数据库返回的 巨大 (长 和 宽)结果集。除此之外,数据库引擎的查询优化器很难找到一个好的查询计划。数据库将很难处理所有数据,命令超时也就不足为奇了。
备选方案
另一种方法是分块加载数据。但这说起来容易做起来难。在某种程度上,您已经分块加载数据,但是这些块太小而且查询太多(是的,N + 1)。块应该更大。没有明确的策略如何做到这一点。这取决于您的 table 结构和数据数量。但让我试着为您指明正确的方向。
5 levels down
为简洁起见,假设 table 和关联是 A
< B
< C
< D
< E
(“<”表示 1:n)。根查询类似于
var query = As.Where(a => a.Property == value).ToList();
[所以你不需要 all As
,因为那很容易:那么你也可以加载所有子项。]
让我们假设您可以 Include
Bs
没有任何问题,但是包括 Cs
已经太多了。所以查询变为:
var query = As.Where(a => a.Property == value)
.Include(a => a.Bs).ToList();
并且Cs
等应该加载成块数据。
Entity Framework 的一个不错的功能是它通过称为 关系修正 的过程自动连接加载到上下文中的所有实体。因此,如果您单独加载 Cs
,将填充其父 B
对象中的集合。这使得加载所需的 Cs
:
变得容易
var cs = Cs.Where(c => c.B.A.Property == value).ToList();
(假设反向引用也是你模型的一部分)
否,如果您可以安全地包含 Ds
,我们就快完成了:
var cs = Cs.Where(c => c.B.A.Property == value)
.Include(c => c.Ds).ToList();
最后一关的加载者:
var es = Es.Where(e => e.D.C.B.A.Property == value).ToList();
这种嵌套级别(圆点)可能看起来很吓人。它将创建一个包含四个连接的查询。但是,与 4 Incudes
的最大区别在于,现在只查询 E
列和行。查询结果没有爆炸。并且数据库引擎针对执行连接进行了优化。
因此,这为您提供了一些处理 Include
级别和单独查询的方法,直到您拥有运行良好(足够)的配置。
最后一件事:记得关闭延迟加载。 EF 会自动填充集合,但不会将它们标记为已加载。如果启用延迟加载,访问集合仍然会触发 N + 1 个查询。
我正在使用 ASP.NET 4.5、MVC5、C#、LINQ、EF6、SQL Server 2012/SQL Azure。
我需要显着提高复杂查询的效率。本质上,该任务的目的是复制一个包含许多子记录的 "sample" 记录集。我目前正在通过 C# 和 LINQ 执行此操作。我怀疑我在多个 Foreach 块中重新查询数据库,因此导致对数据库的多次调用。虽然每个查询都很小,但调用次数却很少。它可能是200+。我相信他们称这是 "N+1" 问题。
以下布局给出了关系和查询的概念。
Table1-<Table1.1
-<Table1.2-<Table1.2.1
-<Table1.2.2-<Table1.2.2.1
-<Table1.2.2.2
我不想使用 "Foreach" 带回 "Table1.1" 等,而是想一次性带回所有相关数据,以尽量减少对数据库的调用次数。我知道我需要使用 "Include"。我已经达到:
db.Table1.Where(r=>r.Id=myId).Include(x=>x.Table1.2).Include(x=>x.Table1.2)
但是我不确定如何更改此语句以将数据恢复为 "Table1.2.2.2"。这是我的问题。
提前谢谢你。
编辑 1
我找到了初步答案。
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
不过我可能不需要中线,所以下面的可能没问题。
db.Table1.Include(x=>x.Table1.1)
.Include(x=>x.Table1.2.Select(y=>y.Table1.2.1)
想法...
编辑2
我还需要下5层。我发现这个检索超时!这是因为 EF 对编译感到困惑,还是因为检索太复杂,或者两者兼而有之,我不确定。一个人可以使用的级别可能有多少限制 "Include" ?此外,我不确定通过指定孙子的路径,然后会自动检索父级,还是必须单独指定父级?
There is possibly a limit on how many levels one can use "Include"?
有!正如我解释
- Number of columns in the
SELECT
clause is the sum of all columns in all involved tables - 行数是包含的子集合中记录的总和
这可能是从数据库返回的 巨大 (长 和 宽)结果集。除此之外,数据库引擎的查询优化器很难找到一个好的查询计划。数据库将很难处理所有数据,命令超时也就不足为奇了。
备选方案
另一种方法是分块加载数据。但这说起来容易做起来难。在某种程度上,您已经分块加载数据,但是这些块太小而且查询太多(是的,N + 1)。块应该更大。没有明确的策略如何做到这一点。这取决于您的 table 结构和数据数量。但让我试着为您指明正确的方向。
5 levels down
为简洁起见,假设 table 和关联是 A
< B
< C
< D
< E
(“<”表示 1:n)。根查询类似于
var query = As.Where(a => a.Property == value).ToList();
[所以你不需要 all As
,因为那很容易:那么你也可以加载所有子项。]
让我们假设您可以 Include
Bs
没有任何问题,但是包括 Cs
已经太多了。所以查询变为:
var query = As.Where(a => a.Property == value)
.Include(a => a.Bs).ToList();
并且Cs
等应该加载成块数据。
Entity Framework 的一个不错的功能是它通过称为 关系修正 的过程自动连接加载到上下文中的所有实体。因此,如果您单独加载 Cs
,将填充其父 B
对象中的集合。这使得加载所需的 Cs
:
var cs = Cs.Where(c => c.B.A.Property == value).ToList();
(假设反向引用也是你模型的一部分)
否,如果您可以安全地包含 Ds
,我们就快完成了:
var cs = Cs.Where(c => c.B.A.Property == value)
.Include(c => c.Ds).ToList();
最后一关的加载者:
var es = Es.Where(e => e.D.C.B.A.Property == value).ToList();
这种嵌套级别(圆点)可能看起来很吓人。它将创建一个包含四个连接的查询。但是,与 4 Incudes
的最大区别在于,现在只查询 E
列和行。查询结果没有爆炸。并且数据库引擎针对执行连接进行了优化。
因此,这为您提供了一些处理 Include
级别和单独查询的方法,直到您拥有运行良好(足够)的配置。
最后一件事:记得关闭延迟加载。 EF 会自动填充集合,但不会将它们标记为已加载。如果启用延迟加载,访问集合仍然会触发 N + 1 个查询。