【问题标题】:Reusing LINQ query results in another LINQ query without re-querying the database重用 LINQ 查询会导致另一个 LINQ 查询,而无需重新查询数据库
【发布时间】:2011-11-26 23:23:25
【问题描述】:

我的应用程序根据用户指定的过滤条件使用 PredicateBuilder 构建动态 LINQ 查询(另外:查看此link 以获得最佳 EF PredicateBuilder 实现)。问题是这个查询通常需要很长时间才能运行,我需要这个查询的结果来执行其他查询(即将结果与其他表连接)。如果我正在编写 T-SQL,我会将第一个查询的结果放入临时表或表变量中,然后围绕它编写其他查询。我想从第一个查询中获取一个 ID 列表(例如,List<Int32> query1IDs),然后执行以下操作:

var query2 = DbContext.TableName.Where(x => query1IDs.Contains(x.ID))

这在理论上可行;但是,query1IDs 中的 ID 数量可以是 数百数千(并且 LINQ 表达式 x => query1IDs.Contains(x.ID) 被转换为 T-SQL“IN”语句, 这显然是不好的) 并且 TableName 中的行数在 百万 中。有没有人对处理这种情况的最佳方法有任何建议?

编辑 1: 进一步说明我在做什么。

好的,我正在构建我的第一个查询 (query1),它只包含我感兴趣的 ID。基本上,我将使用 query1 来“过滤”其他表。注意:我在 LINQ 语句的末尾使用ToList() ---此时查询执行并且 结果发送给客户端:

var query1 = DbContext.TableName1.Where(ComplexFilterLogic).Select(x => x.ID)

然后我使用 query1 并使用它来过滤另一个表 (TableName2)。我现在将ToList() 放在此语句的末尾,因为我想执行它并将结果带给客户端:

var query2 = (from a in DbContext.TableName2 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

然后我使用 query1 并重新使用它来过滤另一个表 (TableName3),执行它并将结果带到客户端:

var query3 = (from a in DbContext.TableName3 join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

我可以继续对任意数量的查询执行此操作:

var queryN = (from a in DbContext.TableNameN join b in query1 on a.ID equals b.ID select new { b.Column1, b.column2, b.column3,...,b.columnM }).ToList();

问题:query1 需要 很长 时间来执行。当我执行 query2、query3...queryN、query1 被执行 (N-1) 次...这不是一种非常有效的做事方式(特别是因为 query1 没有改变)。如前所述,如果我在编写 T-SQL,我会将 query1 的结果放入一个临时表中,然后在后续查询中使用该表。

编辑 2:

我将把回答这个问题的功劳归功于 Albin Sunnanbo 的评论:

当我想在其他几个查询中重用一个繁重的查询时遇到类似的问题时,我总是回到在每个查询中创建一个连接的解决方案,并投入更多精力来优化查询执行(主要是通过调整我的索引)。

我认为这确实是 Entity Framework 所能做的最好的事情。最后,如果性能真的很差,我可能会接受 John Wooley 的建议:

在这种情况下,针对返回多个结果的存储过程并使用内部临时表下降到本机 ADO 可能是此操作的最佳选择。将 EF 用于应用程序的其他 90%。

感谢所有评论这篇文章的人...感谢大家的意见!

【问题讨论】:

  • 如果要将查询直接保存在服务器上,则需要避免将初始 IQueryable 转换为 IEnumerable(通过 ToList/ToArray/AsEnumerable/等)。 EF 应该在连接的表达式树上正确地组合查询。您不能通过 IEnumerable 使用 Contains 传递超过 2000 个参数。为什么 query1IDs 不是 IQueryable?
  • 嗨,吉姆,感谢您的 cmets。请参阅我原来的问题中的编辑 1,以更清楚地了解我正在尝试做什么。问题不在于将查询保留在服务器上(出于您给出的原因,我当然想这样做)...问题是我不想继续执行 query1 因为它非常慢。
  • 可以将临时表与 Linq To Entities 一起使用。我已经用 Linq2Sql 做了这个,但是对 Linq To Entities 的了解还不够,不能说是否可以做类似的事情。有关详细信息,请参阅我对这个问题的回复stackoverflow.com/questions/6122185/…
  • 你好 sgmoore!是的!这是我想用 Linq2Entities 做的事情......但是,我不确定它是否可能。我将对此进行进一步调查。如果在实体框架中可以的话,我认为实现会稍微复杂一些。听起来我必须即时编辑 *.edmx 文件——这可能是不可能的。有人对如何在 Linq2Entities 中实现 sgmoore 的想法有任何想法吗?
  • @sgmoore 选项的缺点是它在高并发环境中不起作用。它还需要数据库中的管理员(创建表/删除表)权限,这通常是一个安全漏洞。对于 L2S,我可以推荐使用返回 IMultipleResults 的存储过程,但 EF 还不支持这些。在这种情况下,针对返回多个结果的存储过程并使用内部临时表下降到本机 ADO 可能是此操作的最佳选择。将 EF 用于应用程序的其他 90%。

标签: c# entity-framework c#-4.0 entity-framework-4 entity-framework-4.1


【解决方案1】:

您是否考虑过按照本文编写查询(使用装饰器设计模式):

Composed LINQ Queries using the Decorator Pattern

前提是,您基本上使用装饰器模式来生成 IQueryable 链,而不是枚举您的第一个(非常固定的)查询,它是查询 1 和查询 N 的结果。这样您始终执行过滤后的表单查询。

希望这可能会有所帮助

【讨论】:

  • 感谢Anas,您的建议和链接,尽管我不确定这是否会对我有所帮助。正如我在编辑中提到的,问题是 query1 的多次执行......据我了解,如果我使用“装饰器模式”方法,query1 仍将被执行多次。我的理解正确吗?
  • 非常欢迎。在您的情况下,如果您使用查询包装器,query1 将是某种连接到 query2...N 的子查询,因此它将在与其他查询的 SQL 连接的上下文中执行,而不是作为单独的查询执行多次,我猜这是您尝试通过使用 query1 返回的 ID 作为其他人的过滤机制来执行的操作。我希望你能尽快找到一个令人满意的解决方案。祝你好运。
  • Anas...这是一个非常有趣的想法。我已经阅读了更多关于“装饰器模式”的内容,如果我说我完全理解它,那我就是在撒谎。但是,从我读过的内容来看,这似乎很有希望。我一开始工作就会尝试一些实验。我会告诉你我是否成功。如果这确实有效,那么我认为这是每个 EF 程序员都应该知道的。再次感谢您的建议!
  • Anas...也许我没有正确理解“装饰器模式”,但它似乎用于“链接”操作。我看不出这对我有什么帮助。我没有将查询 2 链接到 N……它们完全相互独立……它们只是碰巧共享相同的“过滤表”(即 query1)。我承认我并不完全理解“装饰器模式”,所以任何启发都将不胜感激! =)
【解决方案2】:

如果 TableName 的大小不是太大而无法加载您使用的整个表

var tableNameById = DbContext.TableName.ToDictionary(x => x.ID);

获取整个表并自动将其放入本地 Dictionary 并以 ID 为键。

另一种方法是使用.ToList()“强制”LINQ 评估,在这种情况下获取整个表并使用 Linq2Objects 在本地执行Where 部分。

var query1Lookup = new Hashset<int>(query1IDs);
var query2 = DbContext.TableName.ToList().Where(x => query1IDs.Contains(x.ID));

编辑:
将来自一个查询的 ID:s 列表存储在一个列表中,并将该列表用作另一个查询中的过滤器,通常可以重写为连接。
当我想在其他几个查询中重用一个繁重的查询时遇到类似的问题时,我总是回到在每个查询中创建一个连接的解决方案,并投入更多精力来优化查询执行(主要是通过调整我的索引)。

【讨论】:

  • 感谢 Albin 回答我的问题。问题是 TableName 包含数百万行,而 query1ID 中的 ID 数为数百,有时甚至数千。将整个 TableName 返回给客户端不是一种选择。
  • Albin: 我认为您的建议(使用查询连接和调整索引)与我在实体框架中得到的一样好。每次需要时我都必须重新执行第一个查询,这太糟糕了——如果性能不可接受,我只需要手动编写自己的 T-SQL 并使用通常的 ADO.NET 对象。我将把这个问题留到另一天,看看是否还有其他想法,但如果没有,我会将您的答案标记为已接受的答案。再次感谢您对我的问题感兴趣!
  • @HydroPowerDeveloper 如果您的查询中有很多连接、分组或聚合函数,您可能会通过创建索引视图获得好运。
  • 不幸的是,视图不会有太大帮助。查询是动态的(在运行时确定)并且基于用户输入......我不知道将使用哪些表,它们将如何连接,或者将指定什么样的过滤条件。我认为您通过调整索引来优化查询执行的建议是我能做的最好的。我正在研究其他一些想法(参见 sgmoore 和 Anas Karkoukli 的 cmets),但我不确定它们是否会起作用。
【解决方案3】:

由于您正在对结果运行后续查询,因此请获取您的第一个查询并将其用作 SQL Server 上的视图,将视图添加到您的上下文中,并针对该视图构建您的 LINQ 查询。

【讨论】:

  • 感谢您的建议,辅导员本!但是,我无法在 SQL Server 上创建静态视图,因为第一个查询是动态的(该查询是由应用不同过滤条件的用户创建的)。另外,从一个视图来看,它背后的T-SQL每次查询都会运行......这是我要避免的事情之一,因为它需要很长时间才能运行。
猜你喜欢
  • 1970-01-01
  • 2016-03-24
  • 1970-01-01
  • 2017-09-24
  • 2013-07-18
  • 1970-01-01
  • 2016-02-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多