Named Query Filters in EF 10 (multiple query filters per entity)

Named Query Filters in EF 10 (multiple query filters per entity)

4 min read ·

The AI Agent for professional .NET developers. You ship to production; vibes won't cut it. Augment Code's powerful AI coding agent meets professional .NET developers exactly where they are, delivering deep context into even the gnarliest codebases and learning how you work. Ditch the vibes and try Augment Code today.

Phoenix.new: The fastest way to build Elixir apps in-browser Built by the folks at Fly.io, Phoenix.new spins up real Phoenix apps right in the browser—no setup, no yak-shaving. The agent has root access, runs real tests, interacts with the UI in a headless browser, and pushes to GitHub. Get live previews and a dev loop that just works. Get started for free.

Entity Framework Core's global query filters have long been a convenient way to apply common conditions to all queries on an entity. They're especially handy in scenarios like soft deletion and multi-tenancy, where you want the same WHERE clause added automatically to every query.

Previous versions of EF Core, however, suffered from one big limitation: each entity type could only have one filter defined. If you needed to combine multiple conditions (for example, soft-delete and tenant isolation) you either had to write explicit && expressions or manually disable and reapply filters in specific queries.

With EF 10, that changes. The new named query filters feature lets you attach multiple filters to a single entity and reference them by name. You can then disable individual filters as needed, rather than turning off all filters at once.

Let's explore this new capability, why it matters, and some practical ways to use it.

What Are Query Filters?

If you've used EF Core for a while, you may already be familiar with global query filters. A query filter is a condition that EF automatically applies to all queries for a particular entity type. Under the hood, EF adds a WHERE clause whenever that entity is queried. Typical uses include:

  • Soft deletion: filtering out rows where IsDeleted is true so that deleted records don't show up in queries by default
  • Multi-tenancy: filtering by a TenantId so that each tenant only sees its own data

For example, a soft-delete filter might be configured like this:

modelBuilder.Entity<Order>()
    .HasQueryFilter(order => !order.IsDeleted);

With the filter in place, every query on Orders automatically excludes soft-deleted records. To include deleted data (say, for an admin report), you can call IgnoreQueryFilters() on the query. The downside is that all filters on that entity are disabled, which opens the door to accidentally leaking data you don't intend to show.

Using Multiple Query Filters

Until now, EF permitted only one query filter per entity. If you called HasQueryFilter twice on the same entity, the second call overwrote the first. To combine filters you had to write a single expression with &&:

modelBuilder.Entity<Order>()
    .HasQueryFilter(order => !order.IsDeleted && order.TenantId == tenantId);

This works but makes it impossible to selectively disable one condition. IgnoreQueryFilters() disables both, forcing you to manually re-apply whichever filter you still need. EF 10 introduces a better alternative: named query filters.

To attach multiple filters to an entity, call HasQueryFilter with a name for each filter:

modelBuilder.Entity<Order>()
    .HasQueryFilter("SoftDeletionFilter", order => !order.IsDeleted)
    .HasQueryFilter("TenantFilter", order => order.TenantId == tenantId);

Under the hood, EF creates separate filters identified by the names you provide. You can now turn off just the soft-delete filter while keeping the tenant filter in place:

// Returns all orders (including soft‑deleted) for the current tenant
var allOrders = await context.Orders.IgnoreQueryFilters(["SoftDeletionFilter"]).ToListAsync();

If you omit the parameter array, IgnoreQueryFilters() disables all filters for the entity.

Tip: Using Constants for Filter Names

Named filters use string keys. Hard-coding those names throughout your codebase makes it easy to introduce typos and brittle magic strings. To avoid this, define constants or enums for your filter names and reuse them wherever needed. For example:

public static class OrderFilters
{
    public const string SoftDelete = nameof(SoftDelete);
    public const string Tenant = nameof(Tenant);
}

modelBuilder.Entity<Order>()
    .HasQueryFilter(OrderFilters.SoftDelete, order => !order.IsDeleted)
    .HasQueryFilter(OrderFilters.Tenant, order => order.TenantId == tenantId);

// Later in your query
var allOrders = await context.Orders.IgnoreQueryFilters([OrderFilters.SoftDelete]).ToListAsync();

Having the filter names defined in a single place reduces duplication and improves maintainability. Another best practice is to wrap the ignore call in an extension method or repository so that consumers don't directly interact with filter names at all. For example:

public static IQueryable<Order> IncludeSoftDeleted(this IQueryable<Order> query)
    => query.IgnoreQueryFilters([OrderFilters.SoftDelete]);

This makes your intent explicit and centralizes the filter logic in one place.

Wrapping Up

The introduction of named query filters in EF 10 removes one of the longstanding limitations of EF's global query filters feature. You can now:

  • Attach multiple filters to a single entity and manage them individually
  • Selectively disable specific filters in a LINQ query using IgnoreQueryFilters(["FilterName"])
  • Simplify common patterns like soft deletion plus multi-tenancy without resorting to complicated conditional logic

Named query filters can become a powerful tool to keep your queries clean and your domain logic encapsulated.

Whether you're building SaaS applications that isolate tenant data or ensuring that deleted records stay hidden until you explicitly need them, EF 10's named query filters offer the flexibility you've been waiting for.

Give them a try in the preview and start thinking about how they can simplify your codebase.

That's all for today.

See you next week.


Whenever you're ready, there are 4 ways I can help you:

  1. Pragmatic Clean Architecture: Join 4,000+ students in this comprehensive course that will teach you the system I use to ship production-ready applications using Clean Architecture. Learn how to apply the best practices of modern software architecture.
  2. Modular Monolith Architecture: Join 2,000+ engineers in this in-depth course that will transform the way you build modern systems. You will learn the best practices for applying the Modular Monolith architecture in a real-world scenario.
  3. (NEW) Pragmatic REST APIs: Join 1,100+ students in this course that will teach you how to build production-ready REST APIs using the latest ASP.NET Core features and best practices. It includes a fully functional UI application that we'll integrate with the REST API.
  4. Patreon Community: Join a community of 1,000+ engineers and software architects. You will also unlock access to the source code I use in my YouTube videos, early access to future videos, and exclusive discounts for my courses.

Become a Better .NET Software Engineer

Join 68,000+ engineers who are improving their skills every Saturday morning.