Browse Source

Use DbFunction for global query filters.

Resolve #20062
pull/20065/head
maliming 2 years ago
parent
commit
3c8944cec5
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreDbContextOptionsBuilderExtensions.cs
  2. 68
      framework/src/Volo.Abp.EntityFrameworkCore/Microsoft/Extensions/DependencyInjection/AbpEfCoreModelBuilderExtensions.cs
  3. 31
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  4. 62
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptionsExtension.cs
  5. 49
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpEntityQueryProvider.cs
  6. 6
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs
  7. 59
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpCompiledQueryCacheKeyGenerator.cs
  8. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreCurrentDbContext.cs
  9. 21
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreDataFilterDbFunctionMethods.cs
  10. 11
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/AbpEfCoreGlobalFilterOptions.cs
  11. 16
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/GlobalFilters/IAbpEfCoreDbFunctionContext.cs
  12. 2
      framework/test/Volo.Abp.Auditing.Tests/Volo/Abp/Auditing/AbpAuditingTestModule.cs
  13. 4
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs

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

31
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(
@ -713,7 +717,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,22 +786,41 @@ 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;
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>();
if (typeof(ISoftDelete).IsAssignableFrom(typeof(TEntity)))
{
expression = e => !IsSoftDeleteFilterEnabled || !EF.Property<bool>(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<Func<TEntity, bool>> multiTenantFilter = e => !IsMultiTenantFilterEnabled || EF.Property<Guid>(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}";
}
}

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

21
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))!;
}

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

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