Browse Source

Merge pull request #20065 from abpframework/DbFunction

Use DbFunction for global query filters.
pull/20117/head
Halil İbrahim Kalkan 2 years ago
committed by GitHub
parent
commit
d1692fecc3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 83
      docs/en/Data-Filtering.md
  2. 83
      docs/zh-Hans/Data-Filtering.md
  3. 8
      framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo/Abp/EntityFrameworkCore/MySQL/AbpEntityFrameworkCoreMySQLModule.cs
  4. 6
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle.Devart/Volo/Abp/EntityFrameworkCore/Oracle/Devart/AbpEntityFrameworkCoreOracleDevartModule.cs
  5. 6
      framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo/Abp/EntityFrameworkCore/Oracle/AbpEntityFrameworkCoreOracleModule.cs
  6. 8
      framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/PostgreSql/AbpEntityFrameworkCorePostgreSqlModule.cs
  7. 8
      framework/src/Volo.Abp.EntityFrameworkCore.SqlServer/Volo/Abp/EntityFrameworkCore/SqlServer/AbpEntityFrameworkCoreSqlServerModule.cs
  8. 9
      framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs
  9. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs
  10. 68
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs
  11. 38
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  12. 62
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs
  13. 49
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs
  14. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
  15. 59
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs
  16. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs
  17. 25
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs
  18. 10
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs
  19. 16
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs
  20. 2
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  21. 4
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

83
docs/en/Data-Filtering.md

@ -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;
@ -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.

83
docs/zh-Hans/Data-Filtering.md

@ -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;
@ -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)实现数据过滤, 只有正确的使用仓储,它才会工作. 否则你需要手动过滤数据.

8
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 class AbpEntityFrameworkCoreMySQLModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

6
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 class AbpEntityFrameworkCoreOracleDevartModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

6
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 class AbpEntityFrameworkCoreOracleModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

8
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 class AbpEntityFrameworkCorePostgreSqlModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsString;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

8
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 class AbpEntityFrameworkCoreSqlServerModule : AbpModule
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAtEnd;
}
});
Configure<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

9
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<AbpEfCoreGlobalFilterOptions>(options =>
{
options.UseDbFunction = true;
});
}
}

21
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<TContext> AddAbpDbContextOptionsExtension<TContext>(this DbContextOptionsBuilder<TContext> optionsBuilder)
where TContext : DbContext
{
((IDbContextOptionsBuilderInfrastructure) optionsBuilder).AddOrUpdateExtension(new AbpDbContextOptionsExtension());
return optionsBuilder;
}
}

68
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<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;
}
}

38
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -11,11 +11,12 @@ using System.Threading;
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.Entities.Events;
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 @@ using Volo.Abp.Uow;
namespace Volo.Abp.EntityFrameworkCore;
public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext, ITransientDependency
public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext, IAbpEfCoreDbFunctionContext, ITransientDependency
where TDbContext : DbContext
{
public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!;
@ -78,6 +80,8 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
public IOptions<AbpDbContextOptions> Options => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpDbContextOptions>>();
public IOptions<AbpEfCoreGlobalFilterOptions> GlobalFilterOptions => LazyServiceProvider.LazyGetRequiredService<IOptions<AbpEfCoreGlobalFilterOptions>>();
private static readonly MethodInfo ConfigureBasePropertiesMethodInfo
= typeof(AbpDbContext<TDbContext>)
.GetMethod(
@ -99,9 +103,12 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
BindingFlags.Instance | BindingFlags.NonPublic
)!;
protected readonly DbContextOptions DbContextOptions;
protected AbpDbContext(DbContextOptions<TDbContext> options)
: base(options)
{
DbContextOptions = options;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
@ -713,7 +720,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
{
if (mutableEntityType.BaseType == null && ShouldFilterEntity<TEntity>(mutableEntityType))
{
var filterExpression = CreateFilterExpression<TEntity>();
var filterExpression = CreateFilterExpression<TEntity>(modelBuilder);
if (filterExpression != null)
{
modelBuilder.Entity<TEntity>().HasAbpQueryFilter(filterExpression);
@ -782,7 +789,7 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
return false;
}
protected virtual Expression<Func<TEntity, bool>>? CreateFilterExpression<TEntity>()
protected virtual Expression<Func<TEntity, bool>>? CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
where TEntity : class
{
Expression<Func<TEntity, bool>>? expression = null;
@ -790,14 +797,37 @@ public abstract class AbpDbContext<TDbContext> : DbContext, IAbpEfCoreDbContext,
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(e, "IsDeleted");
if (UseDbFunction())
{
expression = e => AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilter(((ISoftDelete)e).IsDeleted, true);
modelBuilder.ConfigureSoftDeleteDbFunction(AbpEfCoreDataFilterDbFunctionMethods.SoftDeleteFilterMethodInfo, this.GetService<AbpEfCoreCurrentDbContext>());
}
}
if (typeof(IMultiTenant).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(e, "TenantId") == CurrentTenantId;
if (UseDbFunction())
{
multiTenantFilter = e => AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilter(((IMultiTenant)e).TenantId, CurrentTenantId, true);
modelBuilder.ConfigureMultiTenantDbFunction(AbpEfCoreDataFilterDbFunctionMethods.MultiTenantFilterMethodInfo, this.GetService<AbpEfCoreCurrentDbContext>());
}
expression = expression == null ? multiTenantFilter : QueryFilterExpressionHelper.CombineExpressions(expression, multiTenantFilter);
}
return expression;
}
protected virtual bool UseDbFunction()
{
return LazyServiceProvider != null && GlobalFilterOptions.Value.UseDbFunction && DbContextOptions.FindExtension<AbpDbContextOptionsExtension>() != null;
}
public virtual string GetCompiledQueryCacheKey()
{
return $"{CurrentTenantId?.ToString() ?? "Null"}:{IsSoftDeleteFilterEnabled}:{IsMultiTenantFilterEnabled}";
}
}

62
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<ICompiledQueryCacheKeyGenerator>(provider =>
ActivatorUtilities.CreateInstance<AbpCompiledQueryCacheKeyGenerator>(provider,
provider.GetRequiredService(serviceDescriptor.ImplementationType)
.As<ICompiledQueryCacheKeyGenerator>())));
}
services.Replace(ServiceDescriptor.Scoped<IAsyncQueryProvider, AbpEntityQueryProvider>());
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<string, string> debugInfo)
{
}
public override string LogFragment => "AbpOptionsExtension";
}
}

49
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<TResult>(Expression expression)
{
using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext))
{
return base.Execute<TResult>(expression);
}
}
public override TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = new CancellationToken())
{
using (AbpEfCoreCurrentDbContext.Use(CurrentDbContext.Context as IAbpEfCoreDbFunctionContext))
{
return base.ExecuteAsync<TResult>(expression, cancellationToken);
}
}
}
#pragma warning restore EF1001

6
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs

@ -4,6 +4,7 @@ using Microsoft.EntityFrameworkCore;
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 class DbContextOptionsFactory
PreConfigure(options, context);
Configure(options, context);
if (serviceProvider.GetRequiredService<IOptions<AbpEfCoreGlobalFilterOptions>>().Value.UseDbFunction)
{
context.DbContextOptions.AddAbpDbContextOptionsExtension();
}
return context.DbContextOptions.Options;
}

59
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<AbpCompiledQueryCacheKey>
{
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);
}
}
}

21
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<IAbpEfCoreDbFunctionContext?> _current = new AsyncLocal<IAbpEfCoreDbFunctionContext?>();
public IAbpEfCoreDbFunctionContext? Context => _current.Value;
public IDisposable Use(IAbpEfCoreDbFunctionContext? context)
{
var previousValue = Context;
_current.Value = context;
return new DisposeAction(() =>
{
_current.Value = previousValue;
});
}
}

25
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs

@ -0,0 +1,25 @@
using System;
using System.Reflection;
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public static class AbpEfCoreDataFilterDbFunctionMethods
{
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." ;
public static bool SoftDeleteFilter(bool isDeleted, bool boolParam)
{
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(NotSupportedExceptionMessage);
}
public static MethodInfo MultiTenantFilterMethodInfo => typeof(AbpEfCoreDataFilterDbFunctionMethods).GetMethod(nameof(MultiTenantFilter))!;
}

10
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs

@ -0,0 +1,10 @@
namespace Volo.Abp.EntityFrameworkCore.GlobalFilters;
public class AbpEfCoreGlobalFilterOptions
{
/// <summary>
/// Use User-defined function mapping to filter data.
/// https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping
/// </summary>
public bool UseDbFunction { get; set; }
}

16
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();
}

2
framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs

@ -70,7 +70,7 @@ public class AbpAuditingTestModule : AbpModule
connection.Open();
using (var context = new AbpAuditingTestDbContext(new DbContextOptionsBuilder<AbpAuditingTestDbContext>()
.UseSqlite(connection).Options))
.UseSqlite(connection).AddAbpDbContextOptionsExtension().Options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
}

4
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

@ -72,7 +72,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
{
options.Configure(abpDbContextConfigurationContext =>
{
abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection);
abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection).AddAbpDbContextOptionsExtension();
});
});
}
@ -101,7 +101,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule
var connection = new AbpUnitTestSqliteConnection("Data Source=:memory:");
connection.Open();
using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder<TestMigrationsDbContext>().UseSqlite(connection).Options))
using (var context = new TestMigrationsDbContext(new DbContextOptionsBuilder<TestMigrationsDbContext>().UseSqlite(connection).AddAbpDbContextOptionsExtension().Options))
{
context.GetService<IRelationalDatabaseCreator>().CreateTables();
context.Database.ExecuteSqlRaw(

Loading…
Cancel
Save