哪种方法效果更好:.Any()与.Count()> 0?

Deadly 提交于 2020-01-09 22:17:38

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

System.Linq命名空间中,我们现在可以将IEnumerable扩展为具有Any()Count() 扩展方法

最近,我被告知,如果我要检查一个集合包含在它里面1个或多个项目,我应该使用.Any()而不是扩展方法.Count() > 0扩展方法,因为.Count()扩展方法必须遍历所有项目。

其次,某些集合具有CountLength 属性 (不是扩展方法)。 它会更好利用这些,而不是.Any().Count()

是/否?


#1楼

注意:当实体框架4实际存在时,我写了这个答案。 这个答案的要点是不要进入微不足道.Any() VS .Count()性能测试。 关键是要表明EF远非完美。 新版本的比较好...但如果你的代码的一部分这是缓慢的,它采用EF,测试直接TSQL和性能进行比较,而不是依赖于假设(即.Any()总是比快.Count() > 0 ) 。


尽管我同意大多数投票赞成的答案和评论-特别是在Any点上, Any信号开发人员的意图都比Count() > 0 -但我遇到的情况是,在SQL Server上,Count的数量级更快(EntityFramework 4)。

这是带有Any that thew timeout异常(约200.000条记录)的查询:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && !a.NewsletterLogs.Any(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr)
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

Count以毫秒为单位的版本:

con = db.Contacts.
    Where(a => a.CompanyId == companyId && a.ContactStatusId <= (int) Const.ContactStatusEnum.Reactivated
        && a.NewsletterLogs.Count(b => b.NewsletterLogTypeId == (int) Const.NewsletterLogTypeEnum.Unsubscr) == 0
    ).OrderBy(a => a.ContactId).
    Skip(position - 1).
    Take(1).FirstOrDefault();

我需要找到一种方法来查看LINQ产生的确切SQL,但很明显,在某些情况下CountAny之间存在巨大的性能差异,不幸的是,您似乎不能在所有情况下都坚持使用Any

编辑:这是生成的SQL。 如你所见,美女;)

ANY

exec sp_executesql N'SELECT TOP (1) 
[Project2].[ContactId] AS [ContactId], 
[Project2].[CompanyId] AS [CompanyId], 
[Project2].[ContactName] AS [ContactName], 
[Project2].[FullName] AS [FullName], 
[Project2].[ContactStatusId] AS [ContactStatusId], 
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
    FROM ( SELECT 
        [Extent1].[ContactId] AS [ContactId], 
        [Extent1].[CompanyId] AS [CompanyId], 
        [Extent1].[ContactName] AS [ContactName], 
        [Extent1].[FullName] AS [FullName], 
        [Extent1].[ContactStatusId] AS [ContactStatusId], 
        [Extent1].[Created] AS [Created]
        FROM [dbo].[Contact] AS [Extent1]
        WHERE ([Extent1].[CompanyId] = @p__linq__0) AND ([Extent1].[ContactStatusId] <= 3) AND ( NOT EXISTS (SELECT 
            1 AS [C1]
            FROM [dbo].[NewsletterLog] AS [Extent2]
            WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])
        ))
    )  AS [Project2]
)  AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4

COUNT

exec sp_executesql N'SELECT TOP (1) 
[Project2].[ContactId] AS [ContactId], 
[Project2].[CompanyId] AS [CompanyId], 
[Project2].[ContactName] AS [ContactName], 
[Project2].[FullName] AS [FullName], 
[Project2].[ContactStatusId] AS [ContactStatusId], 
[Project2].[Created] AS [Created]
FROM ( SELECT [Project2].[ContactId] AS [ContactId], [Project2].[CompanyId] AS [CompanyId], [Project2].[ContactName] AS [ContactName], [Project2].[FullName] AS [FullName], [Project2].[ContactStatusId] AS [ContactStatusId], [Project2].[Created] AS [Created], row_number() OVER (ORDER BY [Project2].[ContactId] ASC) AS [row_number]
    FROM ( SELECT 
        [Project1].[ContactId] AS [ContactId], 
        [Project1].[CompanyId] AS [CompanyId], 
        [Project1].[ContactName] AS [ContactName], 
        [Project1].[FullName] AS [FullName], 
        [Project1].[ContactStatusId] AS [ContactStatusId], 
        [Project1].[Created] AS [Created]
        FROM ( SELECT 
            [Extent1].[ContactId] AS [ContactId], 
            [Extent1].[CompanyId] AS [CompanyId], 
            [Extent1].[ContactName] AS [ContactName], 
            [Extent1].[FullName] AS [FullName], 
            [Extent1].[ContactStatusId] AS [ContactStatusId], 
            [Extent1].[Created] AS [Created], 
            (SELECT 
                COUNT(1) AS [A1]
                FROM [dbo].[NewsletterLog] AS [Extent2]
                WHERE ([Extent1].[ContactId] = [Extent2].[ContactId]) AND (6 = [Extent2].[NewsletterLogTypeId])) AS [C1]
            FROM [dbo].[Contact] AS [Extent1]
        )  AS [Project1]
        WHERE ([Project1].[CompanyId] = @p__linq__0) AND ([Project1].[ContactStatusId] <= 3) AND (0 = [Project1].[C1])
    )  AS [Project2]
)  AS [Project2]
WHERE [Project2].[row_number] > 99
ORDER BY [Project2].[ContactId] ASC',N'@p__linq__0 int',@p__linq__0=4

似乎使用EXISTS进行纯Where运算要比计算Count然后执行Count == 0运算要差得多。

让我知道你们是否发现我的发现有误。 不管“任何与计数”的讨论如何,所有这一切都可以排除的是,将更复杂的LINQ重写为存储过程时会更好;)。


#2楼

编辑:在EF版本6.1.1中已修复。 而且这个答案不再实际

对于SQL Server和EF4-6,Count()的执行速度大约是Any()的两倍。

当您运行Table.Any()时,它会生成类似的内容( 警报:尝试理解它不会伤及大脑

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

需要根据情况扫描2行。

我不喜欢写Count() > 0因为它隐藏了我的意图。 我更喜欢使用自定义谓词:

public static class QueryExtensions
{
    public static bool Exists<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)
    {
        return source.Count(predicate) > 0;
    }
}

#3楼

这取决于数据集的大小以及您对性能的要求是什么?

如果没什么大不了的,请使用最易读的形式,对我自己来说是任何形式,因为它更短,更易读,而不是方程式。


#4楼

由于这是一个非常受欢迎的话题,并且答案各不相同,因此我不得不重新审视这个问题。

测试环境: EF 6.1.3,SQL Server,30万条记录

桌子型号

class TestTable
{
    [Key]
    public int Id { get; set; }

    public string Name { get; set; }

    public string Surname { get; set; }
}

测试代码:

class Program
{
    static void Main()
    {
        using (var context = new TestContext())
        {
            context.Database.Log = Console.WriteLine;

            context.TestTables.Where(x => x.Surname.Contains("Surname")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Any(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname")).Count(x => x.Id > 1000);
            context.TestTables.Where(x => x.Surname.Contains("Surname") && x.Name.Contains("Name")).Count(x => x.Id > 1000);

            Console.ReadLine();
        }
    }
}

结果:

Any()〜3毫秒

Count()〜230ms用于第一次查询,〜400ms用于第二次查询

备注:

就我而言,EF并没有像他的帖子中提到的@Ben那样生成SQL。


#5楼

如果你开始的东西,有一个.Length.Count (如ICollection<T> IList<T> List<T>等) -那么这将是最快的选项,因为它并不需要通过Any()所需的GetEnumerator() / MoveNext() / Dispose()序列来检查非空IEnumerable<T>序列。

仅对于IEnumerable<T> ,然后Any() 通常会更快,因为它只需要查看一次迭代即可。 但是,请注意, Count()的LINQ-to-Objects实现确实检查了ICollection<T> (使用.Count作为优化)-因此,如果您的基础数据源直接是列表/集合,则不会巨大的差异。 不要问我为什么不使用非通用ICollection ...

当然,如果您使用LINQ对其进行过滤( Where等),则将具有基于迭代器块的序列,因此此ICollection<T>优化是无用的。

通常使用IEnumerable<T> :坚持使用Any() ;-p

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!