如何在一个查询中通过 LINQ Include 检索子数据以提高性能?

Posted

技术标签:

【中文标题】如何在一个查询中通过 LINQ Include 检索子数据以提高性能?【英文标题】:How to retrieve child data, via LINQ Include, in one query, to improve performance? 【发布时间】:2016-07-03 10:05:45 【问题描述】:

我正在使用 ASP.NET 4.5、MVC5、C#、LINQ、EF6、SQL Server 2012/SQL Azure。

我需要显着提高复杂查询的效率。本质上,该任务的目的是复制具有许多子记录的“样本”记录集。我目前正在通过 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”等,而是想一次性带回所有相关数据,以尽量减少对数据库的调用次数。我知道我需要使用“包含”。我已经做到了:

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)

想法...

EDIT2

我还需要下 5 级。我发现这个检索超时!这是否是因为 EF 在编译时感到困惑,或者检索太复杂,或者两者我都不确定。可以使用“包含”的级别可能有限制?另外我不确定通过指定孙子的路径然后自动检索父母,还是您必须单独指定父母?

【问题讨论】:

【参考方案1】:

一个人可以使用“包含”的级别可能有限制?

有!正如我向here 解释生成的 SQL 语句一样 -

SELECT 子句中的列数是所有相关表中所有列的总和 行数是包含的子集合中记录的总和

这可能是从数据库返回的巨大(长宽)结果集。除此之外,数据库引擎的查询优化器很难找到一个好的查询计划。数据库将很难处理所有数据,因此命令超时也就不足为奇了。

另一种选择

另一种方法是分块加载数据。但这说起来容易做起来难。在某种程度上,您已经以块的形式加载数据,但是这些块太小并且查询太多(是的,N + 1)。块应该更大。没有明确的策略如何做到这一点。这取决于您的表结构和数据数量。但让我试着为你指明正确的方向。

向下5级

为简洁起见,假设表和关联是A B C D E(“

var query = As.Where(a => a.Property == value).ToList();

[所以你不想要 all As,因为这很容易:那么你也可以加载所有孩子。]

假设您可以IncludeBs 没有任何问题,但是包括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 次查询。

【讨论】:

这是一个惊人的回复。谢谢你。信息最丰富。

以上是关于如何在一个查询中通过 LINQ Include 检索子数据以提高性能?的主要内容,如果未能解决你的问题,请参考以下文章

在ASP.NET Core中通过EF Core实现一个简单的全局过滤查询

如何在 Realm 中通过 NSDate 属性正确查询对象?

如何在 Solidity 中通过多个属性查询结构?

如何在Linux中通过命令查看域名对应的IP

如何在 Microsoft Access 中通过 VBA 设置 INSERT SQL 查询的参数值?

如何在 Linq 查询中多次包含一个表