Create global and scoped filters for Entity Framework queries. The filters are automatically applied to every query and can be used to support use cases such as Multi-Tenancy, Soft Deletes, Active/Inactive, etc.
Filters can be created using boolean linq expressions and also support the Contains() operator.
Access to DynamicFilters is done via extension methods in the EntityFramework.DynamicFilters namespace on the DbContext and DbModelBuilder classes.
Supports MS SQL Server (including Azure), MySQL, and Oracle (*see notes below).
The package is also available on NuGet: EntityFramework.DynamicFilters.
Filters are fined in DbContext.OnModelCreating(). All filters have global scope and will be used by all DbContexts. Each DbContext can also choose to provide a "scoped" filter value or can disable the filter via the DisableFilter() extension method. Scoped parameter changes and filter disabling will apply only to that DbContext and do not affect any existing or future DbContexts.
Filters can be defined on a specific entity class or an interface. Below is an example of a "soft delete" filter created on an ISoftDelete interface. This filter will apply to any entity that implements ISoftDelete and will automatically filter those entities by applying the condition "IsDeleted==false".
modelBuilder.Filter("IsDeleted", (ISoftDelete d) => d.IsDeleted, false);
Filter values can also be provided via a delegate/Func instead of a specific value (as shown in the above example). This can allow you to vary the parameter value dynamically. For example, a filter can be created on the UserID and be provided per http request. Below is an example that obtains a "Person ID" from the Thread.CurrentPrincipal. This delegate will be evaluated each time the query is executed so it will obtain the "Person ID" associated with each request.
modelBuilder.Filter("Notes_CurrentUser", (Note n) => n.PersonID, () => GetPersonIDFromPrincipal(Thread.CurrentPrincipal));
In this example, the Note entity is "owned" by the current user. This filter will ensure that all queries made for Note entities will always be restricted to the current user and it will not be possible for users to retrieve notes for other users.
Filters can also be created using linq conditions and with multiple parameters.
This Filter() command creates a filter that limits BlogEntry records by AccountID and an IsDeleted flag. A parameter is created for each condition with parameter names "accountID" and "isDeleted":
modelBuilder.Filter("BlogEntryFilter",
(BlogEntry b, Guid accountID, bool isDeleted) => (b.AccountID == accountID) && (b.IsDeleted == isDeleted),
() => GetPersonIDFromPrincipal(Thread.CurrentPrincipal),
() => false);
The linq syntax is somewhat limited to boolean expressions but does support the Contains() operator on IEnumerable<> to generate sql "in" clauses:
var values = new List<int> { 1, 2, 3, 4, 5 };
modelBuilder.Filter("ContainsTest", (BlogEntry b, List<int> valueList) => valueList.Contains(b.IntValue.Value), () => values);
If you require support for additional linq operators, please create an issue.
Within a single DbContext instance, filter parameter values can also be changed. These changes are scoped to only that DbContext instance and do not affect any other DbContext instances.
To change the Soft Delete filter shown above to return only deleted records, you could do this:
context.SetFilterScopedParameterValue("IsDeleted", true);
If the filter contains multiple parameters, you must specify the name of the parameter to change like this:
context.SetFilterScopedParameterValue("BlogEntryFilter", "accountID", 12345);
Global parameter values can also be changed using the SetFilterGlobalParameterValue extension method.
To disable a filter, use the DisableFilter extension method like this:
context.DisableFilter("IsDeleted");
Filters can also be globally disabled after they are created in OnModelCreating:
modelBuilder.DisableFilterGlobally("IsDeleted");
Globally disabled filters can then be selectively enabled as needed. Enabling a globally disabled filter will apply only to that DbContext just like scoped parameter values.
context.EnableFilter("IsDeleted");
You can also mass enable/disable all filters within a DbContext at once:
context.DisableAllFilters();
context.EnableAllFilters();
However, note that if a query is executed with a filter disabled, Entity Framework will cache those entities internally. If you then enable a filter, cached entities may be included in child collections that otherwise should not be. Entity Framework caches per DbContext so if you find this to be an issue, you can avoid it by using a fresh DbContext.
Oracle is supported using the Official Oracle ODP.NET, Managed Entity Framework Driver with the following limitations:
- The Oracle driver does not support generating an "in" expression. Using the "Contains" operator will result in outputting a series of equals/or expressions.
- Using a DateTime value tends to throw an exception saying "The member with identity 'Precision' does not exist in the metadata collection." This seems to be a bug in the Oracle driver. Using a DateTimeOffset instead of a DateTime works correctly (which also then uses the Oracle TIMESTAMP datatype instead of DATE).