Skip to content

Commit

Permalink
Use DbFunction for global query filters.
Browse files Browse the repository at this point in the history
Resolve #20062
  • Loading branch information
maliming committed Jun 18, 2024
1 parent 493cf71 commit 3c8944c
Show file tree
Hide file tree
Showing 13 changed files with 364 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Volo.Abp.EntityFrameworkCore;

namespace Microsoft.Extensions.DependencyInjection;

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

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

namespace Microsoft.Extensions.DependencyInjection;

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

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

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

return modelBuilder;
}

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

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

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

return modelBuilder;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -37,7 +39,7 @@

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!;
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -713,7 +717,7 @@ protected virtual void ConfigureGlobalFilters<TEntity>(ModelBuilder modelBuilder
{
if (mutableEntityType.BaseType == null && ShouldFilterEntity<TEntity>(mutableEntityType))
{
var filterExpression = CreateFilterExpression<TEntity>();
var filterExpression = CreateFilterExpression<TEntity>(modelBuilder);
if (filterExpression != null)
{
modelBuilder.Entity<TEntity>().HasAbpQueryFilter(filterExpression);
Expand Down Expand Up @@ -782,22 +786,41 @@ protected virtual bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType
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}";
}
}
Original file line number Diff line number Diff line change
@@ -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";
}
}
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,6 +28,11 @@ public static DbContextOptions<TDbContext> Create<TDbContext>(IServiceProvider s
PreConfigure(options, context);
Configure(options, context);

if (serviceProvider.GetRequiredService<IOptions<AbpEfCoreGlobalFilterOptions>>().Value.UseDbFunction)
{
context.DbContextOptions.AddAbpDbContextOptionsExtension();
}

return context.DbContextOptions.Options;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
});
}
}
Loading

0 comments on commit 3c8944c

Please sign in to comment.