Analyzer rule

LC003: Prefer Any() Over Count() Existence Checks

LC003 flags Count() > 0 style existence checks on EF Core queries and suggests Any() or AnyAsync(), which can stop at the first matching row.

Default severity
Warning
Code fix
Available
Category
Performance

Change the severity in .editorconfig:

[*.cs]
dotnet_diagnostic.LC003.severity = error

Sample code · Analyzer source

LC003: Prefer Any() Over Count() Existence Checks

In Plain Terms

Imagine you want to know if there are any cookies left in the jar. Count() > 0 is like dumping the entire jar onto the table and counting 500 cookies one by one just to say “Yes”. Any() is like opening the lid, seeing one cookie, and saying “Yes” immediately.

What It Flags

LC003 reports IQueryable existence checks that use Count, LongCount, CountAsync, or LongCountAsync when Any or AnyAsync communicates the same intent more directly.

if (db.Users.Count(u => u.Active) > 0) // LC003
{
    // ...
}
var hasUsers = await db.Users.LongCountAsync() != 0; // LC003
return db.Users.Count() == 0; // LC003

Why It Matters

For relational providers, Any() is normally translated as an existence query such as EXISTS or a single-row probe. The database can stop after the first qualifying row.

Count() asks for the full cardinality. When the result is only compared with zero or one to answer “does at least one row exist?”, counting every matching row is unnecessary work and can force more scanning, aggregation, and memory pressure on large datasets.

Safer Shapes

Use Any() when the question is “are there any rows?”

if (db.Users.Any(u => u.Active))
{
    // ...
}

Use !Any() when the question is “are there no rows?”

return !db.Users.Any();

Use AnyAsync() for EF Core async queries.

var hasUsers = await db.Users.AnyAsync();

Patterns

These existence checks report:

query.Count() > 0;
0 < query.Count();
query.Count() >= 1;
1 <= query.Count();
query.Count() != 0;
0 != query.Count();
query.Count() == 0;
0 == query.Count();
await query.CountAsync() == 0;
await query.LongCountAsync() != 0;

The rule is not limited to if statements. It also reports in boolean assignments, return expressions, ternary conditions, and other scalar expression contexts.

var hasAny = query.Count() > 0;      // LC003
return query.Count() == 0;          // LC003
var state = query.Count() != 0 ? 1 : 0; // LC003

When Count Is Correct

Keep Count() when the actual total matters.

var total = await db.Users.CountAsync();

Keep Count() for thresholds that cannot be expressed as a simple existence check.

if (db.Users.Count() > 1)
{
    // Need at least two rows, not just one.
}
if (db.Users.Count() >= 2)
{
    // Also not equivalent to Any().
}

The rule deliberately stays quiet for those thresholds.

Scope

LC003 only targets IQueryable sources. It does not report List<T>.Count, array lengths, or in-memory IEnumerable<T> counts because those APIs have different cost models and may already be cheap or precomputed.

It recognises the LINQ Queryable.Count/LongCount methods and EF Core CountAsync/LongCountAsync extension methods. Provider-specific SQL may vary, but the existence-vs-cardinality intent is provider-independent.

Fix Strategy

The fixer rewrites the comparison expression:

  • Count() > 0, Count() >= 1, and Count() != 0 become Any().
  • Count() == 0 becomes !Any(), including named constants that fold to zero.
  • Async count methods become await AnyAsync().
  • Predicate counts keep their predicate: Count(x => x.Active) > 0 becomes Any(x => x.Active).

The fixer changes only the existence-check expression. It does not rewrite cases where the numeric count result is used for display, pagination, thresholds above one, or any business rule that needs the exact total.