2008-11-20 11 views
459

en el espacio de nombre System.Linq, ahora podemos ampliar nuestros IEnumerable para tener los métodos de extensión Any() y Count().Qué método funciona mejor: .Any() vs .Count()> 0?

poco me dijeron que si quiero comprobar que una colección contiene 1 o más elementos en su interior, que debe utilizar el método .Any() extensión en lugar del método .Count() > 0 extensión debido a que el método de .Count() extensión tiene que recorrer todos los elementos .

En segundo lugar, algunas colecciones tienen una propiedad (no un método de extensión) que es Count o Length. ¿Sería mejor utilizar esos, en lugar de .Any() o .Count()?

yea/nae?

Respuesta

566

Si usted está comenzando con algo que tiene un .Length o .Count (como ICollection<T>, IList<T>, List<T>, etc) - entonces esta será la opción más rápida, ya que no tiene que pasar por el GetEnumerator()/MoveNext()/Dispose() secuencia requerida por Any() para comprobar si hay una secuencia IEnumerable<T> no vacía.

Por tan sólo IEnumerable<T>, a continuación, se Any()general ser más rápido, ya que sólo tiene que mirar a una iteración. Sin embargo, tenga en cuenta que la implementación de LINQ-to-Objects de Count() comprueba ICollection<T> (utilizando .Count como optimización), por lo que si su fuente de datos subyacente es directamente una lista/colección, no habrá una gran diferencia. No me pregunte por qué no utiliza el ICollection no genérico ...

Por supuesto, si ha utilizado LINQ para filtrarlo, etc. (Where etc.), tendrá una secuencia basada en bloque iterador, y entonces esta optimización ICollection<T> es inútil.

En general, con IEnumerable<T>: seguir con Any() ;-P

+5

Marc: ICollection en realidad no se deriva de ICollection. También me sorprendió, pero Reflector no miente. –

+7

¿La implementación de Any() no comprueba la interfaz ICollection y la marca para la propiedad Count? – derigel

+5

No, no lo hace (habiendo comprobado el reflector) –

6

Bueno, el método .Count() extensión no utilizar la propiedad .Count, pero yo asumiría que no se usará el método .Count() para una colección sencilla, sino más bien al final de una declaración de LINQ con criterios de filtrado, etc.

En ese contexto, .Any() será más rápido que .Count() > 0.

+8

En realidad, para una ICollection , lo hará. Pero como dices, esto no ayuda una vez que has filtrado, etc. –

53

Nota: escribí esta respuesta cuando Entity Framework 4 era real. El objetivo de esta respuesta no era entrar en las triviales pruebas de rendimiento .Any() vs .Count(). El punto era señalar que EF está lejos de ser perfecto. Las versiones más nuevas son mejores ... pero si tiene una parte del código lenta y utiliza EF, pruebe con TSQL directo y compare el rendimiento en lugar de basarse en suposiciones (que .Any() SIEMPRE es más rápido que .Count() > 0).


Aunque estoy de acuerdo con la información más votado respuesta y comentarios - sobre todo en el punto Any señales desarrollador intención mejor que Count() > 0 - que he tenido situación en la que Contador es más rápido por orden de magnitud en SQL Server (EntityFramework 4).

Aquí es consulta con Any que Thew excepción de tiempo de espera (en ~ 200.000 registros):

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 versión ejecutado en cuestión de milisegundos:

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

Tengo que encontrar una manera de ver qué SQL exacto producen ambos LINQs, pero es obvio que hay una enorme diferencia de rendimiento entre Count y Any en algunos casos, y desafortunadamente parece que no puedes quedarte con Any en todos los casos.

EDITAR: Aquí están los SQL generados. Bellezas como se puede ver;)

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 

Parece que pura Donde con EXISTS trabajos mucho peores que el cálculo de conde y luego hacer Dónde con el Conde == 0

Avíseme si observa algún error en mis conclusiones. Lo que se puede sacar de todo esto independientemente de la discusión de Any vs Count es que cualquier LINQ más complejo es mucho mejor cuando se reescribe como Procedimiento almacenado;).

+2

Me encantaría ver algunos planes de consultas SQL que cada consulta de linq genera para cada escenario. –

+29

basado en el SQL, todo lo que puedo decir es: ambas consultas se ven horribles. Sabía que había una razón por la que normalmente escribo mi propio TSQL ... –

+0

. Cualquiera tendría que mirar a través de todas las filas como lo haría Count. ¡Que tu ejemplo dé un resultado tan horrible es un poco extraño, en el peor de los casos! Cualquiera debería ser solo un poco más lento que Count. En su caso, buscaría formas de simplificar la selección, tal vez dividirla en etapas o reordenar las condiciones, si eso fuera posible. ¡Pero su punto de que el Any es mejor que Count regla no es válido! Cualquiera es mejor que Count es uno muy bueno. – Bent

9

EDIT: se corrigió en EF versión 6.1.1. y esta respuesta ya no es real

Para SQL Server y EF4-6, Count() funciona aproximadamente dos veces más rápido que Any().

Cuando se ejecuta Table.Any(), que va a generar algo así como (alerta: no hacen daño al cerebro tratando de entenderlo)

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] 

que requiere 2 scans de filas con su condición .

No me gusta escribir Count() > 0 porque oculta mi intención. Yo prefiero usar predicado a medida para esto:

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

Me di cuenta de esto también. Any() SQL no tiene ningún sentido en absoluto. No estoy seguro de por qué no lo hacen: CASE WHEN (EXISTS (sql)) THEN 1 ELSE 0 END. No puedo pensar en una razón por la que necesitan hacer un NO EXISTE para devolver 0. –

+0

Esto es falso. Encontraste un mal plan de consulta por casualidad. Esto pasa. Cualquiera es, casi siempre, más rápido. – usr

+0

Comprobé el sql generada en 6.1.3, ellas fijas que:.. SELECCIONAR CASO CUANDO (EXISTE (SELECCIONAR 1 AS [C1] DE [dbo] [TestTables] AS [Extent1] DONDE [Extent1] [Id ]> 1000 )) THEN cast (1 como bit) ELSE cast (0 como bit) END AS [C1] FROM (SELECCIONAR 1 COMO X) AS [SingleRowTable1] – Ben

4

Depende, ¿Cuán grande es el conjunto de datos y cuáles son sus requisitos de rendimiento?

Si no es nada gigantesco use la forma más legible, que para mí es cualquiera, porque es más corta y legible que una ecuación.

13

Dado que este es un tema bastante popular y las respuestas difieren, tuve que echar un vistazo fresco al problema.

Prueba env: EF 6.1.3, SQL Server, 300k registros

modelo Tabla: Código

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

    public string Name { get; set; } 

    public string Surname { get; set; } 
} 

prueba:

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

Resultados :

Cualquier() ~ 3 ms

Count() ~ 230ms para primera consulta, ~ 400 ms para la segunda

Observaciones:

para mi caso EF no generó SQL como @Ben mencionado en su publicación.

+1

Para una comparación adecuada, debe hacer 'Count() > 0'. :RE – Andrew

0

se puede hacer una prueba sencilla de resolver esto:

var query = // hacer cualquier consulta aquí

  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; 

Comprobar los valores de testCount y testAny.

Cuestiones relacionadas