Linq To Entities - 任何VS First VS存在

时间:2012-09-14 17:20:43

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

我正在使用Entity Framework,我需要检查名称为“xyz”的产品是否存在......

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

对于这种情况,哪一个是最好的选择?哪一个表现最好?

谢谢,

米格尔

5 个答案:

答案 0 :(得分:26)

好吧,我不打算对此进行评估,但迭戈的回答让事情变得复杂,我认为还有一些额外的解释。

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

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

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

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

...突然实体框架决定创建条件的两个版本,因此查询的确需要两倍于它真正需要的工作。但是,以下查询没有任何改善:

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
  3. ......哪个会更快?这取决于。

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

    第二个查询强制数据库检查表中的每个项目而不会发生短路,这意味着它可能需要花费N倍的时间,具体取决于之前需要评估的项目数量找到一个匹配。我们假设该表有10,000个项目:

    • 如果表中的项目与条件不匹配,则此查询将大约一半的时间作为第一个查询。
    • 如果表格中的第一项与条件匹配,则此查询将比第一次查询的大约长5000倍
    • 如果表格中的一个项目匹配,则此查询将比第一个查询的平均花费2,500倍
    • 如果查询能够利用Title和键列上的索引,则此查询将大约一半的时间作为第一个查询。

    总而言之,如果你是:

    1. 使用实体框架4(因为较新版本可能会改进查询结构)实体框架6.1或更早版本(自6.1.1 has a fix to improve the query起),AND
    2. 直接查询表(与执行子查询相反),AND
    3. 直接使用结果(而不是谓词的一部分),AND
    4. 或者:
      1. 您在要查询的表上设置了良好的索引,或者
      2. 您希望在大部分时间内找不到该项目
    5. 然后您可以期望.Any()的使用时间是.Count()的两倍。例如,查询可能需要100毫秒而不是50或10而不是5。

      在任何其他情况下 .Any() 至少应该,并且可能比{{1>更快 }}

      无论如何,在您确定这实际上是产品性能不佳的根源之前,您应该更关心什么是易于理解的。 .Count()更清楚,更简洁地陈述了你真正想要弄清楚的东西,所以坚持下去。

答案 1 :(得分:21)

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

至少你没有问过。在哪里(x => x.Count()> 0)需要对整个匹配集进行评估和迭代,然后才能确定你有一条记录。任何短路请求都会明显加快。

答案 2 :(得分:2)

有人会认为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查询完全相同)

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

好吧,我决定自己尝试一下。请注意,我正在将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的方式。与没有CASE THEN语句的EF相比,我可以用ANY在Oracle中编写一个更优化的查询。
  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();