2008-11-20 9 views
459

w przestrzeni nazw System.Linq, możemy teraz rozszerzyć nasze IEnumerable, aby uzyskać Any() i Count()metody rozszerzania.Która metoda działa lepiej: .Any() vs .Count()> 0?

powiedziano mi niedawno, że jeśli chcę, aby sprawdzić, że zbiór zawiera 1 lub więcej elementów wewnątrz niej, należy użyć metody .Any() przedłużacza zamiast metody .Count() > 0 przedłużacz, ponieważ metoda .Count() rozszerzenie musi iterację wszystkich elementów .

Po drugie, niektóre kolekcje mają właściwość (nie jest to metoda rozszerzająca), która jest Count lub Length. Czy byłoby lepiej użyć tych, zamiast .Any() lub .Count()?

tak/nie?

Odpowiedz

566

Jeśli zaczynasz z czymś, co ma .Length lub .Count (takich jak ICollection<T>, IList<T>, List<T> itp) - wtedy będzie to najszybsza opcja, ponieważ nie muszą przejść przez GetEnumerator()/MoveNext()/Dispose() sekwencja wymagana przez Any(), aby sprawdzić niepustą sekwencję IEnumerable<T>.

Dla prostu IEnumerable<T>, następnie Any() będzie ogólnie być szybciej, ponieważ ma tylko spojrzeć na jednej iteracji. Zauważ jednak, że implementacja LINQ-to-Object dla Count() sprawdza się pod kątem ICollection<T> (używając jako optymalizacji) - więc jeśli twoje podstawowe źródło danych to bezpośrednio lista/kolekcja, nie będzie dużej różnicy. Nie pytaj mnie, dlaczego nie używa on nietypowego ICollection ...

Oczywiście, jeśli użyłeś LINQ do filtrowania go itp. (Where itp.), Będziesz miał sekwencję opartą na bloku iteratora, i tak ta optymalizacja ICollection<T> jest bezużyteczna.

Ogólnie z IEnumerable<T>: kij z Any() ;-P

+5

Marc: ICollection w rzeczywistości nie pochodzi z ICollection. Ja też byłem zaskoczony, ale Reflector nie kłamie. –

+7

Czy nie wykonuje kontroli Any() dla interfejsu ICollection i sprawdza po dla właściwości Count? – derigel

+5

Nie, nie ma (po sprawdzeniu reflektora) –

6

Cóż, metoda .Count() rozszerzenie nie będzie użyć właściwości .Count, ale zakładam, że nie będzie korzystać z metody .Count() prostego gromadzenia danych, ale raczej na końcu instrukcji LINQ z kryteriów filtrowania, etc.

W tym kontekście .Any() będzie szybszy niż .Count() > 0.

+8

W rzeczywistości dla ICollection będzie. Ale jak mówisz - to nie pomaga po filtrowaniu itp. –

53

Uwaga: Napisałem tę odpowiedź kiedy Entity Framework 4 była rzeczywista. Celem tej odpowiedzi nie było uzyskanie łatwego testu wydajności .Any() vs .Count(). Chodziło o to, by sygnalizować, że EF jest daleki od doskonałości. Nowsze wersje są lepsze ... ale jeśli masz część kodu, która jest wolna i używa EF, przetestuj z bezpośrednim TSQL i porównaj wydajność, zamiast polegać na założeniach (że .Any() jest ZAWSZE szybszy niż .Count() > 0).


Chociaż zgadzam się z najbardziej-głosowałem odpowiedź i komentarze - szczególnie na punkcie Any sygnałów intencyjny deweloper lepiej niż Count() > 0 - miałem sytuację, w której hrabia jest szybsza o rząd wielkości na SQL Server (EntityFramework 4).

Oto zapytanie z Any że thew Timeout wyjątku (na ~ 200,000 rekordy):

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 wersja wykonana w ciągu kilku milisekund:

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(); 

muszę znaleźć sposób, aby zobaczyć co dokładnie SQL oba LINQ produkują - ale jest oczywiste, że istnieje ogromna różnica wydajności między Count i Any w niektórych przypadkach, i niestety wydaje się, że nie można po prostu trzymać się z Any we wszystkich przypadkach.

EDYCJA: Tutaj generowane są instrukcje SQL. Beauties jak widać;)

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 

Wydaje się, że z czystym Gdzie ISTNIEJE działa znacznie gorsze niż obliczania policzyć i wtedy robi Gdzie z hrabiego == 0

Daj mi znać, jeśli zauważysz błąd w swoich ustaleniach. To, co można z tego wyrzucić bez względu na dyskusję Any vs Count, oznacza, że ​​jakikolwiek bardziej złożony LINQ jest o wiele lepszy, gdy przepisywany jest jako procedura zapisana;).

+2

Chciałbym zobaczyć plany SQL Query, które są generowane przez każde zapytanie linq dla każdego scenariusza. –

+29

na podstawie SQL, wszystko, co mogę powiedzieć, to: oba zapytania wyglądają okropnie. Wiedziałem, że był powód, dla którego normalnie piszę własny TSQL ... –

+0

! Każdy musiałby przejrzeć wszystkie wiersze, tak jak Hrabia. To, że twój przykład daje taki przerażający wynik, jest trochę dziwne, w najgorszym przypadku! Każdy powinien być tylko trochę wolniejszy niż Count. W twoim przypadku szukałbym sposobów na uproszczenie selekcji, być może podzielenie jej na etapy lub zmianę warunków, jeśli to możliwe. Ale twoja uwaga, że ​​Dowolna jest lepsza niż reguła nie ma znaczenia! Każda jest lepsza niż hrabia jest bardzo dobra. – Bent

9

EDYTOWANIE: zostało naprawione w wersji EF 6.1.1. i ta odpowiedź nie jest już aktualna. Dla SQL Servera i EF4-6, Count() wykonuje około dwa razy szybciej niż Any().

Po uruchomieniu Table.Any(), będzie generować coś podobnego (wpisu: nie boli mózg próbuje zrozumieć)

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] 

że wymaga 2 skany wierszy z warunkiem .

Nie lubię pisać Count() > 0, ponieważ to ukrywa moją intencję. Wolę używać do tego celu predykatu niestandardowego:

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

Też to zauważyłem. Any() SQL nie ma żadnego sensu. Nie jestem pewien, dlaczego nie robią: CASE WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. Nie mogę wymyślić powodu, dla którego muszą wykonać NOT NOTA, aby zwrócić 0. –

+0

To jest fałsz. Znalazłeś zły plan zapytania losowo. To się stało. Każda jest prawie zawsze szybsza. – usr

+0

Sprawdziłem sql wygenerowany w 6.1.3, naprawiłem go: WYBIERZ PRZYPADKĘ KIEDY (EGZAMIN (WYBIERZ 1 AS [C1] FROM [dbo]. [TestTables] AS [Extent1] WHERE [Extent1]. [Id ]> 1000 )) THEN cast (1 as bit) ELSE cast (0 jako bit) END AS [C1] FROM (WYBIERZ 1 AS X) JAK [SingleRowTable1] – Ben

4

To zależy od tego, jak duży jest zestaw danych i jakie są twoje wymagania dotyczące wydajności?

Jeśli to nic wielkiego, użyj najbardziej czytelnej formy, , która dla mnie jest dowolna, ponieważ jest krótsza i czytelniejsza niż równanie.

13

Ponieważ jest to dość popularny temat i odpowiedzi różnią się, musiałem przyjrzeć się świeżemu problemowi.

Testowanie env: EF 6.1.3, SQL Server, 300K rekordy

modelu Tabela: Kod

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

    public string Name { get; set; } 

    public string Surname { get; set; } 
} 

Test:

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(); 
     } 
    } 
} 

Wyniki :

Obojętnie() ~ 3ms

Count() ~ 230ms dla pierwszego zapytania, ~ 400ms dla drugich

Uwagi:

dla mojego przypadku EF nie generować jak SQL @Ben wspomniany w swoim poście.

+1

Aby uzyskać prawidłowe porównanie, powinieneś zrobić: 'Count() > 0'. :RE – Andrew

0

Można zrobić prosty test, aby dowiedzieć się tego:

var query = // żadnego zapytania tutaj

  var timeCount = new Stopwatch(); 
      timeCount.Start(); 
      if (query.Count > 0) 
      { 

      } 
      timeCount.Stop(); 
      var testCount = timeCount.Elapsed; 

      var timeAny = new Stopwatch(); 
      timeAny.Start(); 
      if (query.Any()) 
      { 

      } 
      timeAny.Stop(); 
      var testAny = timeAny.Elapsed; 

sprawdzenia wartości testCount i testAny.