mirror of https://github.com/abpframework/abp.git
30 changed files with 612 additions and 40 deletions
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
} |
||||
|
} |
||||
@ -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"; |
||||
|
} |
||||
|
} |
||||
@ -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
|
||||
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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; |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -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))!; |
||||
|
} |
||||
@ -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; } |
||||
|
} |
||||
@ -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(); |
||||
|
} |
||||
Loading…
Reference in new issue