Global Query Filters

Global query filters are LINQ query predicates (a boolean expression typically passed to the LINQ Where query operator) applied to Entity Types in the metadata model (usually in OnModelCreating). Such filters are automatically applied to any LINQ queries involving those Entity Types, including Entity Types referenced indirectly, such as through the use of Include or direct navigation property references. Some common applications of this feature are:

  • Soft delete - An Entity Type defines an IsDeleted property.
  • Multi-tenancy - An Entity Type defines a TenantId property.

Example

The following example shows how to use Global Query Filters to implement soft-delete and multi-tenancy query behaviors in a simple blogging model.

Tip

You can view this article's sample on GitHub.

First, define the entities:

public class Blog
{
    private string _tenantId;

    public int BlogId { get; set; }
    public string Name { get; set; }
    public string Url { get; set; }

    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public bool IsDeleted { get; set; }

    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Note the declaration of a _tenantId field on the Blog entity. This will be used to associate each Blog instance with a specific tenant. Also defined is an IsDeleted property on the Post entity type. This is used to keep track of whether a Post instance has been "soft-deleted". That is, the instance is marked as deleted without physically removing the underlying data.

Next, configure the query filters in OnModelCreating using the HasQueryFilter API.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>().Property<string>("TenantId").HasField("_tenantId");

    // Configure entity filters
    modelBuilder.Entity<Blog>().HasQueryFilter(b => EF.Property<string>(b, "TenantId") == _tenantId);
    modelBuilder.Entity<Post>().HasQueryFilter(p => !p.IsDeleted);
}

The predicate expressions passed to the HasQueryFilter calls will now automatically be applied to any LINQ queries for those types.

Tip

Note the use of a DbContext instance level field: _tenantId used to set the current tenant. Model-level filters will use the value from the correct context instance (that is, the instance that is executing the query).

Disabling Filters

Filters may be disabled for individual LINQ queries by using the IgnoreQueryFilters() operator.

blogs = db.Blogs
    .Include(b => b.Posts)
    .IgnoreQueryFilters()
    .ToList();

Limitations

Global query filters have the following limitations:

  • Filters cannot contain references to navigation properties.
  • Filters can only be defined for the root Entity Type of an inheritance hierarchy.