【问题标题】:Performance of LINQ Any vs FirstOrDefault != nullLINQ Any 与 FirstOrDefault 的性能!= null
【发布时间】:2011-12-01 10:41:50
【问题描述】:

我贡献的开源项目 (OSP) 代码中有多个位置,必须确定集合中的元素是否满足特定条件。

我见过在某些情况下使用 LINQ 表达式 Any(lambda expression) 和在其他情况下使用 FirstOrDefault(lambda expression) != null,但从未考虑过。

我现在已经到了必须对从查询到数据库的集合进行一些迭代并想要优化运行时的地步。

所以我认为FirstOrDefault(lambda expression) != null 应该比Any(lambda expression) 快​​,对吧?

FirstOrDefault(lambda expression) != null 的情况下,迭代(可能)在找到满足条件的元素时停止(更糟糕的情况是它遍历整个集合并返回null)。

Any(lambda expression)的情况下,我想即使找到满足条件的元素,迭代也会继续到集合的末尾。

编辑:杰克逊波普提到并链接了相关的 MSDN 文章,上述情况不正确。

我的想法是正确的还是我遗漏了什么?

【问题讨论】:

  • “在Any(lambda expression)的情况下,我想即使找到满足条件的元素,迭代也会持续到集合的末尾”。为什么?
  • 因为我没有阅读 Jackson Pope 提到的 MSDN 文章。大声笑
  • 我也没有——我只是没有理由认为 LINQ 实现是荒谬的

标签: c# linq


【解决方案1】:

你在这里混合东西。您在谈论集合,但您似乎没有使用 LINQ to 对象,但您正在查询数据库。

LINQ to objects:
Enumerable.AnyEnumerable.FirstOrDefault 应该执行相同的操作,因为它们的代码几乎相同:

FirstOrDefault

foreach (TSource source1 in source)
{
    if (predicate(source1))
        return source1;
}
return default (TSource);

Any:

foreach (TSource source1 in source)
{
    if (predicate(source1))
        return true
}
return false;

LINQ 到某些数据库:
您正在使用实体框架、LINQ to SQL 或 NHibernate,并在相应的数据上下文中使用 Queryable.AnyQueryable.FirstOrDefault
在这种情况下,实际上没有集合,因为这些调用不是在内存对象上执行的,而是转换为 SQL 的。

这意味着,性能差异源于 LINQ 提供程序如何将代码转换为 SQL,因此最好先检查创建的语句。它们是等价的吗?还是它们非常不同(select count(0) from Xselect top 1 from X)?那么差异可能在于数据库的查询优化器,索引以及其他什么......

【讨论】:

  • 你说得有道理。我们使用实体进行数据库查询并将表存储在集合中。然后我们遍历这些集合以使用 LINQ 提取我们想要的数据。
  • @Jimi:我不明白你的评论,抱歉。也许你想改写或扩展它。
  • 所以在这种情况下,您使用的是 LINQ to 对象。您在测试中测量的差异不是来自这两种方法的实现,而是来自您的测试设置或其他东西。有关这两种方法的实现,请参阅更新答案。
  • “FirstOrDefault”必须从对象返回实际数据,因此如果索引较小,“Any”调用应该有可能更快.尤其是在一个有很多列的宽表中。
【解决方案2】:

Any() 中的枚举也会在找到匹配项后立即停止:

https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.any

我希望性能非常相似。请注意,FirstOrDefault 版本不适用于值类型的集合(因为默认值不为 null),但 Any 版本可以。

【讨论】:

  • 那么当您想知道是否存在满足指定条件的东西时使用Any(),但如果您真的想要返回该对象,是否可以使用FirstorDefault()
  • @Chris:是的,我就是这么用的。
  • 那为什么FirstOrDefault(lambda expression) != null更快呢?完成了一些Ticks 测试,FirstOrDefault(lambda expression) != null 总是更快结束。
  • @Jimi 您必须向我们展示您的测试代码。很难做到正确。
  • @AakashM 它涉及对数据库的查询,因此即使发布代码,您也无法对其进行测试。
【解决方案3】:

这个问题的问题在于它不是在上下文中提出的。 我提供了一个答案,因为我在代码审查中看到了很多,这让我很困扰。 LINQ 不应成为停止思考的借口。

var people = new [] { "Steve", "Joe" };

if (people.Any(s => s == "Joe"))
{
    var joe = people.First(s => s == "Joe");
    // do something with joe
}

// if people is 1,000,000 people and joe is near the end do we want BigO to approach 2N here at worst case ?

var joe1N = people.FirstOrDefault(s => s == "Joe");
if (joe1N != null)
{
    // do something with joe
}

// or do we want to ensure worst case is N by simply using a variable ?

【讨论】:

  • 这不是问题的答案...因为我参加聚会迟到了:我真的不知道你想告诉我们什么。在这种情况下,我们应该使用FirstOrDefault 还是应该使用Any + First?我不明白这一点。你(或其他人)能解释一下吗?
  • 我明白你的观点,第一种方法慢了两倍,但 BigO 2N 仍然是 N
【解决方案4】:

为什么要在找到满足条件的元素后继续?如果条件适用于 1 个元素,则该条件为“任何”。

我认为它们的性能应该差不多,但是 Any() 确实更清楚地表达了您的意图。

【讨论】:

  • 是的。我的问题不是表达意图和代码可读性,而是运行时性能。
  • Any() 更简洁——但 FirstOrDefault() 通常也会尝试返回找到的第一个项目,因此从技术上讲,它会占用带宽和不需要的返回数据。实际上,LINQ 中的优化可能会阻止这种情况发生,但是您想依赖它吗?
【解决方案5】:

我的两分钱...

我在使用 Any() 时遇到了巨大的性能问题。我正在使用 Telerik 网格来显示相关数据的列表,即

-我有一个“人”表

-一个“公司”表

-一个“PEOPLE_COMPANY”链接表

-一个“PEOPLE_ROL”链接表

-还有一个包含主类别、子类别和描述的“ROL”表。

视图混合数据并具有一些属性,可按需加载有关特定角色(管理员、报告者、经理)的数据。

var x = GetPeople().Where(p => p.ROLs.Any(i => i.USO_Id == MainCatId && i.TUP_Id == (int)RolType) && p.PER_Id != per_id);

var x = GetPersonas().Where(p => p.ROLs.FirstOrDefault(i => i.USO_Id == MainCatId && i.TUP_Id == (int)RolType) != null && p.PER_Id != per_id);

我的网格使用 AJAX,使用“Any”加载需要 10 多秒,使用“FirstOrDefault”加载需要 3 秒或更少。没有花时间调试它,因为它需要拦截来自 Telerik 组件和我的模型的调用。

希望这会有所帮助...所以好好测试一下吧:)

【讨论】:

  • 我不喜欢在 First 或 Any 上使用条件。相反,我使用 Where(例如 Where(i => i.USO_Id == MainCatId && i.TUP_Id == (int)RolType) && p.PER_Id != per_id).Any()
  • @Jorge - 谢谢,很好的例子。在我的情况下(SQL Server 2005 上的 NH 3.3.1),我的 .Any() 需要 15 秒,而等效的 .FirstOrDefault() != null 始终是亚秒级。
【解决方案6】:

我们可以使用 .Count(x => x....) != 0 代替 .Any(x => x....) 或 .FirstOrDefault( x => x....) != null

因为下面是Linq的查询生成,

(在 Any() 中,我认为不需要第二个(不存在)条件。)

.Any(x => x.Col_1 == 'xxx')

    (@p__linq__0 varchar(8000))SELECT 
CASE WHEN ( EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Table_X] AS [Extent1]
    WHERE [Extent1].[Col_1] = @p__linq__0
)) THEN cast(1 as bit) WHEN ( NOT EXISTS (SELECT 
    1 AS [C1]
    FROM [dbo].[Table_X] AS [Extent2]
    WHERE [Extent2].[Col_1] = @p__linq__0
)) THEN cast(0 as bit) END AS [C1]
FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]

.FirstOrDefault(x => x.Col_1 == 'xxx') != null

 (@p__linq__0 varchar(8000))SELECT TOP (1) 
[Extent1].[Col_1] AS [Col_1], 
[Extent1].[Col_2] AS [Col_2], 
...
[Extent1].[Col_n] AS [Col_n]
FROM [dbo].[Table_X] AS [Extent1]
WHERE [Extent1].[Col_1] = @p__linq__0

.Count(x => x.Col_1 == 'xxx') != 0

  (@p__linq__0 varchar(8000))SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Table_X] AS [Extent1]
    WHERE [Extent1].[Col_1] = @p__linq__0
)  AS [GroupBy1]

【讨论】:

  • 是的,.Any 和 .Count 方法都可以命中相同的索引(数据少,比表快),但这取决于索引的组织方式和表的行数已。 .Any 实现应该是最快的。
  • 但我的想法是,对于“.Any”实现,如果第一个条件失败,那么它将执行第二个条件。所以“.Any”执行这两条语句返回错误的结果。
【解决方案7】:

虽然它有类似的实现,但我相信 Any 会更快一些,因为它不必返回一个对象,在某些情况下它可能很丰富,具有状态和行为,这取决于查询的考虑程度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-03
    • 2011-01-10
    • 1970-01-01
    • 1970-01-01
    • 2012-01-03
    • 1970-01-01
    相关资源
    最近更新 更多