DEV Community

Cover image for EF Core Global Query Filters: A Complete Guide
Spyros Ponaris
Spyros Ponaris

Posted on • Edited on

4

EF Core Global Query Filters: A Complete Guide

A Global Query Filter in Entity Framework Core is a LINQ expression that is automatically applied to all queries involving a specific entity type. These filters are defined at the model level (typically in OnModelCreating) and cannot be bypassed accidentally, ensuring consistent filtering behavior across your application.

Once applied, the filter affects all operations that involve querying the entity, including DbSet.ToListAsync(), navigation property loading, projections, and even joins — unless explicitly overridden.

Common Use Cases for Global Query Filters

Soft Deletes
Instead of permanently deleting data, you mark it as deleted with a flag (e.g., IsDeleted = true). A global query filter ensures that only non-deleted data is returned.

Multi-Tenancy
In a multi-tenant application, data is separated by tenant. A global query filter ensures that each tenant only accesses their own data, usually by filtering on a TenantId.

User-Based Data Access
Similar to multi-tenancy, you might want to restrict data access per user (e.g., filtering by UserId), especially in multi-user applications.

Archiving/Versioning
You might want to exclude archived or outdated versions of data by default, showing only active versions.

Use Cases

Soft Deletes – Automatically filter out entities where IsDeleted =
true.

Multi-Tenancy – Ensure that a tenant only accesses their own data.

User-Based Filtering – Restrict users to see only their own records.

How to Define a Global Query Filter

Global filters are defined in the OnModelCreating method inside the DbContext class using the HasQueryFilter method.

Example : Soft Delete Implementation ..

public class BaseEntity
{
    public bool IsDeleted { get; set; }
}

public class Product : BaseEntity
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>()
            .HasQueryFilter(p => !p.IsDeleted);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, every query on Products automatically filters out records where IsDeleted = true.

Querying Products

var products = context.Products.ToList(); // Implicitly applies "WHERE IsDeleted = 0"
Enter fullscreen mode Exit fullscreen mode

Bypassing the Global Filter

You can explicitly disable the filter using .IgnoreQueryFilters().

var allProducts = context.Products.IgnoreQueryFilters().ToList();
Enter fullscreen mode Exit fullscreen mode

Applying Filters Dynamically (Multi-Tenancy Example)

public class AppDbContext : DbContext
{
    private readonly int _tenantId;

    public AppDbContext(DbContextOptions<AppDbContext> options, ITenantProvider tenantProvider)
        : base(options)
    {
        _tenantId = tenantProvider.GetTenantId();
    }

    public DbSet<Order> Orders { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Order>()
            .HasQueryFilter(o => o.TenantId == _tenantId);
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, EF Core automatically filters records based on _tenantId.

Limitations and Considerations

Filters do not apply to raw SQL queries (FromSqlRaw).

Navigation properties respect global filters (e.g., if Order has OrderDetails, the filter applies to them too).

Ensure filters do not cause unexpected performance issues on large datasets.

You can define multiple query filters per entity.

*Combining Multiple Filters
*

modelBuilder.Entity<Product>()
    .HasQueryFilter(p => !p.IsDeleted)
    .HasQueryFilter(p => p.Status == "Active");
Enter fullscreen mode Exit fullscreen mode

This results in:

SELECT * FROM Products WHERE IsDeleted = 0 AND Status = 'Active'
Enter fullscreen mode Exit fullscreen mode

Disabling All Global Filters

You can disable all filters for a specific context query using:

context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
Enter fullscreen mode Exit fullscreen mode

or

var allOrders = context.Orders.IgnoreQueryFilters().ToList();
Enter fullscreen mode Exit fullscreen mode

Limitations and Considerations

Performance: Filters are applied in every query, so ensure they're efficient.

Testing: Filters can affect test queries; consider using IgnoreQueryFilters() in unit tests when needed.

Eager Loading: Filters also apply when loading related entities using Include().

Multiple Filters: You can define filters on multiple entities, but complex filtering logic might require careful design to avoid unintended side effects.

Conclusion

EF Core’s Global Query Filters help enforce consistent rules across queries without adding redundant Where clauses.

They are a powerful feature for soft deletes, multi-tenancy, and user-based access control. However, they should be used carefully, considering potential performance impacts.

Source Code

You can find the complete source code for this tutorial at:
GitHub Repository

Here are some useful references to learn more about EF Core Global Query Filters and Soft Delete:

Official EF Core Documentation
Global Query Filters
https://learn.microsoft.com/en-us/ef/core/querying/filters

Entity Framework Core SQLite Provider
https://learn.microsoft.com/en-us/ef/core/providers/sqlite/

Managing Data with EF Core
https://learn.microsoft.com/en-us/ef/core/

Community Articles & Tutorials
EF Core: Global Query Filters for Soft Deletes
https://www.thinktecture.com/en/entity-framework-core/global-query-filters/

Implementing Soft Delete in EF Core
https://medium.com/swlh/soft-delete-in-entity-framework-core-6c6b7a9784c7

Using SQLite with EF Core in Console Applications
https://code-maze.com/using-sqlite-with-entity-framework-core/

Hostinger image

Get n8n VPS hosting 3x cheaper than a cloud solution

Get fast, easy, secure n8n VPS hosting from $4.99/mo at Hostinger. Automate any workflow using a pre-installed n8n application and no-code customization.

Start now

Top comments (2)

Collapse
 
canro91 profile image
Cesar Aguirre

I didn't know we could bypass those filters. Thanks for sharing!

Collapse
 
stevsharp profile image
Spyros Ponaris

Thanks for the comment! Glad it helped!

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay