¿Qué método funciona mejor: .Any () vs .Count ()> 0?

en el espacio de nombres System.Linq , ahora podemos extender nuestros IEnumerable para tener los métodos de extensión Any() y Count() .

Hace poco me dijeron que si quería verificar que una colección contiene 1 o más elementos, debería usar el método de extensión .Any() lugar del método de extensión .Count() > 0 porque el método de extensión .Count() tiene que iterar a través de todos los artículos.

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

sí / nae?

Si está comenzando con algo que tiene un .Length o .Count (como ICollection , IList , List , etc.), esta será la opción más rápida, ya que no necesita vaya a través de la secuencia GetEnumerator() / MoveNext() / Dispose() requerida por Any() para verificar si hay una secuencia IEnumerable no vacía.

Solo por IEnumerable , entonces Any() generalmente será más rápido, ya que solo tiene que mirar una iteración. Sin embargo, tenga en cuenta que la implementación de LINQ-to-Objects de Count() comprueba ICollection (utilizando .Count como optimización), de modo que si su fuente de datos subyacente es directamente una lista / colección, no habrá una gran diferencia No me preguntes por qué no usa ICollection no genérico …

Por supuesto, si ha usado LINQ para filtrarlo, etc. ( Where etc.), tendrá una secuencia basada en bloque iterador, por lo que esta optimización de ICollection es inútil.

En general, con IEnumerable : seguir con Any() ;-p

Nota: escribí esta respuesta cuando Entity Framework 4 era real. El objective de esta respuesta no era entrar en pruebas triviales de rendimiento de .Any() vs .Count() . El punto era señalar que EF está lejos de ser perfecto. Las versiones más nuevas son mejores … pero si tienes parte del código que es lento y usa EF, prueba con TSQL directo y compara el rendimiento en lugar de confiar en suposiciones (que .Any() SIEMPRE es más rápido que .Count() > 0 ) .


Aunque estoy de acuerdo con la mayoría de respuestas y comentarios votados, especialmente sobre el punto Any bash de desarrollador de señales mejor que Count() > 0 – He tenido una situación en la que Count es más rápido por orden de magnitud en SQL Server (EntityFramework 4).

Aquí está la consulta con Any that thew timeout exception (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 del Count ejecutó 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(); 

Necesito 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 seguir con Any en todos los casos.

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

ANY :

 exec sp_executesql N'SELECT TOP (1) 
 [Proyecto2]. [ContactId] AS [ContactId], 
 [Proyecto2]. [CompanyId] AS [CompanyId], 
 [Proyecto2]. [ContactName] AS [ContactName], 
 [Proyecto2]. [Nombre completo] AS [Nombre completo], 
 [Proyecto2]. [ContactStatusId] AS [ContactStatusId], 
 [Proyecto2]. [Creado] AS [Creado]
 FROM (SELECCIONE [Proyecto2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Proyecto2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Creado] AS [Creado], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
     DESDE (SELECCIONAR 
         [Extensión1]. [ContactId] AS [ContactId], 
         [Extensión1]. [Id empresa] AS [Id empresa], 
         [Extensión1]. [Nombre de contacto] AS [Nombre de contacto], 
         [Extensión1]. [Nombre completo] AS [Nombre completo], 
         [Extensión1]. [ContactStatusId] AS [ContactStatusId], 
         [Extensión1]. [Creado] AS [Creado]
         FROM [dbo]. [Contacto] AS [Extensión1]
         DONDE ([Extensión1]. [ID de empresa] = @ p__linq__0) Y ([Extensión1]. [Id. De informe de contacto] <= 3) Y (NO EXISTE (SELECCIONAR) 
             1 AS [C1]
             FROM [dbo]. [NewsletterLog] AS [Extensión2]
             DONDE ([Extensión1]. [Id. De contacto] = [Extensión2]. [Id. De contacto]) AND (6 = [Extensión2]. [Nro. De boletín informativo])
         )
     ) AS [Proyecto2]
 ) AS [Proyecto2]
 DONDE [Proyecto2]. [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) 
 [Proyecto2]. [ContactId] AS [ContactId], 
 [Proyecto2]. [CompanyId] AS [CompanyId], 
 [Proyecto2]. [ContactName] AS [ContactName], 
 [Proyecto2]. [Nombre completo] AS [Nombre completo], 
 [Proyecto2]. [ContactStatusId] AS [ContactStatusId], 
 [Proyecto2]. [Creado] AS [Creado]
 FROM (SELECCIONE [Proyecto2]. [ContactId] AS [ContactId], [Project2]. [CompanyId] AS [CompanyId], [Project2]. [ContactName] AS [ContactName], [Project2]. [FullName] AS [FullName] , [Proyecto2]. [ContactStatusId] AS [ContactStatusId], [Project2]. [Creado] AS [Creado], row_number () OVER (ORDER BY [Project2]. [ContactId] ASC) AS [row_number]
     DESDE (SELECCIONAR 
         [Proyecto1]. [ContactId] AS [ContactId], 
         [Proyecto1]. [CompanyId] AS [CompanyId], 
         [Proyecto1]. [ContactName] AS [ContactName], 
         [Proyecto1]. [Nombre completo] AS [Nombre completo], 
         [Proyecto1]. [ContactStatusId] AS [ContactStatusId], 
         [Proyecto1]. [Creado] AS [Creado]
         DESDE (SELECCIONAR 
             [Extensión1]. [ContactId] AS [ContactId], 
             [Extensión1]. [Id empresa] AS [Id empresa], 
             [Extensión1]. [Nombre de contacto] AS [Nombre de contacto], 
             [Extensión1]. [Nombre completo] AS [Nombre completo], 
             [Extensión1]. [ContactStatusId] AS [ContactStatusId], 
             [Extensión1]. [Creado] AS [Creado], 
             (SELECCIONAR 
                 COUNT (1) AS [A1]
                 FROM [dbo]. [NewsletterLog] AS [Extensión2]
                 DONDE ([Extensión1]. [Id. De contacto] = [Extensión2]. [Id. De contacto]) AND (6 = [Extensión2]. [Nro. De claseTarjetaDeLibro])) AS [C1]
             FROM [dbo]. [Contacto] AS [Extensión1]
         ) AS [Proyecto1]
         DONDE ([Proyecto1]. [ID de empresa] = @ p__linq__0) AND ([Proyecto1]. [ContactStatusId] <= 3) AND (0 = [Proyecto1]. [C1])
     ) AS [Proyecto2]
 ) AS [Proyecto2]
 DONDE [Proyecto2]. [Row_number]> 99
 ORDER BY [Project2]. [ContactId] ASC ', N' @ p__linq__0 int ', @ p__linq__0 = 4

Parece que “puro” con EXISTS funciona mucho peor que cuando se calcula el conteo y luego se hace “Where with Count == 0”.

Avíseme si ustedes ven algún error en mis hallazgos. 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;).

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

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

Modelo de tabla :

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

Código de 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:

Cualquiera () ~ 3ms

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

Observaciones:

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

EDITAR: se arregló en EF versión 6.1.1. y esta respuesta no es más real

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

Cuando ejecuta Table.Any (), generará algo así como ( alerta: no duele el 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] 

eso requiere 2 escaneos de filas con su condición.

No me gusta escribir Count() > 0 porque oculta mi intención. Prefiero usar un predicado personalizado para esto:

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

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

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

Depende de qué tan grande es el conjunto de datos y cuáles son sus requisitos de rendimiento.

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

Puede hacer una prueba simple para resolver esto:

 var query = //make any query here 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; 

Verifique los valores de testCount y testAny.

Acerca del método Count () , si IEnumarable es una colección IC , no podemos iterar en todos los ítems porque podemos recuperar el campo Count de ICollection , si IEnumerable no es una ICollection debemos iterar en todos los ítems usando un tiempo con a MoveNext , eche un vistazo al Código de .NET Framework:

 public static int Count(this IEnumerable source) { if (source == null) throw Error.ArgumentNull("source"); ICollection collectionoft = source as ICollection; if (collectionoft != null) return collectionoft.Count; ICollection collection = source as ICollection; if (collection != null) return collection.Count; int count = 0; using (IEnumerator e = source.GetEnumerator()) { checked { while (e.MoveNext()) count++; } } return count; } 

Referencia: fuente de referencia enumerable