EF Core: Query Filters
Reading Time: 2 minutes
Problem
As applications grow, the same Where clauses get repeated everywhere:
IsDeleted == falsefor soft-deleteTenantId == currentTenantIdfor multi-tenant isolation- other visibility rules based on status or ownership
This leads to common issues:
- missed filters in one endpoint causing data leaks
- duplicated query logic across services and repositories
- inconsistent behavior between read paths
- harder maintenance when rules change
In short, relying on developers to remember critical filters in every query is error-prone.
Solution
Use global query filters with HasQueryFilter so EF Core applies rules automatically to all queries for an entity.
Typical uses:
- soft-delete filtering
- tenant isolation
- default visibility constraints
Also use IgnoreQueryFilters() only when explicitly needed, such as admin screens, audit views, or restore workflows.
Description
1) Soft-delete filter (.NET)
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public bool IsDeleted { get; set; }
}
public class AppDbContext : DbContext
{
public DbSet<Product> Products => Set<Product>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted);
}
}
Now every query on Products automatically excludes deleted rows unless overridden.
2) Multi-tenant filter (.NET)
Inject tenant context into your DbContext and use it in the filter.
public interface ITenantProvider
{
Guid TenantId { get; }
}
public class Order
{
public int Id { get; set; }
public Guid TenantId { get; set; }
public decimal Total { get; set; }
}
public class AppDbContext : DbContext
{
private readonly ITenantProvider _tenantProvider;
public AppDbContext(
DbContextOptions<AppDbContext> options,
ITenantProvider tenantProvider) : base(options)
{
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _tenantProvider.TenantId);
}
}
This helps enforce tenant boundaries by default and reduces accidental cross-tenant reads.
3) Combining filters
If you need both soft-delete and tenant rules, combine them in one expression:
modelBuilder.Entity<Order>()
.HasQueryFilter(o => !o.IsDeleted && o.TenantId == _tenantProvider.TenantId);
4) When to bypass filters
Use IgnoreQueryFilters() intentionally and sparingly:
var allRowsIncludingDeleted = await db.Products
.IgnoreQueryFilters()
.ToListAsync();
Good use cases:
- admin portals
- forensic/debug tooling
- restore deleted data
5) Practical cautions
- Query filters affect every query for that entity, including includes and projections.
- Test admin/reporting paths that require unfiltered access.
- Keep filter predicates simple and index-backed for predictable performance.
Summary
EF Core query filters centralize critical data-access rules and make your application safer by default.
You get:
- less duplicated query logic
- fewer missed
Whereconditions - stronger tenant and soft-delete consistency
Use HasQueryFilter for defaults and IgnoreQueryFilters() only for explicit exceptional scenarios.
References
- Microsoft Docs: Global Query Filters
- Microsoft Docs: IgnoreQueryFilters
- Microsoft Docs: DbContext Lifetime and Configuration
- Microsoft Docs: Performance - Efficient Querying