Analyzer rule
Spec: LC021 - Avoid IgnoreQueryFilters
EF Core LINQ performance analyzer and Roslyn analyzer for catching query issues at compile time.
Spec: LC021 - Avoid IgnoreQueryFilters
Goal
Detect usage of EF Core’s IgnoreQueryFilters() on an IQueryable. Global query filters are often used for critical cross-cutting concerns like multi-tenancy, soft-delete, or security. Bypassing them can lead to data leaks or incorrect business logic.
The Problem
Global query filters are applied automatically to all queries for a given entity type. IgnoreQueryFilters() disables them for the current query. While sometimes necessary (e.g., for administrative tools or restoring soft-deleted items), it is often used accidentally or without full understanding of the security implications.
Example Violation
// Violation: Might bypass multi-tenancy or soft-delete filters
var allUsers = db.Users.IgnoreQueryFilters().ToList();
The Fix
Ensure that bypassing global filters is intentional and documented. If possible, use explicit filtering instead of relying on global filters if you need to access “filtered out” data frequently.
Analyzer Logic
ID: LC021
Category: Security (or Reliability)
Severity: Warning
Algorithm
- Target Method: Intercept invocations named
IgnoreQueryFilters. - EF Core Boundary: Require
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.IgnoreQueryFilters. - Query Boundary: Require an
IQueryablereceiver so unrelated instance methods or customIEnumerablehelpers with the same name stay silent.
Test Cases
Violations
db.Users.IgnoreQueryFilters().Where(x => x.Active);
db.Users.IgnoreQueryFilters(new[] { "TenantFilter" }).ToList();
EntityFrameworkQueryableExtensions.IgnoreQueryFilters(db.Users).ToList();
Valid
db.Users.Where(x => x.Active);
LC021 intentionally stays quiet for lookalikes that are not the EF Core extension method:
// Custom IQueryable helper outside Microsoft.EntityFrameworkCore is not LC021.
CustomQueryExtensions.IgnoreQueryFilters(query);
// Instance or IEnumerable helpers with the same name are not LC021.
auditQuery.IgnoreQueryFilters();
values.IgnoreQueryFilters();
Shipped Behavior
LC021 reports EF Core IgnoreQueryFilters() calls so filter bypasses are visible during review. That includes EF Core’s named-filter overload, because IgnoreQueryFilters(filterKeys) still disables the named filters passed to it. The fixer removes the call when the bypass is accidental, including static extension-method syntax such as EntityFrameworkQueryableExtensions.IgnoreQueryFilters(query) and named-filter syntax such as query.IgnoreQueryFilters(filterKeys); keep the diagnostic suppressed or documented only when the query intentionally crosses tenant, soft-delete, or security-filter boundaries.
Intentional bypasses should be local and auditable:
#pragma warning disable LC021
var reviewedUser = db.Users
.IgnoreQueryFilters()
.TagWith("Audited tenant-review bypass")
.Where(user => user.Id == userId)
.ToList();
#pragma warning restore LC021
Prefer a narrow pragma around the reviewed query over disabling LC021 for a whole file or project. If a query needs to bypass filters regularly, prefer a named repository/service method that documents the business reason and applies explicit replacement filters.
For approved service-level bypasses, a targeted SuppressMessage attribute with a concrete justification is also supported:
[SuppressMessage("Security", "LC021", Justification = "Reviewed tenant-admin bypass.")]
public List<User> GetAllTenantsForAdminReview()
{
return db.Users
.IgnoreQueryFilters()
.Where(user => user.IsActive)
.ToList();
}
Method-level and type-level SuppressMessage suppressions are supported for reviewed bypass services. Repository-wide analyzer config such as dotnet_diagnostic.LC021.severity = none is also honored by Roslyn, but treat it as a project policy decision rather than a local exception. Generated code is excluded from LC021 analysis.
The fixer is intentionally narrow: it removes only the .IgnoreQueryFilters() call, or the equivalent static extension wrapper, and preserves the rest of the query chain. For named-filter overloads, extension syntax keeps the original query receiver (query.IgnoreQueryFilters(filterKeys) becomes query) while static syntax keeps the explicit source query argument (EntityFrameworkQueryableExtensions.IgnoreQueryFilters(query, filterKeys) and EntityFrameworkQueryableExtensions.IgnoreQueryFilters(filterKeys: filters, source: query) both become query). Do not apply the fixer when the bypass is part of an approved administrative, tenant-review, soft-delete restore, or security-audit workflow.