Skip to main content

On This Page

EF Core 10 Renames Global Query Filters to Named Query Filters: What You Need to Know

3 min read
Share

These articles are AI-generated summaries. Please check the original sources for full details.

Named Global Query Filters Were Updated in EF Core 10

EF Core 10 renames global query filters to named query filters. This update solves a critical limitation: support for multiple query filters on a single entity.

Why This Matters

Previously, EF Core allowed only one global query filter per entity, forcing developers to either combine conditions (e.g., IsDeleted && TenantId == currentTenant) or create workarounds. This was especially problematic for applications needing both soft-delete and multi-tenant filters simultaneously. Named Query Filters in EF Core 10 remove this constraint, letting you apply multiple independent filters per entity and toggle them individually, aligning the framework with real-world data access patterns.

Key Insights

  • Global query filters were renamed to Named Query Filters in EF Core 10 (2026) – source: Anton Martyniuk’s article on dev.to.
  • Soft deletion uses an IsDeleted column with a HasQueryFilter(x => !x.IsDeleted) to automatically exclude deleted records – example: Book entity in the article.
  • The IgnoreQueryFilters() method allows bypassing all filters, useful for admin or audit queries – tool: EF Core, used by .NET developers.
  • Multi-tenant applications can apply filters per tenant ID, but earlier EF Core versions limited to one filter per entity – now named filters support multiple.

Working Examples

Complete example of soft-delete query filter for Book entity, including DbContext configuration and two API endpoints – one honoring the filter, one bypassing it.

public class Author
{
    public required Guid Id { get; set; }
    public required string Name { get; set; }
    public required string Country { get; set; }
    public required List<Book> Books { get; set; } = [];
}

public class Book
{
    public required Guid Id { get; set; }
    public required string Title { get; set; }
    public required int Year { get; set; }
    public required bool IsDeleted { get; set; }
    public required Guid TenantId { get; set; }
    public required Author Author { get; set; }
}

public class ApplicationDbContext : DbContext
{
    public DbSet<Author> Authors { get; set; } = default!;
    public DbSet<Book> Books { get; set; } = default!;

    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Book>()
            .HasQueryFilter(x => !x.IsDeleted);

        base.OnModelCreating(modelBuilder);

        modelBuilder.Entity<Author>(entity =>
        {
            entity.ToTable("authors");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.Name);
            entity.Property(x => x.Id).IsRequired();
            entity.Property(x => x.Name).IsRequired();
            entity.Property(x => x.Country).IsRequired();
            entity.HasMany(x => x.Books).WithOne(x => x.Author);
        });

        modelBuilder.Entity<Book>(entity =>
        {
            entity.ToTable("books");
            entity.HasKey(x => x.Id);
            entity.HasIndex(x => x.Title);
            entity.Property(x => x.Id).IsRequired();
            entity.Property(x => x.Title).IsRequired();
            entity.Property(x => x.Year).IsRequired();
            entity.Property(x => x.IsDeleted).IsRequired();
            entity.HasOne(x => x.Author).WithMany(x => x.Books);
        });
    }
}

// Minimal API endpoint
app.MapGet("/api/books", async (ApplicationDbContext dbContext) =>
{
    var nonDeletedBooks = await dbContext.Books.ToListAsync();
    return Results.Ok(nonDeletedBooks);
});

// Bypassing filters
app.MapGet("/api/all-books", async (ApplicationDbContext dbContext) =>
{
    var allBooks = await dbContext.Books
        .IgnoreQueryFilters()
        .Where(x => x.IsDeleted)
        .ToListAsync();
    return Results.Ok(allBooks);
});

Practical Applications

  • Soft delete: Book entity automatically filters out IsDeleted = true in all queries, reducing repetitive Where clauses – pitfall: forgetting to use IgnoreQueryFilters when needed leads to missing data.
  • Multi-tenant: Apply a TenantId filter to isolate tenant data – pitfall: accidentally applying the same filter to shared entities without careful design, causing data leakage.
  • Audit log retrieval: Use IgnoreQueryFilters() selectively to include soft-deleted or cross-tenant records for reporting – pitfall: overusing IgnoreQueryFilters negates the benefits of filters and may introduce security holes.

References:

Continue reading

Next article

Scaling Your Pokémon Team: When Your AI System Outgrows Its Original Design

Related Content