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


En el espacio de nombres System.Linq, ahora podemos extender nuestros IEnumerable ' s para tener elAny() y Count() métodos de extensión .

Me dijeron recientemente que si quiero verificar que una colección contiene 1 o más elementos dentro de ella, debería usar el método de extensión .Any() en 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 elementos.

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

Yea / nae ?

Author: Pure.Krome, 2008-11-20

8 answers

Si 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 necesita pasar por el GetEnumerator()/MoveNext()/Dispose() secuencia requerida por Any() para verificar una secuencia IEnumerable<T> no vacía.

Para solo IEnumerable<T>, 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<T> (usando .Count como un optimización) - así 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 el ICollection no genérico...

Por supuesto, si ha utilizado LINQ para filtrarlo etc (Where etc), tendrá una secuencia basada en bloques iteradores, por lo que esta optimización ICollection<T> es inútil.

En general con IEnumerable<T>: quédate con Any(); - p

 609
Author: Marc Gravell,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-11-20 12:51:19

Nota: Escribí esta respuesta cuando Entity Framework 4 era real. El objetivo de esta respuesta no era entrar en pruebas de rendimiento triviales .Any() vs .Count(). El objetivo era señalar que EF dista mucho de ser perfecto. Las versiones más nuevas son mejores... pero si tiene una parte del código que es lenta y utiliza EF, pruebe con TSQL directo y compare 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 respuesta más votada y comentarios-especialmente en el punto Any señales intención del desarrollador mejor que Count() > 0 - He tenido una situación en la que el Recuento es más rápido por orden de magnitud en SQL Server (EntityFramework 4).

Aquí está la consulta con Any que la excepción de tiempo de espera w (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 ejecutada 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 un gran rendimiento diferencia entre Count y Any en algunos casos, y desafortunadamente parece que no puede quedarse con Any en todos los casos.

EDITAR: Aquí se generan SQL. 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 pure Where with EXISTS funciona mucho peor que calcular el Conteo y luego hacer Where with Count == 0.

Háganme saber si ven algún error en mis hallazgos. Lo que se puede sacar de todo esto independientemente de Cualquier vs La discusión de Count es que cualquier LINQ más complejo es mucho mejor cuando se reescribe como Procedimiento almacenado ;).

 55
Author: kape123,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-01-30 18:16:39

Dado que este es un tema bastante popular y las respuestas difieren, tuve que tomar una nueva mirada sobre el problema.

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

Modelo de cuadro :

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

    public string Name { get; set; }

    public string Surname { get; set; }
}

Código de ensayo:

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:

Any () ~ 3ms

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

Observaciones:

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

 16
Author: kamil-mrzyglod,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-28 08:14:57

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 aproximadamente dos veces más rápido que cualquier().

Cuando se ejecuta la tabla.Cualquier (), generará algo como (alerta: no lastimes 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]

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

No me gusta escribir Count() > 0 porque oculta mi intención. Prefiero usar predicado personalizado 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;
    }
}
 10
Author: Ben,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-13 21:58:50

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

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

 7
Author: Lasse Vågsæther Karlsen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-09 13:37:46

Depende, ¿qué tan 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 corto y legible en lugar de una ecuación.

 5
Author: Timothy Gonzalez,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-12-22 23:43:13

Puedes hacer una prueba simple para averiguar 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;

Compruebe los valores de testCount y testAny.

 2
Author: Bronks,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-05-08 15:18:41

Sobre el método Count(), si el {[2] }Enumarable es un ICollection, entonces no podemos iterar a través de todos los elementos porque podemos recuperar el campo Count de ICollection, si el {[2] }Enumerable no es un ICollection debemos iterar a través de todos los elementos usando un mientras que con un MoveNext, eche un vistazo al Código de. NET Framework:

public static int Count<TSource>(this IEnumerable<TSource> source)
{
    if (source == null) 
        throw Error.ArgumentNull("source");

    ICollection<TSource> collectionoft = source as ICollection<TSource>;
    if (collectionoft != null) 
        return collectionoft.Count;

    ICollection collection = source as ICollection;
    if (collection != null) 
        return collection.Count;

    int count = 0;
    using (IEnumerator<TSource> e = source.GetEnumerator())
    {
        checked
        {
            while (e.MoveNext()) count++;
        }
    }
    return count;
}

Referencia: Fuente de referencia Enumerable

 1
Author: Thiago Coelho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-03-28 17:35:36