【问题标题】:Excluding large List<int> from LINQ to Entities query从 LINQ to Entities 查询中排除大型 List<int>
【发布时间】:2013-12-13 18:21:09
【问题描述】:

我有一个包含大量项目的列表 - 最多 10,000 个。

我正在寻找从 IQueryable/List 中排除这些的最有效方法。

由于获取此 Id 列表所涉及的过程很复杂,因此不可能在查询中执行此操作。

下面的示例具有极高的开销,想知道是否有人能够解释其可能的原因以及是否有更好的方法来实现这一点?

results = from q1 in results 
  where excludedRecords.All(x => x != q1.ItemId)
  select q1;

【问题讨论】:

  • 结果中有多少项?排除记录中有多少。 excludeRecords 是什么类型的?
  • LINQ 最终归结为一个 for 循环(在我的理解中)。我想可能有办法通过限制您循环的项目数量来优化这一点。顺便说一句,“不可能在查询中执行此操作”——也许不是在单个查询中,但我觉得必须有一种方法可以使用 SQL 来帮助您。考虑到这一点,也许寻找和测试查询方法可能是值得的?
  • @drew_w 这个评论是错误的。 Linq 不会“归结为 for 循环”
  • @Hogan 根据this stack,LINQ lambda 被编译为一个函数,然后将其扩展为适当的循环语法(IEnumerable 最终也解析为循环)。我对这个的理解不正确吗?
  • 据我了解,人们喜欢 LINQ 的原因是为了可读性,以及因为在访问变量之前不会调用已编译的 lambda。

标签: linq linq-to-entities


【解决方案1】:

根据您的查询形式,我将excludedRecords 视为整数列表。此外,由于您将 LINQ 标记为实体,因此我将 results 视为 DbSet 在 DbContext 中。

这是将本地列表 (excludedRecords) 与等待转换为 SQL (results) 的 IQueryable 组合起来的问题。为了让 EF 能够将完整的表达式(您的查询)翻译成 SQL,它必须将此本地列表翻译成可以成为 SQL 语句一部分的“某物”。使用 All() 和许多其他基于集合的 LINQ 语句,当加入本地列表时,EF 通过从单行表构建一个临时表(排序)来做到这一点。本地列表中只有 5 个元素,这看起来像

SELECT ...
    FROM [dbo].[Table] AS [Extent1]
    WHERE  EXISTS (SELECT 
        1 AS [C1]
        FROM  (SELECT 
            1 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
        UNION ALL
            SELECT 
            2 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable2]
        UNION ALL
            SELECT 
            3 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable3]
        UNION ALL
            SELECT 
            4 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable4]
        UNION ALL
            SELECT 
            5 AS [C1]
            FROM  ( SELECT 1 AS X ) AS [SingleRowTable5]) AS [UnionAll4]
        WHERE ([Extent1].[Id] = [UnionAll4].[C1]) OR (CASE WHEN ([Extent1].[Id] <> [UnionAll4].[C1]) THEN cast(1 as bit) WHEN ([Extent1].[Id] = [UnionAll4].[C1]) THEN cast(0 as bit) END IS NULL)
    )

虽然这可能会生成大量 SQL 语句,但当本地列表不包含“太多”元素(比如说,最多 1000 个)时,它仍然是可行的。

让 EF 更有效地使用本地列表的唯一语句是 Contains。 Contains 可以很容易地转换为 SQL IN 语句。如果我们将您的查询改写为 Contains 的等价物,这也是您问题的答案,...

results = from q1 in results 
          where !excludedRecords.Contains(q1.ItemId)
          select q1;

... SQL 查询将如下所示

SELECT ...
    FROM [dbo].[Table] AS [Extent1]
    WHERE  NOT ([Extent1].[Id] IN (1, 2, 3, 4, 5))

IN 语句可以处理比这个“临时表”更多的元素,尽管这个数量仍然有限(可能是 3000)。

【讨论】:

    【解决方案2】:

    这只是代码的一部分,但看起来您有两个列表 - 结果和排除记录。对于结果中的每个元素,您将遍历 excludeRecords 中的所有元素。这就是它慢的原因,它是O(N x M)

    Linq 和 sql 通过加入来解决这个问题,如果你加入(或等效的),你应该会看到一些不错的性能,因为这对我来说就像 O(NlgM)

    看起来像这样(现在无法测试)

    var results2 = from q1 in results
                    join x in excludedRecords on q1.LeadID = x into joined
                    from z in joined.DefaultIfEmpty()
                    where z == null
                    select q1;
    

    【讨论】:

    • 感谢 cmets Hogan。针对结果运行上述内容会引发堆栈溢出。结果对象的类型为 IQueryable
    • @Nick - 我必须测试自己才能回答 - 我可能有一个错字,但你明白了,做左连接,只取那些像 SQL 一样为空的。如果你用谷歌搜索,我相信有一些例子。很遗憾,现在无法测试。
    • 问题是你不能使用来自实体框架的IQueryable 加入内存列表。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多