Skip to content

Case 13: The Inefficient Existence Check: .Count() > 0 vs. .Any()

The Story

Imagine you are building a feature in an ASP.NET Core web application. Before a user can delete a Brand, the system must check if there are any Products still associated with that brand.

The Problem Code (Doing Too Much Work)

csharp
public async Task<bool> CanDeleteBrandAsync(int brandId)
{
    // MISTAKE: Using CountAsync() just to check if something exists.
    var productCount = await _context.Products
                                     .Where(p => p.BrandId == brandId)
                                     .CountAsync();

    return productCount == 0;
}

Why is this a problem?

  1. The Generated SQL: EF Core translates the .CountAsync() method into a SQL query like this:
    sql
    SELECT COUNT(*)
    FROM "Products"
    WHERE "BrandId" = @p0;
  2. Extra Work for the Database:
    • The database has to find all products that belong to that brand and count every single one.
    • If a brand has 500,000 products, the database will count all 500,000 of them.
  3. The Core Issue: We don't need to know the exact number is 500,000. We only need to know if the number is greater than 0. The database is doing a lot of heavy lifting just to give us a "yes/no" answer.

The Solution: Use .Any() to Check for Existence

The .Any() method is designed for exactly this purpose. It translates into a query that checks if "any row" matches the condition.

Here is the better code:

csharp
public async Task<bool> CanDeleteBrandAsync(int brandId)
{
    // OPTIMIZED: Use AnyAsync().
    // "Are there any products with this BrandId?"
    var brandHasProducts = await _context.Products
                                         .AnyAsync(p => p.BrandId == brandId);

    // Return true if there are NO products
    return !brandHasProducts;
}

What We Gained

  1. More Efficient SQL: .AnyAsync() is translated into a much more efficient query, often using EXISTS or LIMIT 1.
    sql
    -- Equivalent SQL
    SELECT CASE
        WHEN EXISTS (
            SELECT 1
            FROM "Products"
            WHERE "BrandId" = @p0)
        THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT)
    END
  2. Short-circuiting: The database will use an index on BrandId and stop searching as soon as it finds the very first matching product. It doesn't need to count the other 499,999 products.
  3. Superior Performance: The execution time of .AnyAsync() is almost constant and extremely fast, whether the brand has 1 product or 1 million products. In contrast, the time for .CountAsync() increases with the number of products.

The Golden Rule

  • When you need to check for existence (yes or no), use .Any().
  • When you need to know the exact number, use .Count().

This is the LINQ version of using EXISTS instead of COUNT(*) in raw SQL. A small change in your C# code can make a huge difference in database performance.