Global Query Filters in EF Core – The Complete Guide (with .NET 10 Updates)

When building applications with Entity Framework Core (EF Core), it’s common to find yourself writing the same filtering conditions in multiple queries. Maybe you’re hiding soft-deleted records, limiting results to a specific tenant, or showing only data that a certain role is allowed to see. For beginners, this repetition can feel tedious. For experienced developers, it’s a potential source of bugs—forgetting to apply the filter just once could lead to incorrect results or even a data leak. Global query filters solve this problem by letting you define these rules once, at the model level, and having EF Core apply them automatically to every query.

What Are Global Query Filters?

A global query filter is a Boolean condition that EF Core automatically includes in the generated SQL for an entity. Once defined, the filter applies to all LINQ queries for that entity, no need to add it manually every time.

Think of it like a permanent WHERE clause that’s baked into your entity configuration.

Use Cases for Global Query Filters

Global query filters are most useful when there’s a rule that should always apply to certain entities. For beginners, think of them as “safety nets” that make sure critical rules never get skipped accidentally.

1. Soft Deletion – Keep Data for Audit or Recovery

In many systems, deleted data can’t be removed permanently, it must be kept for audits, recovery, or compliance. For example, an e-commerce system might retain deleted customer accounts for fraud investigations.

If you simply mark records as deleted (IsDeleted = true), you must remember to exclude them in every query. Forgetting even once could show deleted data to the wrong user. A global query filter hides them automatically everywhere.

C#
modelBuilder.Entity<Blog>()
    .HasQueryFilter(b => !b.IsDeleted);

2. Multi-Tenancy – Separate Data for Different Customers

In a SaaS app, multiple companies share the same database. Each tenant should only see their own data.

Without a filter, you’d have to remember Where(x => x.TenantId == currentTenantId) everywhere. One missed filter could cause a major data breach. Global filters make tenant isolation automatic.

C#
modelBuilder.Entity<Blog>()
    .HasQueryFilter(b => b.TenantId == _currentTenantId);

3. Archiving Old Data – Hide It by Default

Some applications keep older records for reporting but don’t want them in daily queries.

Old data can slow down queries and clutter results. A global query filter keeps archived data out of normal queries while still allowing it in historical reports.

C#
modelBuilder.Entity<Order>()
    .HasQueryFilter(o => !o.IsArchived);

Basic Example – Soft Deletion

Let’s say we have a Blog entity:

C#
public sealed class Blog
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public bool IsDeleted { get; set; }    
}

We can add a global filter in OnModelCreating to automatically exclude blogs that are marked as deleted:

C#
public sealed class AppDbContext(DbContextOptions<AppDbContext> options)
    : DbContext(options)
{
    public DbSet<Blog> Blogs { get; set; } = null!;

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>(e =>
        {
            e.Property(p => p.Name).IsRequired();
        });

        // Global query filter to exclude soft-deleted blogs
        modelBuilder.Entity<Blog>()
            .HasQueryFilter(b => !b.IsDeleted);

        // Example: If multi-tenancy is needed, combine into the same filter
        // modelBuilder.Entity<Blog>()
        //     .HasQueryFilter(b => !b.IsDeleted && b.TenantId == _tenantId);
    }
}

Once the global query filter is in place, any endpoint querying Blogs without overriding it will automatically return only non-deleted blogs:

C#
app.MapGet("/api/blogs", async (ApplicationDbContext db, CancellationToken ct) =>
{
    var blogs = await db.Blogs
        .AsNoTracking()
        .ToListAsync(ct);

    return Results.Ok(blogs);
});

This endpoint will only return blogs where IsDeleted is false, because the global filter is applied automatically.

Ignoring Global Query Filters

In some cases, like an admin dashboard-you might want to see all blogs, including those that are soft-deleted.

For EF Core versions before .NET 10, you can use IgnoreQueryFilters() to bypass all global filters:

C#
app.MapGet("/api/blogs/all", async (ApplicationDbContext db, CancellationToken ct) =>
{
    var allBlogs = await db.Blogs
        .IgnoreQueryFilters() // Disables all global filters
        .AsNoTracking()
        .ToListAsync(ct);

    return Results.Ok(allBlogs);
});

Note: In EF Core < 10, IgnoreQueryFilters() removes every filter applied to the entity—so if you have multiple rules combined (e.g., soft delete + tenant restriction), both will be ignored.

Limitations in Global Query Filters (EF Core < 10)

Before EF Core 10, global query filters had two major limitations:

  1. Only one filter per entity – If you needed multiple rules (e.g., soft deletion + tenant isolation), you had to merge them into one condition:
C#
modelBuilder.Entity<Blog>()
    .HasQueryFilter(b => !b.IsDeleted && b.TenantId == _tenantId);

This worked but made it impossible to disable just one filter

2. All-or-nothing disabling – If you used IgnoreQueryFilters(), EF Core removed every filter on the entity. You couldn’t disable only the soft-delete rule but keep the tenant filter.

.NET 10 Named Filters — The Solution

EF Core 10 (included in .NET 10) introduces named filters, allowing:

  • Multiple filters per entity
  • Selective disabling of specific filters

Defining named filters:

C#
modelBuilder.Entity<Blog>()
    .HasQueryFilter("SoftDeletionFilter", b => !b.IsDeleted)
    .HasQueryFilter("TenantFilter", b => b.TenantId == _tenantId);

Disabling just one filter:

C#
var blogsIncludingDeleted = await context.Blogs
    .IgnoreQueryFilters(["SoftDeletionFilter" ])
    .ToListAsync();

Disabling all filters (same as before):

C#
var allBlogs = await context.Blogs
    .IgnoreQueryFilters()
    .ToListAsync();

This makes global query filters far more modular and manageable.

Considerations When Using Global Query Filters

  • Navigation properties: Related entities that fail their own filters may cause the parent entity to be excluded (due to inner joins). Use optional relationships for left joins if needed.
  • Performance: Filters are applied to every query, so ensure filtered columns are indexed.
  • Configuration classes: If you use IEntityTypeConfiguration, you can’t directly access DbContext properties in the filter unless you inject the values.

Summary

Global query filters in EF Core let you apply cross-cutting rules – like soft deletion, tenant isolation, and role-based access—once and have them enforced automatically.

In EF Core < 10, you could only have one filter per entity and disabling filters was all-or-nothing. EF Core 10’s named filters fix these issues, making filters more flexible and maintainable.

Takeaways

Global query filters are an excellent way to eliminate repetitive conditions in your queries and ensure important rules are never missed. They’re especially valuable for scenarios like soft deletion, multi-tenancy, archiving, and role-based access, where consistent filtering is critical.

Before EF Core 10, filters were less flexible because you could only define one filter per entity, and disabling filters was an all-or-nothing action. With the introduction of named filters in EF Core 10, it’s now possible to define multiple rules for the same entity and selectively disable them when needed.

Finally, always consider the performance impact of filters and how they interact with joins, ensuring that filtered columns are properly indexed and queries remain efficient.

Found this article useful? Share it with your network and spark a conversation.