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);
}
}
Now, every query on Products automatically filters out records where IsDeleted = true.
Querying Products
var products = context.Products.ToList(); // Implicitly applies "WHERE IsDeleted = 0"
Bypassing the Global Filter
You can explicitly disable the filter using .IgnoreQueryFilters().
var allProducts = context.Products.IgnoreQueryFilters().ToList();
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);
}
}
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");
This results in:
SELECT * FROM Products WHERE IsDeleted = 0 AND Status = 'Active'
Disabling All Global Filters
You can disable all filters for a specific context query using:
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
or
var allOrders = context.Orders.IgnoreQueryFilters().ToList();
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/
Top comments (2)
I didn't know we could bypass those filters. Thanks for sharing!
Thanks for the comment! Glad it helped!