From 3c8944cec5d67a5699270977453a84c3b5250b28 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Jun 2024 10:21:58 +0800 Subject: [PATCH 1/3] Use DbFunction for global query filters. Resolve #20062 --- ...EfCoreDbContextOptionsBuilderExtensions.cs | 21 ++++++ .../AbpEfCoreModelBuilderExtensions.cs | 68 +++++++++++++++++++ .../Abp/EntityFrameworkCore/AbpDbContext.cs | 31 +++++++-- .../AbpDbContextOptionsExtension.cs | 62 +++++++++++++++++ .../AbpEntityQueryProvider.cs | 49 +++++++++++++ .../DbContextOptionsFactory.cs | 6 ++ .../AbpCompiledQueryCacheKeyGenerator.cs | 59 ++++++++++++++++ .../AbpEfCoreCurrentDbContext.cs | 21 ++++++ .../AbpEfCoreDataFilterDbFunctionMethods.cs | 21 ++++++ .../AbpEfCoreGlobalFilterOptions.cs | 11 +++ .../IAbpEfCoreDbFunctionContext.cs | 16 +++++ .../Abp/Auditing/AbpAuditingTestModule.cs | 2 +- .../AbpEntityFrameworkCoreTestModule.cs | 4 +- 13 files changed, 364 insertions(+), 7 deletions(-) create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs create mode 100644 framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs new file mode 100644 index 00000000000..8c4b636d9e7 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs @@ -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 AddAbpDbContextOptionsExtension(this DbContextOptionsBuilder optionsBuilder) + where TContext : DbContext + { + ((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension()); + return optionsBuilder; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs new file mode 100644 index 00000000000..19ad9e02679 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs @@ -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() == 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() == 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; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 138bf82ec4f..c1c87cdeadc 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -11,11 +11,12 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; using Volo.Abp.Auditing; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; @@ -24,6 +25,7 @@ using Volo.Abp.Domain.Repositories; using Volo.Abp.EntityFrameworkCore.ChangeTrackers; using Volo.Abp.EntityFrameworkCore.EntityHistory; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.ValueConverters; using Volo.Abp.EventBus.Distributed; @@ -37,7 +39,7 @@ namespace Volo.Abp.EntityFrameworkCore; -public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, ITransientDependency +public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, IAbpEfCoreDbFunctionContext, ITransientDependency where TDbContext : DbContext { public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!; @@ -78,6 +80,8 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, public IOptions Options => LazyServiceProvider.LazyGetRequiredService>(); + public IOptions GlobalFilterOptions => LazyServiceProvider.LazyGetRequiredService>(); + private static readonly MethodInfo ConfigureBasePropertiesMethodInfo = typeof(AbpDbContext) .GetMethod( @@ -713,7 +717,7 @@ protected virtual void ConfigureGlobalFilters(ModelBuilder modelBuilder { if (mutableEntityType.BaseType == null && ShouldFilterEntity(mutableEntityType)) { - var filterExpression = CreateFilterExpression(); + var filterExpression = CreateFilterExpression(modelBuilder); if (filterExpression != null) { modelBuilder.Entity().HasAbpQueryFilter(filterExpression); @@ -782,22 +786,41 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType return false; } - protected virtual Expression>? CreateFilterExpression() + protected virtual Expression>? CreateFilterExpression(ModelBuilder modelBuilder) where TEntity : class { Expression>? expression = null; + var abpEfCoreCurrentDbContext = this.GetService(); if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, "IsDeleted"); + + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + { + expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true); + modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, abpEfCoreCurrentDbContext); + } } if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity))) { Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, "TenantId") == CurrentTenantId; + + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + { + multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true); + modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, abpEfCoreCurrentDbContext); + } + expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter); } return expression; } + + public virtual string GetCompiledQueryCacheKey() + { + return $"{CurrentTenantId?.ToString() ?? "Null"}{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}"; + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs new file mode 100644 index 00000000000..d9dc4cef9f0 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; + +namespace Volo.Abp.EntityFrameworkCore; + +public class AbpDbContextOptionsExtension : IDbContextOptionsExtension +{ + public void ApplyServices(IServiceCollection services) + { + var serviceDescriptor = services.FirstOrDefault(x => x.ServiceType == typeof(ICompiledQueryCacheKeyGenerator)); + if (serviceDescriptor != null && serviceDescriptor.ImplementationType != null) + { + services.Remove(serviceDescriptor); + services.AddScoped(serviceDescriptor.ImplementationType); + services.Add(ServiceDescriptor.Scoped(provider => + ActivatorUtilities.CreateInstance(provider, + provider.GetRequiredService(serviceDescriptor.ImplementationType) + .As()))); + } + + services.Replace(ServiceDescriptor.Scoped()); + services.AddSingleton(typeof(AbpEfCoreCurrentDbContext)); + } + + public void Validate(IDbContextOptions options) + { + } + + public DbContextOptionsExtensionInfo Info => new AbpOptionsExtensionInfo(this); + + private class AbpOptionsExtensionInfo : DbContextOptionsExtensionInfo + { + public AbpOptionsExtensionInfo(IDbContextOptionsExtension extension) + : base(extension) + { + } + + public override bool IsDatabaseProvider => false; + + public override int GetServiceProviderHashCode() + { + return 0; + } + + public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo other) + { + return other is AbpOptionsExtensionInfo; + } + + public override void PopulateDebugInfo(IDictionary debugInfo) + { + } + + public override string LogFragment => "AbpOptionsExtension"; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs new file mode 100644 index 00000000000..665757e17bf --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs @@ -0,0 +1,49 @@ +using System.Linq.Expressions; +using System.Threading; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query.Internal; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; + +namespace Volo.Abp.EntityFrameworkCore; + +#pragma warning disable EF1001 +public class AbpEntityQueryProvider : EntityQueryProvider +{ + protected AbpEfCoreCurrentDbContext AbpEfCoreCurrentDbContext { get; } + protected ICurrentDbContext CurrentDbContext { get; } + + public AbpEntityQueryProvider( + IQueryCompiler queryCompiler, + AbpEfCoreCurrentDbContext abpEfCoreCurrentDbContext, + ICurrentDbContext currentDbContext) + : base(queryCompiler) + { + AbpEfCoreCurrentDbContext = abpEfCoreCurrentDbContext; + CurrentDbContext = currentDbContext; + } + + public override object Execute(Expression expression) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.Execute(expression); + } + } + + public override TResult Execute(Expression expression) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.Execute(expression); + } + } + + public override TResult ExecuteAsync(Expression expression, CancellationToken cancellationToken = new CancellationToken()) + { + using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext)) + { + return base.ExecuteAsync(expression, cancellationToken); + } + } +} +#pragma warning restore EF1001 diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 1b3a6f48b56..113a4aa86c2 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.Data; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.MultiTenancy; namespace Volo.Abp.EntityFrameworkCore.DependencyInjection; @@ -27,6 +28,11 @@ public static DbContextOptions Create(IServiceProvider s PreConfigure(options, context); Configure(options, context); + if (serviceProvider.GetRequiredService>().Value.UseDbFunction) + { + context.DbContextOptions.AddAbpDbContextOptionsExtension(); + } + return context.DbContextOptions.Options; } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs new file mode 100644 index 00000000000..e43ae1e04de --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs @@ -0,0 +1,59 @@ +using System; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Query; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpCompiledQueryCacheKeyGenerator : ICompiledQueryCacheKeyGenerator +{ + protected ICompiledQueryCacheKeyGenerator InnerCompiledQueryCacheKeyGenerator { get; } + protected ICurrentDbContext CurrentContext { get; } + + public AbpCompiledQueryCacheKeyGenerator( + ICompiledQueryCacheKeyGenerator innerCompiledQueryCacheKeyGenerator, + ICurrentDbContext currentContext) + { + InnerCompiledQueryCacheKeyGenerator = innerCompiledQueryCacheKeyGenerator; + CurrentContext = currentContext; + } + + public virtual object GenerateCacheKey(Expression query, bool async) + { + var cacheKey = InnerCompiledQueryCacheKeyGenerator.GenerateCacheKey(query, async); + if (CurrentContext.Context is IAbpEfCoreDbFunctionContext abpEfCoreDbFunctionContext) + { + return new AbpCompiledQueryCacheKey(cacheKey, abpEfCoreDbFunctionContext.GetCompiledQueryCacheKey()); + } + + return cacheKey; + } + + private readonly struct AbpCompiledQueryCacheKey : IEquatable + { + private readonly object _compiledQueryCacheKey; + private readonly string _currentFilterCacheKey; + + public AbpCompiledQueryCacheKey(object compiledQueryCacheKey, string currentFilterCacheKey) + { + _compiledQueryCacheKey = compiledQueryCacheKey; + _currentFilterCacheKey = currentFilterCacheKey; + } + + public override bool Equals(object? obj) + { + return obj is AbpCompiledQueryCacheKey key && Equals(key); + } + + public bool Equals(AbpCompiledQueryCacheKey other) + { + return _compiledQueryCacheKey.Equals(other._compiledQueryCacheKey) && + _currentFilterCacheKey == other._currentFilterCacheKey; + } + + public override int GetHashCode() + { + return HashCode.Combine(_compiledQueryCacheKey, _currentFilterCacheKey); + } + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs new file mode 100644 index 00000000000..c3a29b6d702 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs @@ -0,0 +1,21 @@ +using System; +using System.Threading; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpEfCoreCurrentDbContext +{ + private readonly AsyncLocal _current = new AsyncLocal(); + + public IAbpEfCoreDbFunctionContext? Context => _current.Value; + + public IDisposable Use(IAbpEfCoreDbFunctionContext? context) + { + var previousValue = Context; + _current.Value = context; + return new DisposeAction(() => + { + _current.Value = previousValue; + }); + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs new file mode 100644 index 00000000000..e7d672e9ba1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs @@ -0,0 +1,21 @@ +using System; +using System.Reflection; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public static class AbpEfCoreDataFilterDbFunctionMethods +{ + public static bool SoftDeleteFilter(bool isDeleted, bool boolParam) + { + throw new NotSupportedException("This method should be replaced by the database function call."); + } + + public static MethodInfo SoftDeleteFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(SoftDeleteFilter))!; + + public static bool MultiTenantFilter(Guid? tenantId, Guid? currentTenantId, bool boolParam) + { + throw new NotSupportedException("This method should be replaced by the database function call."); + } + + public static MethodInfo MultiTenantFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(MultiTenantFilter))!; +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs new file mode 100644 index 00000000000..180ae849e7d --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs @@ -0,0 +1,11 @@ +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public class AbpEfCoreGlobalFilterOptions +{ + public bool UseDbFunction { get; set; } + + public AbpEfCoreGlobalFilterOptions() + { + UseDbFunction = true; + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs new file mode 100644 index 00000000000..85096e9e0a1 --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; + +public interface IAbpEfCoreDbFunctionContext +{ + IAbpLazyServiceProvider LazyServiceProvider { get; set; } + + ICurrentTenant CurrentTenant { get; } + + IDataFilter DataFilter { get; } + + string GetCompiledQueryCacheKey(); +} diff --git a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs index 4fe3ae9c165..4e14dbc262d 100644 --- a/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs +++ b/framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs @@ -70,7 +70,7 @@ private static SqliteConnection CreateDatabaseAndGetConnection() connection.Open(); using (var context = new AbpAuditingTestDbContext(new DbContextOptionsBuilder() - .UseSqlite(connection).Options)) + .UseSqlite(connection).AddAbpDbContextOptionsExtension().Options)) { context.GetService().CreateTables(); } diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index 5e81aa1a7d9..a13aea29452 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -72,7 +72,7 @@ public override void ConfigureServices(ServiceConfigurationContext context) { options.Configure(abpDbContextConfigurationContext => { - abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); + abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection).AddAbpDbContextOptionsExtension(); }); }); } @@ -101,7 +101,7 @@ private static SqliteConnection CreateDatabaseAndGetConnection() var connection = new AbpUnitTestSqliteConnection("Data Source=:memory:"); connection.Open(); - using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder().UseSqlite(connection).Options)) + using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder().UseSqlite(connection).AddAbpDbContextOptionsExtension().Options)) { context.GetService().CreateTables(); context.Database.ExecuteSqlRaw( From c76b9e6c4ae227a70ce7a7fdf02722fb5ea5fce7 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Jun 2024 12:46:15 +0800 Subject: [PATCH 2/3] Set `UseDbFunction` to `false` by default. --- .../MySQL/AbpEntityFrameworkCoreMySQLModule.cs | 8 +++++++- .../AbpEntityFrameworkCoreOracleDevartModule.cs | 6 ++++++ .../Oracle/AbpEntityFrameworkCoreOracleModule.cs | 6 ++++++ .../AbpEntityFrameworkCorePostgreSqlModule.cs | 8 +++++++- .../AbpEntityFrameworkCoreSqlServerModule.cs | 8 +++++++- .../Sqlite/AbpEntityFrameworkCoreSqliteModule.cs | 9 ++++++++- .../Volo/Abp/EntityFrameworkCore/AbpDbContext.cs | 12 +++++++----- .../AbpEfCoreDataFilterDbFunctionMethods.cs | 8 ++++++-- .../GlobalFilters/AbpEfCoreGlobalFilterOptions.cs | 5 ----- 9 files changed, 54 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs index 6431590230d..0b493f5058a 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs @@ -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; @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString; } }); + + Configure(options => + { + options.UseDbFunction = true; + }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs index 3b4129b272a..317f664b04f 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.Guids; using Volo.Abp.Modularity; @@ -18,5 +19,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; } }); + + Configure(options => + { + options.UseDbFunction = true; + }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs index 54627a90c0f..76ffe32f7a5 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs @@ -1,4 +1,5 @@ using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.Guids; using Volo.Abp.Modularity; @@ -16,5 +17,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; } }); + + Configure(options => + { + options.UseDbFunction = true; + }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs index 2b679cccef5..b82b128b3b1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs @@ -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; @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString; } }); + + Configure(options => + { + options.UseDbFunction = true; + }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs index 81868098461..acebc98d862 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs @@ -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; @@ -17,5 +18,10 @@ public override void ConfigureServices(ServiceConfigurationContext context) options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd; } }); + + Configure(options => + { + options.UseDbFunction = true; + }); } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs index e7725461548..52415d73b18 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs @@ -1,3 +1,4 @@ +using Volo.Abp.EntityFrameworkCore.GlobalFilters; using Volo.Abp.Modularity; namespace Volo.Abp.EntityFrameworkCore.Sqlite; @@ -7,5 +8,11 @@ namespace Volo.Abp.EntityFrameworkCore.Sqlite; )] public class AbpEntityFrameworkCoreSqliteModule : AbpModule { - + public override void ConfigureServices(ServiceConfigurationContext context) + { + Configure(options => + { + options.UseDbFunction = true; + }); + } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index c1c87cdeadc..6f45b75dc31 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -103,9 +103,12 @@ private static readonly MethodInfo ConfigureValueGeneratedMethodInfo BindingFlags.Instance | BindingFlags.NonPublic )!; + protected readonly DbContextOptions DbContextOptions; + protected AbpDbContext(DbContextOptions options) : base(options) { + DbContextOptions = options; } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -791,15 +794,14 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType { Expression>? expression = null; - var abpEfCoreCurrentDbContext = this.GetService(); if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity))) { expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, "IsDeleted"); - if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension() != null) { expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true); - modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, abpEfCoreCurrentDbContext); + modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, this.GetService()); } } @@ -807,10 +809,10 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType { Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, "TenantId") == CurrentTenantId; - if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction) + if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension() != null) { multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true); - modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, abpEfCoreCurrentDbContext); + modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, this.GetService()); } expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter); diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs index e7d672e9ba1..35694433eda 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs @@ -5,16 +5,20 @@ namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; public static class AbpEfCoreDataFilterDbFunctionMethods { + private const string NotSupportedExceptionMessage = "Your EF Core database provider does not support 'User-defined function mapping'." + + "Please set 'UseDbFunction' of 'AbpEfCoreGlobalFilterOptions' to false to disable it." + + "See https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping for more information." ; + public static bool SoftDeleteFilter(bool isDeleted, bool boolParam) { - throw new NotSupportedException("This method should be replaced by the database function call."); + throw new NotSupportedException(NotSupportedExceptionMessage); } public static MethodInfo SoftDeleteFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(SoftDeleteFilter))!; public static bool MultiTenantFilter(Guid? tenantId, Guid? currentTenantId, bool boolParam) { - throw new NotSupportedException("This method should be replaced by the database function call."); + throw new NotSupportedException(NotSupportedExceptionMessage); } public static MethodInfo MultiTenantFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(MultiTenantFilter))!; diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs index 180ae849e7d..e1641cd2a1d 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs @@ -3,9 +3,4 @@ namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; public class AbpEfCoreGlobalFilterOptions { public bool UseDbFunction { get; set; } - - public AbpEfCoreGlobalFilterOptions() - { - UseDbFunction = true; - } } From 109ca428bb9237c5b863c9a2361b36d2ae490783 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 18 Jun 2024 16:19:24 +0800 Subject: [PATCH 3/3] Update `Data-Filtering.md`. --- docs/en/Data-Filtering.md | 83 +++++++++++++++++-- docs/zh-Hans/Data-Filtering.md | 83 +++++++++++++++++-- .../Abp/EntityFrameworkCore/AbpDbContext.cs | 11 ++- .../AbpEfCoreDataFilterDbFunctionMethods.cs | 2 +- .../AbpEfCoreGlobalFilterOptions.cs | 4 + 5 files changed, 165 insertions(+), 18 deletions(-) diff --git a/docs/en/Data-Filtering.md b/docs/en/Data-Filtering.md index 830979e710a..4706912fe7f 100644 --- a/docs/en/Data-Filtering.md +++ b/docs/en/Data-Filtering.md @@ -217,17 +217,14 @@ protected override bool ShouldFilterEntity(IMutableEntityType entityTyp return base.ShouldFilterEntity(entityType); } -protected override Expression> CreateFilterExpression() +protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder) { - var expression = base.CreateFilterExpression(); + var expression = base.CreateFilterExpression(modelBuilder); if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) { - Expression> isActiveFilter = - e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); - expression = expression == null - ? isActiveFilter - : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); + Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); + expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); } return expression; @@ -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() ?? 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}"; +} +```` + ### 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. diff --git a/docs/zh-Hans/Data-Filtering.md b/docs/zh-Hans/Data-Filtering.md index 8a43ad93b1b..01c4590761b 100644 --- a/docs/zh-Hans/Data-Filtering.md +++ b/docs/zh-Hans/Data-Filtering.md @@ -166,17 +166,14 @@ protected override bool ShouldFilterEntity(IMutableEntityType entityTyp return base.ShouldFilterEntity(entityType); } -protected override Expression> CreateFilterExpression() +protected override Expression> CreateFilterExpression(ModelBuilder modelBuilder) { - var expression = base.CreateFilterExpression(); + var expression = base.CreateFilterExpression(modelBuilder); if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))) { - Expression> isActiveFilter = - e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); - expression = expression == null - ? isActiveFilter - : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); + Expression> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property(e, "IsActive"); + expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter); } return expression; @@ -186,6 +183,78 @@ protected override Expression> CreateFilterExpression 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}"; +} +```` + ### MongoDB ABP抽象了 `IMongoDbRepositoryFilterer` 接口为[MongoDB 集成](MongoDB.md)实现数据过滤, 只有正确的使用仓储,它才会工作. 否则你需要手动过滤数据. diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 6f45b75dc31..794058ad6ea 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -798,7 +798,7 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType { expression = e => !IsSoftDeleteFilterEnabled || !EF.Property(e, "IsDeleted"); - if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension() != null) + if (UseDbFunction()) { expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true); modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, this.GetService()); @@ -809,7 +809,7 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType { Expression> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property(e, "TenantId") == CurrentTenantId; - if (LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension() != null) + if (UseDbFunction()) { multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true); modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, this.GetService()); @@ -821,8 +821,13 @@ protected virtual bool ShouldFilterEntity(IMutableEntityType entityType return expression; } + protected virtual bool UseDbFunction() + { + return LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension() != null; + } + public virtual string GetCompiledQueryCacheKey() { - return $"{CurrentTenantId?.ToString() ?? "Null"}{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}"; + return $"{CurrentTenantId?.ToString() ?? "Null"}:{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}"; } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs index 35694433eda..10cb6be280e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs @@ -5,7 +5,7 @@ namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; public static class AbpEfCoreDataFilterDbFunctionMethods { - private const string NotSupportedExceptionMessage = "Your EF Core database provider does not support 'User-defined function mapping'." + + public const string NotSupportedExceptionMessage = "Your EF Core database provider does not support 'User-defined function mapping'." + "Please set 'UseDbFunction' of 'AbpEfCoreGlobalFilterOptions' to false to disable it." + "See https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping for more information." ; diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs index e1641cd2a1d..f97031ff137 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs @@ -2,5 +2,9 @@ namespace Volo.Abp.EntityFrameworkCore.GlobalFilters; public class AbpEfCoreGlobalFilterOptions { + /// + /// Use User-defined function mapping to filter data. + /// https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping + /// public bool UseDbFunction { get; set; } }