19

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

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

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

谢谢你,

米格尔

4

5 回答 5

32

好的,我不打算对此进行权衡,但是 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. 使用实体框架 4(因为较新的版本可能会改进查询结构)实体框架 6.1 或更早版本(因为6.1.1 有改进查询的修复程序),并且
  2. 直接查询表(而不是执行子查询),并且
  3. 直接使用结果(而不是作为谓词的一部分),并且
  4. 任何一个:
    1. 您在要查询的表上设置了良好的索引,或者
    2. 您希望大部分时间都找不到该项目

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

在任何其他 情况下,.Any()都应该至少.Count().

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

于 2012-09-15T18:20:37.770 回答
28

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

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

于 2012-09-14T19:51:30.820 回答
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

于 2012-09-15T00:26:39.163 回答
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 的真正威力在于您使用子查询并且只是测试相关数据的存在性。

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

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();
于 2020-05-19T02:15:15.423 回答
-1

Any()并与它First()一起使用IEnumerable,使您可以灵活地懒惰地评估事物。但是Exists()需要列表。

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

于 2012-09-14T17:27:30.850 回答