mirror of https://github.com/abpframework/abp.git
1 changed files with 130 additions and 0 deletions
@ -0,0 +1,130 @@ |
|||
# Use User-Defined Function Mapping for Global Filter |
|||
|
|||
## Introduction |
|||
|
|||
ABP provides data filters that can filter queries automatically based on some rules. This feature is useful for implementing multi-tenancy, soft delete, and other global filters. It uses [EF Core's Global Query Filters system](https://learn.microsoft.com/en-us/ef/core/querying/filters) for the EF Core Integration. |
|||
|
|||
EF Core Global Query Filters generate filter conditions and apply them to query SQL. ABP controls whether this filter condition takes effect through a variable. However, this variable may cause performance losses in some scenarios. |
|||
|
|||
## The Filter Condition Variable |
|||
|
|||
Think of a scenario with a global filter `IIsActive` that filters out inactive entities. |
|||
|
|||
```csharp |
|||
public class Book : IIsActive |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
public bool IsActive { get; set; } |
|||
} |
|||
``` |
|||
|
|||
The SQL generated by the [EF Core Global Query Filters](https://learn.microsoft.com/en-us/ef/core/querying/filters) is as follows: |
|||
|
|||
The `__ef_filter__p_0` variable controls whether the filter condition takes effect. |
|||
|
|||
```SQL |
|||
SELECT * FROM [AppBooks] AS [a] |
|||
WHERE (@__ef_filter__p_0 = CAST(1 AS bit) OR [a].[IsActive] = CAST(1 AS bit)) |
|||
``` |
|||
|
|||
The generated SQL is not optimal, and some databases do not optimize it well. |
|||
|
|||
## Using User-defined function mapping for global filters |
|||
|
|||
In the [latest ABP](https://github.com/abpframework/abp/pull/20065), we use the [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) to implement global filters more efficiently. This feature is enabled by default. |
|||
|
|||
To use this new feature for your custom global filters, you need to change your `DbContext` like below: |
|||
|
|||
````csharp |
|||
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false; |
|||
|
|||
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType) |
|||
{ |
|||
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) |
|||
{ |
|||
return true; |
|||
} |
|||
|
|||
return base.ShouldFilterEntity<TEntity>(entityType); |
|||
} |
|||
|
|||
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder) |
|||
{ |
|||
var expression = base.CreateFilterExpression<TEntity>(modelBuilder); |
|||
|
|||
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) |
|||
{ |
|||
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive"); |
|||
|
|||
if (UseDbFunction()) |
|||
{ |
|||
isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true); |
|||
|
|||
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>(); |
|||
modelBuilder.HasDbFunction(typeof(MyProjectNameDbContext).GetMethod(nameof(IsActiveFilter))!) |
|||
.HasTranslation(args => |
|||
{ |
|||
// (bool isActive, bool boolParam) |
|||
var isActive = args[0]; |
|||
var boolParam = args[1]; |
|||
|
|||
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IIsActive>() == true) |
|||
{ |
|||
// isActive == true |
|||
return new SqlBinaryExpression( |
|||
ExpressionType.Equal, |
|||
isActive, |
|||
new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping), |
|||
boolParam.Type, |
|||
boolParam.TypeMapping); |
|||
} |
|||
|
|||
// empty where sql |
|||
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping); |
|||
}); |
|||
} |
|||
|
|||
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); |
|||
} |
|||
|
|||
return expression; |
|||
} |
|||
|
|||
public static bool IsActiveFilter(bool isActive, bool boolParam) |
|||
{ |
|||
throw new NotSupportedException(AbpEfCoreDataFilterDbFunctionMethods.NotSupportedExceptionMessage); |
|||
} |
|||
|
|||
public override string GetCompiledQueryCacheKey() |
|||
{ |
|||
return $"{base.GetCompiledQueryCacheKey()}:{IsActiveFilterEnabled}"; |
|||
} |
|||
```` |
|||
|
|||
After these changes, the SQL generated by the EF Core Global Query Filters is as follows: |
|||
|
|||
Enable `IIsActive` filter: |
|||
|
|||
```SQL |
|||
SELECT * FROM [AppBooks] AS [a] WHERE |
|||
[a].[IsActive] = CAST(1 AS bit) |
|||
``` |
|||
|
|||
Disable `IIsActive` filter: |
|||
|
|||
```SQL |
|||
SELECT * FROM [AppBooks] AS [a] |
|||
``` |
|||
|
|||
## Conclusion |
|||
|
|||
We have implemented global filters using User-defined function mapping, which can generate more efficient SQL and thus improve performance. |
|||
|
|||
Upgrade to the latest ABP version and enjoy the performance improvement! |
|||
|
|||
## References |
|||
|
|||
- [ABP Framework Data Filtering](https://docs.abp.io/en/abp/latest/Data-Filtering) |
|||
- [EF Core's Global Query Filters system](https://learn.microsoft.com/en-us/ef/core/querying/filters) |
|||
- [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) |
|||
Loading…
Reference in new issue