From edecd7f14c1abd779db943e8bdf4834fcacd2707 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 1 Jul 2024 11:17:25 +0800 Subject: [PATCH 1/2] Add community article to introduction the `DBFcuntion` feature. --- .../POST.md | 130 ++++++++++++++++++ 1 file changed, 130 insertions(+) create mode 100644 docs/en/Community-Articles/2024-07-01-Use_User-Defined_Function_Mapping_For_Global_Filter/POST.md 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) From e4657437a2a6fbec8fb5d355b347910f56d6e187 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Mon, 1 Jul 2024 14:02:07 +0300 Subject: [PATCH 2/2] Update POST.md --- .../POST.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) 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 index b15502723a..37171d1219 100644 --- 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 @@ -4,11 +4,11 @@ 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. +EF Core Global Query Filters generate filter conditions and apply them to SQL queries. 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. +Think of a scenario with a global filter `IIsActive`, which filters out inactive entities: ```csharp public class Book : IIsActive @@ -21,20 +21,20 @@ public class Book : IIsActive 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 `__ef_filter__p_0` variable controls whether the filter condition takes effect. + 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. +In the [upcoming preview version of ABP, v8.3.0-rc.1](https://github.com/abpframework/abp/pull/20065), we start 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, so you don't need to make any changes if you create a new solution and start from scratch. Otherwise, you can enable it easily by following the instructions below. -To use this new feature for your custom global filters, you need to change your `DbContext` like below: +To use this new feature for your custom global filters, you need to change your `DbContext` as follows: ````csharp protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled() ?? false; @@ -102,16 +102,16 @@ public override string GetCompiledQueryCacheKey() } ```` -After these changes, the SQL generated by the EF Core Global Query Filters is as follows: +After these changes, the SQL generated by the EF Core Global Query Filters will be as follows: -Enable `IIsActive` filter: +Enabling the `IIsActive` filter: ```SQL SELECT * FROM [AppBooks] AS [a] WHERE [a].[IsActive] = CAST(1 AS bit) ``` -Disable `IIsActive` filter: +Disabling the `IIsActive` filter: ```SQL SELECT * FROM [AppBooks] AS [a] @@ -119,7 +119,7 @@ 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. +We have implemented global filters using [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/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!