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