Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use DbFunction for global query filters. #20065

Merged
merged 3 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions docs/en/Data-Filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,17 +217,14 @@ protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityTyp
return base.ShouldFilterEntity<TEntity>(entityType);
}

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>();
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);

if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter =
e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null
? isActiveFilter
: QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}

return expression;
Expand All @@ -251,6 +248,78 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
}
````

#### Using User-defined function mapping for global filters

Using [User-defined function mapping](https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) for global filters will gain performance improvements.

To use this feature, 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}";
}
````

### MongoDB

ABP abstracts the `IMongoDbRepositoryFilterer` interface to implement data filtering for the [MongoDB Integration](MongoDB.md), it works only if you use the repositories properly. Otherwise, you should manually filter the data.
Expand Down
83 changes: 76 additions & 7 deletions docs/zh-Hans/Data-Filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,17 +166,14 @@ protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityTyp
return base.ShouldFilterEntity<TEntity>(entityType);
}

protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>();
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);

if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter =
e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null
? isActiveFilter
: QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}

return expression;
Expand All @@ -186,6 +183,78 @@ protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntit
* 添加 `IsActiveFilterEnabled` 属性用于检查是否启用了 `IIsActive` . 内部使用了之前介绍到的 `IDataFilter` 服务.
* 重写 `ShouldFilterEntity` 和 `CreateFilterExpression` 方法检查给定实体是否实现 `IIsActive` 接口,在必要时组合表达式.

#### 使用用户定义的函数映射

使用[用户定义的函数映射](https://learn.microsoft.com/zh-cnn/ef/core/querying/user-defined-function-mapping) 可以获取性能提升.

要使用此功能,请更改DbContext如下所示:

````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}";
}
````

### MongoDB

ABP抽象了 `IMongoDbRepositoryFilterer` 接口为[MongoDB 集成](MongoDB.md)实现数据过滤, 只有正确的使用仓储,它才会工作. 否则你需要手动过滤数据.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;

namespace Volo.Abp.EntityFrameworkCore.MySQL;
Expand All @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});

Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;

Expand All @@ -18,5 +19,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});

Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;

Expand All @@ -16,5 +17,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});

Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;

namespace Volo.Abp.EntityFrameworkCore.PostgreSql;
Expand All @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});

Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Volo.Abp.Guids;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Guids;
using Volo.Abp.Modularity;

namespace Volo.Abp.EntityFrameworkCore.SqlServer;
Expand All @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context)
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
}
});

Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.Modularity;

namespace Volo.Abp.EntityFrameworkCore.Sqlite;
Expand All @@ -7,5 +8,11 @@ namespace Volo.Abp.EntityFrameworkCore.Sqlite;
)]
public class AbpEntityFrameworkCoreSqliteModule : AbpModule
{

public override void ConfigureServices(ServiceConfigurationContext context)
{
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Volo.Abp.EntityFrameworkCore;

namespace Microsoft.Extensions.DependencyInjection;

public static class AbpEfCoreDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder AddAbpDbContextOptionsExtension(this DbContextOptionsBuilder optionsBuilder)
{
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension());
return optionsBuilder;
}

public static DbContextOptionsBuilder<TContext> AddAbpDbContextOptionsExtension<TContext>(this DbContextOptionsBuilder<TContext> optionsBuilder)
where TContext : DbContext
{
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension());
return optionsBuilder;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
using System.Linq.Expressions;
using System.Reflection;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Volo.Abp;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.GlobalFilters;
using Volo.Abp.MultiTenancy;

namespace Microsoft.Extensions.DependencyInjection;

public static class AbpEfCoreModelBuilderExtensions
{
public static ModelBuilder ConfigureSoftDeleteDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext)
{
modelBuilder.HasDbFunction(methodInfo)
.HasTranslation(args =>
{
// (bool isDeleted, bool boolParam)
var isDeleted = args[0];
var boolParam = args[1];

if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<ISoftDelete>() == true)
{
// IsDeleted == false
return new SqlBinaryExpression(
ExpressionType.Equal,
isDeleted,
new SqlConstantExpression(Expression.Constant(false), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}

// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});

return modelBuilder;
}

public static ModelBuilder ConfigureMultiTenantDbFunction(this ModelBuilder modelBuilder, MethodInfo methodInfo, AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext)
{
modelBuilder.HasDbFunction(methodInfo)
.HasTranslation(args =>
{
// (Guid? tenantId, int? currentTenantId)
var tenantId = args[0];
var currentTenantId = args[1];
var boolParam = args[2];

if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IMultiTenant>() == true)
{
// TenantId == CurrentTenantId
return new SqlBinaryExpression(
ExpressionType.Equal,
tenantId,
currentTenantId,
boolParam.Type,
boolParam.TypeMapping);
}

// empty where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});

return modelBuilder;
}
}
Loading
Loading