diff --git a/docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md b/docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md new file mode 100644 index 0000000000..b15502723a --- /dev/null +++ b/docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md @@ -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() ?? false; + +protected override bool ShouldFilterEntity(IMutableEntityType entityType) +{ + if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) + { + return true; + } + + return base.ShouldFilterEntity(entityType); +} + +protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder) +{ + var expression = base.CreateFilterExpression(modelBuilder); + + if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) + { + Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); + + if (UseDbFunction()) + { + isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true); + + var abpEfCoreCurrentDbContext = this.GetService(); + 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() == 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)