【问题标题】:Linq To Entities - Any VS First VS ExistsLinq To Entities - Any VS First VS Exists
【发布时间】:2012-09-07 21:27:31
【问题描述】:

我正在使用实体框架,我需要检查名称 = "xyz" 的产品是否存在......

我想我可以使用 Any()、Exists() 或 First()。

对于这种情况,哪一个是最佳选择?哪个性能最好?

谢谢,

米格尔

【问题讨论】:

    标签: entity-framework ef-code-first exists any


    【解决方案1】:

    好的,我不打算对此发表意见,但 Diego 的回答使事情变得复杂,我认为需要进行一些额外的解释。

    在大多数情况下,.Any() 会更快。以下是一些示例。

    Workflows.Where(w => w.Activities.Any())
    Workflows.Where(w => w.Activities.Any(a => a.Title == "xyz"))
    

    在上面的两个例子中,Entity Framework 产生了一个最优的查询。 .Any() 调用是谓词的一部分,Entity Framework 可以很好地处理这个问题。但是,如果我们将 .Any() 的结果作为结果集的一部分,如下所示:

    Workflows.Select(w => w.Activities.Any(a => a.Title == "xyz"))
    

    ...突然,Entity Framework 决定创建两个版本的条件,因此查询的工作量是它真正需要的工作量的两倍。但是,以下查询也好不到哪里去:

    Workflows.Select(w => w.Activities.Count(a => a.Title == "xyz") > 0)
    

    鉴于上述查询,Entity Framework 仍将创建两个版本的条件,此外它还需要 SQL Server 进行实际计数,这意味着它不会在找到项目后立即短路.

    但如果您只是比较这两个查询:

    1. Activities.Any(a => a.Title == "xyz")
    2. Activities.Count(a => a.Title == "xyz") > 0

    ...哪个会更快?视情况而定。

    第一个查询产生了一个低效的双条件查询,这意味着它需要花费两倍的时间。

    第二个查询强制数据库在不短路的情况下检查表中的每个项目,这意味着它可能需要 N 倍的时间,具体取决于在找到一个之前需要评估多少个项目匹配。假设该表有 10,000 个项目:

    • 如果表中没有任何项目符合条件,则此查询将 大约一半时间作为第一个查询。
    • 如果表中的第一项符合条件,则此查询将比第一个查询花费大约 5,000 倍
    • 如果表中的一项匹配,则此查询将比第一个查询平均长 2,500 倍
    • 如果查询能够利用 Title 和键列上的索引,则此查询将花费大约一半时间作为第一个查询。

    总之,如果你是:

    1. 使用 Entity Framework 4(因为较新的版本可能会改进查询结构) Entity Framework 6.1 或更早版本(自 6.1.1 has a fix to improve the query),并且
    2. 直接查询表(而不是执行子查询),AND
    3. 直接使用结果(而不是作为谓词的一部分),并且
    4. 要么:
      1. 您在要查询的表上设置了良好的索引,或者
      2. 您希望在大多数情况下找不到该项目

    那么,您可以预期 .Any() 花费的时间是 .Count() 的两倍。例如,查询可能需要 100 毫秒而不是 50 毫秒。或者 10 毫秒而不是 5。

    在任何其他情况下.Any() 应该至少与.Count() 一样快,并且可能.Count() 快几个数量级

    无论如何,在您确定这实际上是您的产品性能不佳的根源之前,您应该更加关注易于理解的内容。 .Any() 更清楚、更简洁地说明了您真正想要弄清楚的内容,所以坚持下去。

    【讨论】:

    • 如果您试图解决 Any() 错误 (?),而不是将其转换为 Count() > 0,我会使用 FirstOrDefault() != null
    • @Mormegil:一个有趣的想法。但是我对其进行了测试,这会创建一个更疯狂的查询,其中包含两个外部应用的选择语句。所以它实际上并没有解决问题,而且它使代码(在我看来)稍微不那么清晰。
    • +1 表示“除非您确定这实际上是您的产品性能不佳的根源,否则您应该更关心易于理解的内容。”评论
    • EF 6.1.1 修复了 Any() 创建一个带有 2 个 EXISTS 子句的 CASE。见WI 192
    • @StriplingWarrior 您可以通过以下方式消除双重存在: Workflows.Select(w => w.Activities.Any(a => a.Title == "xyz") ? true :错误的)。 (这是很久以前完成的,在最新版本的 EF 中行为可能已经改变)。
    【解决方案2】:

    任何在数据库级别转换为“存在”。 First 转换为 Select Top 1 ... 在这些之间, Exists 将执行 First ,因为不需要获取实际对象,只需要一个布尔结果值。

    至少您没有询问 .Where(x => x.Count() > 0) ,它需要评估和迭代整个匹配集,然后才能确定您有一条记录。任何都会使请求短路并且可以显着加快。

    【讨论】:

    • +1。澄清一下:计算整个匹配集是在数据库级别完成的,因此它不会将对象获取到您的应用程序中。还是.Any()比较简单,而且会优于.Where(x => x.Count > 0),所以应该首选。
    • @StriplingWarrior 确实计数是在数据库中完成的,但它仍然比计数更糟糕,这要归功于 EF 可怕的查询生成。看我的回答。
    【解决方案3】:

    有人会认为Any() 提供了更好的结果,因为它转换为EXISTS 查询...但是 EF 严重损坏,生成了这个(已编辑):

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent1]
        WHERE Condition
    )) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent2]
        WHERE Condition
    )) THEN cast(0 as bit) END AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    

    代替:

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
        1 AS [C1]
        FROM [MyTable] AS [Extent1]
        WHERE Condition
    )) THEN cast(1 as bit)
       ELSE cast(0 as bit) END AS [C1]
    FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
    

    ...基本上将查询成本加倍(对于简单的查询;对于复杂的查询更糟)

    我发现使用.Count(condition) > 0 几乎总是更快(成本与正确编写的EXISTS 查询完全相同)

    【讨论】:

    • 一个有趣的观察,但我担心这个答案过于简单化到开始可能误导的程度。 See my answer.
    【解决方案4】:

    好的,我决定自己尝试一下。请注意,我将 OracleManagedDataAccess 提供程序与 OracleEntityFramework 一起使用,但我猜它会生成兼容的 SQL。

    我发现对于一个简单的谓词,First() 比 Any() 更快。我将展示 EF 中的两个查询和生成的 SQL。请注意,这是一个简化的示例,但问题是询问简单谓词是否存在、是否存在或首先更快。

     var any = db.Employees.Any(x => x.LAST_NAME.Equals("Davenski"));
    

    那么这在数据库中解决了什么问题?

    SELECT 
    CASE WHEN ( EXISTS (SELECT 
    1 AS "C1"
    FROM "MYSCHEMA"."EMPLOYEES" "Extent1"
    WHERE ('Davenski' = "Extent1"."LAST_NAME")
     )) THEN 1 ELSE 0 END AS "C1"
    FROM  ( SELECT 1 FROM DUAL ) "SingleRowTable1"
    

    它正在创建一个 CASE 语句。我们知道,ANY 只是合成糖。它解析为数据库级别的 EXISTS 查询。如果您在数据库级别也使用 ANY,就会发生这种情况。但这似乎不是这个查询最优化的 SQL。 在上面的示例中,此处不需要 EF 构造 Any(),它只会使查询复杂化。

     var first = db.Employees.Where(x => x.LAST_NAME.Equals("Davenski")).Select(x=>x.ID).First();
    

    这在数据库中解析为:

    SELECT 
    "Extent1"."ID" AS "ID"
    FROM "MYSCHEMA"."EMPLOYEES" "Extent1"
    WHERE ('Davenski' = "Extent1"."LAST_NAME") AND (ROWNUM <= (1) )
    

    现在这看起来是比初始查询更优化的查询。为什么?它没有使用 CASE ... THEN 语句。

    我多次运行这些琐碎的示例,在几乎所有情况下(没有双关语),First() 更快。

    此外,我运行了一个原始 SQL 查询,认为这样会更快:

    var sql = db.Database.SqlQuery<int>("SELECT ID FROM MYSCHEMA.EMPLOYEES WHERE LAST_NAME = 'Davenski' AND ROWNUM <= (1)").First();
    

    性能实际上是最慢的,但类似于 Any EF 构造。

    反思:

    1. EF Any 不完全映射到您在数据库中使用 Any 的方式。我本可以在 Oracle 中使用 ANY 编写一个比没有 CASE THEN 语句的 EF 生成的查询更优化的查询。
    2. 始终在日志文件或调试输出窗口中检查生成的 SQL。
    3. 如果您要使用 ANY,请记住它是 EXISTS 的语法糖。 Oracle 也使用 SOME,这与 ANY 相同。您通常会在谓词中使用它来代替 IN。在这种情况下,它会在您的 WHERE 子句中生成一系列 OR。 ANY 或 EXISTS 的真正威力在于您使用子查询并且只是测试相关数据的存在性。

    这是一个真正有意义的例子。我正在测试相关数据的存在性。我不想从相关表中获取所有记录。在这里我想知道是否有带有评论的调查。

    var b = db.Survey.Where(x => x.Comments.Any()).ToList();
    

    这是生成的 SQL:

    SELECT 
    "Extent1"."SURVEY_ID" AS "SURVEY_ID", 
    "Extent1"."SURVEY_DATE" AS "SURVEY_DATE"
    FROM "MYSCHEMA"."SURVEY" "Extent1"
    WHERE ( EXISTS (SELECT 
    1 AS "C1"
    FROM "MYSCHEMA"."COMMENTS" "Extent2"
    WHERE ("Extent1"."SURVEY_ID" = "Extent2"."SURVEY_ID")
     ))
    

    这是优化的 SQL! 我相信 EF 在生成 SQL 方面做得很好。但是您必须了解 EF 构造如何映射到 DB 构造,否则您可能会创建一些讨厌的查询。

    获取相关数据计数的最佳方法可能是使用集合查询计数进行显式加载。这比之前帖子中提供的示例要好得多。在这种情况下,您不会加载相关实体,而只是获取计数。在这里,我只是想找出我对特定调查有多少评论。

      var d = db.Survey.Find(1);
    
      var  e = db.Entry(d).Collection(f => f.Comments)
                                   .Query()
                                   .Count();
    

    【讨论】:

      【解决方案5】:

      Any()First()IEnumerable 一起使用,这为您提供了懒惰评估事物的灵活性。但是Exists() 需要列表。

      我希望这可以为您解决问题并帮助您决定使用哪一个。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-18
        • 2010-10-27
        • 2017-03-31
        • 1970-01-01
        • 2011-08-29
        • 2013-08-06
        • 2011-04-27
        • 2012-06-24
        相关资源
        最近更新 更多