哪一種方法效果更好:.Any()與.Count()> 0?

System.Linq命名空間中,咱們如今能夠將IEnumerable擴展爲具備Any()Count() 擴展方法sql

最近,我被告知,若是我要檢查一個集合包含在它裏面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)。 this

這是帶有Any that thew timeout異常(約200.000條記錄)的查詢: spa

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以毫秒爲單位的版本: code

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之間存在巨大的性能差別,不幸的是,您彷佛不能在全部狀況下都堅持使用Anyip

編輯:這是生成的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

相關文章
相關標籤/搜索