committed by
GitHub
20 changed files with 1079 additions and 94 deletions
@ -0,0 +1,374 @@ |
|||||
|
using Elastic.Clients.Elasticsearch; |
||||
|
using Elastic.Clients.Elasticsearch.QueryDsl; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Expressions; |
||||
|
using Volo.Abp; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AuditLogging.Elasticsearch; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// ES DSL表达式翻译器:把 <see cref="Expression{TDelegate}"/> 翻译为
|
||||
|
/// Elastic.Clients.Elasticsearch 的 <see cref="Query"/>(QueryDsl)。
|
||||
|
/// <para>
|
||||
|
/// 支持的算子(超出即抛 <see cref="NotSupportedException"/>,fail loud):
|
||||
|
/// <list type="bullet">
|
||||
|
/// <item>逻辑:&&、||、!(映射为 bool filter / should+minimum_should_match / must_not)</item>
|
||||
|
/// <item>比较:==、!=、>、>=、<、<=(数值与日期映射为 term / range)</item>
|
||||
|
/// <item>null 判断:字段 == null / != null(映射为 must_not exists / exists)</item>
|
||||
|
/// <item>字符串:Contains / StartsWith / EndsWith(映射为 wildcard)、Equals(映射为 term)</item>
|
||||
|
/// <item>集合:x.Actions.Any(predicate)(映射为 nested 查询或扁平字段展开)</item>
|
||||
|
/// <item>常量:true / false(映射为 match_all / match_none)</item>
|
||||
|
/// </list>
|
||||
|
/// </para>
|
||||
|
/// </summary>
|
||||
|
internal class AuditLogExpressionQueryTranslator |
||||
|
{ |
||||
|
private bool _actionsIsNested; |
||||
|
private bool _caseInsensitiveWildcard; |
||||
|
private bool _appendKeywordForStringEquality; |
||||
|
|
||||
|
public AuditLogExpressionQueryTranslator( |
||||
|
bool actionsIsNested = false, |
||||
|
bool caseInsensitiveWildcard = true, |
||||
|
bool appendKeywordForStringEquality = true) |
||||
|
{ |
||||
|
_actionsIsNested = actionsIsNested; |
||||
|
_caseInsensitiveWildcard = caseInsensitiveWildcard; |
||||
|
_appendKeywordForStringEquality = appendKeywordForStringEquality; |
||||
|
} |
||||
|
|
||||
|
public Query Translate(Expression<Func<AuditLog, bool>> expression) |
||||
|
{ |
||||
|
Check.NotNull(expression, nameof(expression)); |
||||
|
|
||||
|
return TranslateNode(expression.Body, prefix: null); |
||||
|
} |
||||
|
|
||||
|
private Query TranslateNode(Expression node, string? prefix) |
||||
|
{ |
||||
|
return node switch |
||||
|
{ |
||||
|
ConstantExpression { Value: bool value } => |
||||
|
value ? new MatchAllQuery() : new MatchNoneQuery(), |
||||
|
UnaryExpression { NodeType: ExpressionType.Not } unary => |
||||
|
(!TranslateNode(unary.Operand, prefix))!, |
||||
|
UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary => |
||||
|
TranslateNode(unary.Operand, prefix), |
||||
|
BinaryExpression binary => TranslateBinary(binary, prefix), |
||||
|
MethodCallExpression method => TranslateMethodCall(method, prefix), |
||||
|
MemberExpression member when member.Type == typeof(bool) => |
||||
|
new TermQuery { Field = ResolveField(member, prefix).Path, Value = true }, |
||||
|
_ => throw new NotSupportedException($"Unsupported expression node {node.NodeType}: {node}"), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private Query TranslateBinary(BinaryExpression node, string? prefix) |
||||
|
{ |
||||
|
return node.NodeType switch |
||||
|
{ |
||||
|
ExpressionType.AndAlso or ExpressionType.And => (Query)new BoolQuery |
||||
|
{ |
||||
|
Filter = new Query[] { TranslateNode(node.Left, prefix), TranslateNode(node.Right, prefix) }, |
||||
|
}!, |
||||
|
ExpressionType.OrElse or ExpressionType.Or => (Query)new BoolQuery |
||||
|
{ |
||||
|
Should = new Query[] { TranslateNode(node.Left, prefix), TranslateNode(node.Right, prefix) }, |
||||
|
MinimumShouldMatch = 1, |
||||
|
}!, |
||||
|
ExpressionType.Equal => TranslateComparison(node, prefix), |
||||
|
ExpressionType.NotEqual => (!TranslateComparison(node, prefix))!, |
||||
|
ExpressionType.GreaterThan or ExpressionType.GreaterThanOrEqual or ExpressionType.LessThan or ExpressionType.LessThanOrEqual => TranslateComparison(node, prefix), |
||||
|
_ => throw new NotSupportedException($"Unsupported binary operator {node.NodeType}: {node}"), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private Query TranslateComparison(BinaryExpression node, string? prefix) |
||||
|
{ |
||||
|
var (fieldExpression, valueExpression) = ResolveOperands(node); |
||||
|
var field = ResolveField(fieldExpression, prefix); |
||||
|
|
||||
|
if (IsNullConstant(valueExpression)) |
||||
|
{ |
||||
|
return new BoolQuery |
||||
|
{ |
||||
|
MustNot = new Query[] { new ExistsQuery { Field = field.Path } }, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
var value = Evaluate(valueExpression); |
||||
|
|
||||
|
return value == null |
||||
|
? throw new NotSupportedException("The null value is only supported for the == null / != null comparison.") |
||||
|
: node.NodeType switch |
||||
|
{ |
||||
|
ExpressionType.Equal => BuildEquality(field, value), |
||||
|
ExpressionType.GreaterThan => BuildRange(field, greaterThan: value), |
||||
|
ExpressionType.GreaterThanOrEqual => BuildRange(field, greaterThanOrEqualTo: value), |
||||
|
ExpressionType.LessThan => BuildRange(field, lessThan: value), |
||||
|
ExpressionType.LessThanOrEqual => BuildRange(field, lessThanOrEqualTo: value), |
||||
|
_ => throw new NotSupportedException($"Unsupported comparison operator {node.NodeType}"), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static (Expression Field, Expression Value) ResolveOperands(BinaryExpression node) |
||||
|
{ |
||||
|
var leftIsField = IsFieldLike(node.Left); |
||||
|
var rightIsField = IsFieldLike(node.Right); |
||||
|
if (leftIsField == rightIsField) |
||||
|
{ |
||||
|
throw new NotSupportedException($"The comparison expression must have one side as a field and the other side as a value: {node}"); |
||||
|
} |
||||
|
return leftIsField ? (node.Left, node.Right) : (node.Right, node.Left); |
||||
|
} |
||||
|
|
||||
|
private static bool IsFieldLike(Expression expression) |
||||
|
{ |
||||
|
var current = expression; |
||||
|
while (current is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary) |
||||
|
{ |
||||
|
current = unary.Operand; |
||||
|
} |
||||
|
|
||||
|
while (current is MemberExpression member) |
||||
|
{ |
||||
|
current = member.Expression!; |
||||
|
} |
||||
|
|
||||
|
return current is ParameterExpression; |
||||
|
} |
||||
|
|
||||
|
private static bool IsNullConstant(Expression expression) |
||||
|
{ |
||||
|
while (expression is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary) |
||||
|
{ |
||||
|
expression = unary.Operand; |
||||
|
} |
||||
|
|
||||
|
return expression is ConstantExpression { Value: null }; |
||||
|
} |
||||
|
|
||||
|
private Query BuildEquality(FieldRef field, object value) |
||||
|
{ |
||||
|
if (field.Type == typeof(string)) |
||||
|
{ |
||||
|
var fieldName = _appendKeywordForStringEquality |
||||
|
? field.Path + ".keyword" |
||||
|
: field.Path; |
||||
|
return new TermQuery |
||||
|
{ |
||||
|
Field = fieldName, |
||||
|
Value = (string)value, |
||||
|
CaseInsensitive = _caseInsensitiveWildcard, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (field.Type == typeof(DateTime) || field.Type == typeof(DateTime?)) |
||||
|
{ |
||||
|
var date = (DateTime)value; |
||||
|
return new DateRangeQuery |
||||
|
{ |
||||
|
Field = field.Path, |
||||
|
Gte = date, |
||||
|
Lte = date, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
return new TermQuery { Field = field.Path, Value = NormalizeValue(value) }; |
||||
|
} |
||||
|
|
||||
|
private static Query BuildRange( |
||||
|
FieldRef field, |
||||
|
object? greaterThan = null, |
||||
|
object? greaterThanOrEqualTo = null, |
||||
|
object? lessThan = null, |
||||
|
object? lessThanOrEqualTo = null) |
||||
|
{ |
||||
|
if (field.Type == typeof(DateTime) || field.Type == typeof(DateTime?)) |
||||
|
{ |
||||
|
var range = new DateRangeQuery { Field = field.Path }; |
||||
|
if (greaterThan != null) |
||||
|
{ |
||||
|
range.Gt = (DateTime)greaterThan; |
||||
|
} |
||||
|
|
||||
|
if (greaterThanOrEqualTo != null) |
||||
|
{ |
||||
|
range.Gte = (DateTime)greaterThanOrEqualTo; |
||||
|
} |
||||
|
|
||||
|
if (lessThan != null) |
||||
|
{ |
||||
|
range.Lt = (DateTime)lessThan; |
||||
|
} |
||||
|
|
||||
|
if (lessThanOrEqualTo != null) |
||||
|
{ |
||||
|
range.Lte = (DateTime)lessThanOrEqualTo; |
||||
|
} |
||||
|
|
||||
|
return range; |
||||
|
} |
||||
|
|
||||
|
var numberRange = new NumberRangeQuery { Field = field.Path }; |
||||
|
if (greaterThan != null) |
||||
|
{ |
||||
|
numberRange.Gt = ToNumber(greaterThan); |
||||
|
} |
||||
|
|
||||
|
if (greaterThanOrEqualTo != null) |
||||
|
{ |
||||
|
numberRange.Gte = ToNumber(greaterThanOrEqualTo); |
||||
|
} |
||||
|
|
||||
|
if (lessThan != null) |
||||
|
{ |
||||
|
numberRange.Lt = ToNumber(lessThan); |
||||
|
} |
||||
|
|
||||
|
if (lessThanOrEqualTo != null) |
||||
|
{ |
||||
|
numberRange.Lte = ToNumber(lessThanOrEqualTo); |
||||
|
} |
||||
|
|
||||
|
return numberRange; |
||||
|
} |
||||
|
|
||||
|
private Query BuildWildcard(string fieldName, string pattern) |
||||
|
{ |
||||
|
return new WildcardQuery |
||||
|
{ |
||||
|
Field = fieldName, |
||||
|
Value = pattern, |
||||
|
CaseInsensitive = _caseInsensitiveWildcard, |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private Query TranslateMethodCall(MethodCallExpression node, string? prefix) |
||||
|
{ |
||||
|
if (node.Method.DeclaringType == typeof(Enumerable) && node.Method.Name == nameof(Enumerable.Any)) |
||||
|
{ |
||||
|
var collectionField = ResolveField(node.Arguments[0], prefix); |
||||
|
|
||||
|
Query inner; |
||||
|
if (node.Arguments.Count == 1) |
||||
|
{ |
||||
|
inner = new ExistsQuery { Field = collectionField.Path }; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var predicate = UnwrapLambda(node.Arguments[1]); |
||||
|
inner = TranslateNode(predicate.Body, prefix: collectionField.Path); |
||||
|
} |
||||
|
|
||||
|
return _actionsIsNested |
||||
|
? new NestedQuery(collectionField.Path, inner) |
||||
|
: inner; |
||||
|
} |
||||
|
|
||||
|
if (node.Method.DeclaringType == typeof(string) && node.Object != null) |
||||
|
{ |
||||
|
var field = ResolveField(node.Object, prefix); |
||||
|
var value = (string)Evaluate(node.Arguments[0])!; |
||||
|
return node.Method.Name switch |
||||
|
{ |
||||
|
nameof(string.Contains) => BuildWildcard(field.Path, "*" + EscapeWildcard(value) + "*"), |
||||
|
nameof(string.StartsWith) => BuildWildcard(field.Path, EscapeWildcard(value) + "*"), |
||||
|
nameof(string.EndsWith) => BuildWildcard(field.Path, "*" + EscapeWildcard(value)), |
||||
|
_ => throw new NotSupportedException($"Unsupported string method {node.Method.Name}"), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
if (node.Method.Name == nameof(string.Equals)) |
||||
|
{ |
||||
|
var fieldExpression = node.Object ?? node.Arguments[0]; |
||||
|
var valueExpression = node.Object != null ? node.Arguments[0] : node.Arguments[1]; |
||||
|
var field = ResolveField(fieldExpression, prefix); |
||||
|
return BuildEquality(field, (string)Evaluate(valueExpression)!); |
||||
|
} |
||||
|
|
||||
|
throw new NotSupportedException( |
||||
|
$"Unsupported method invocation {node.Method.DeclaringType?.Name}.{node.Method.Name}"); |
||||
|
} |
||||
|
|
||||
|
private static LambdaExpression UnwrapLambda(Expression expression) |
||||
|
{ |
||||
|
while (expression is UnaryExpression { NodeType: ExpressionType.Quote } unary) |
||||
|
{ |
||||
|
expression = unary.Operand; |
||||
|
} |
||||
|
return (LambdaExpression)expression; |
||||
|
} |
||||
|
|
||||
|
private static object? Evaluate(Expression expression) |
||||
|
{ |
||||
|
return expression is ConstantExpression constant |
||||
|
? constant.Value |
||||
|
: Expression.Lambda(expression).Compile().DynamicInvoke(); |
||||
|
} |
||||
|
|
||||
|
private static string EscapeWildcard(string value) |
||||
|
{ |
||||
|
return value.Replace("\\", "\\\\").Replace("*", "\\*").Replace("?", "\\?"); |
||||
|
} |
||||
|
|
||||
|
private static FieldValue NormalizeValue(object value) |
||||
|
{ |
||||
|
return value switch |
||||
|
{ |
||||
|
string s => s, |
||||
|
bool b => b, |
||||
|
int i => i, |
||||
|
long l => l, |
||||
|
double d => d, |
||||
|
Guid g => g.ToString(), |
||||
|
Enum e => Convert.ToInt64(e), |
||||
|
_ => Convert.ToDouble(value), |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private static Number? ToNumber(object? value) |
||||
|
{ |
||||
|
if (value == null) |
||||
|
{ |
||||
|
return null; |
||||
|
} |
||||
|
return value is double d ? d : Convert.ToInt64(value); |
||||
|
} |
||||
|
|
||||
|
private readonly record struct FieldRef(string Path, Type Type); |
||||
|
|
||||
|
private static FieldRef ResolveField(Expression expression, string? prefix) |
||||
|
{ |
||||
|
var current = expression; |
||||
|
while (current is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unary) |
||||
|
{ |
||||
|
current = unary.Operand; |
||||
|
} |
||||
|
|
||||
|
var names = new Stack<string>(); |
||||
|
while (current is MemberExpression member) |
||||
|
{ |
||||
|
names.Push(member.Member.Name); |
||||
|
current = member.Expression!; |
||||
|
} |
||||
|
|
||||
|
if (current is not ParameterExpression) |
||||
|
{ |
||||
|
throw new NotSupportedException($"Unable to parse as field path: {expression}"); |
||||
|
} |
||||
|
|
||||
|
var path = string.Join(".", names); |
||||
|
if (!string.IsNullOrEmpty(prefix) && path.Length > 0) |
||||
|
{ |
||||
|
path = prefix + "." + path; |
||||
|
} |
||||
|
else if (path.Length == 0) |
||||
|
{ |
||||
|
path = prefix ?? string.Empty; |
||||
|
} |
||||
|
|
||||
|
return new FieldRef(path, expression.Type); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,4 @@ |
|||||
|
namespace System.Runtime.CompilerServices |
||||
|
{ |
||||
|
internal static class IsExternalInit { } |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Expressions; |
||||
|
using System.Reflection; |
||||
|
|
||||
|
using VoloAuditLog = Volo.Abp.AuditLogging.AuditLog; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// 审计日志表达式树转换器
|
||||
|
/// </summary>
|
||||
|
internal class AuditLogExpressionQueryConverter : ExpressionVisitor |
||||
|
{ |
||||
|
private readonly IReadOnlyDictionary<Type, Type> _typeMap; |
||||
|
private readonly Dictionary<ParameterExpression, ParameterExpression> _parameterMap = new(); |
||||
|
|
||||
|
public AuditLogExpressionQueryConverter() |
||||
|
: this(BuildDefaultTypeMap()) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public AuditLogExpressionQueryConverter(IReadOnlyDictionary<Type, Type> typeMap) |
||||
|
{ |
||||
|
_typeMap = typeMap ?? throw new ArgumentNullException(nameof(typeMap)); |
||||
|
} |
||||
|
|
||||
|
public Expression<Func<VoloAuditLog, bool>> Convert(Expression<Func<AuditLog, bool>> expression) |
||||
|
{ |
||||
|
ArgumentNullException.ThrowIfNull(expression); |
||||
|
|
||||
|
_parameterMap.Clear(); |
||||
|
var rootParameter = Expression.Parameter(typeof(VoloAuditLog), expression.Parameters[0].Name); |
||||
|
_parameterMap[expression.Parameters[0]] = rootParameter; |
||||
|
|
||||
|
var body = Visit(expression.Body); |
||||
|
return Expression.Lambda<Func<VoloAuditLog, bool>>(body, rootParameter); |
||||
|
} |
||||
|
|
||||
|
protected override Expression VisitLambda<T>(Expression<T> node) |
||||
|
{ |
||||
|
var parameters = node.Parameters.Select(p => |
||||
|
{ |
||||
|
if (_parameterMap.TryGetValue(p, out var mapped)) |
||||
|
{ |
||||
|
return mapped; |
||||
|
} |
||||
|
if (_typeMap.TryGetValue(p.Type, out var targetType)) |
||||
|
{ |
||||
|
mapped = Expression.Parameter(targetType, p.Name); |
||||
|
_parameterMap[p] = mapped; |
||||
|
return mapped; |
||||
|
} |
||||
|
return p; |
||||
|
}).ToArray(); |
||||
|
|
||||
|
var body = Visit(node.Body); |
||||
|
return Expression.Lambda(body, parameters); |
||||
|
} |
||||
|
|
||||
|
protected override Expression VisitParameter(ParameterExpression node) |
||||
|
=> _parameterMap.TryGetValue(node, out var mapped) ? mapped : node; |
||||
|
|
||||
|
protected override Expression VisitMember(MemberExpression node) |
||||
|
{ |
||||
|
var expression = Visit(node.Expression); |
||||
|
|
||||
|
if (expression != null |
||||
|
&& node.Member is PropertyInfo property |
||||
|
&& _typeMap.ContainsKey(property.DeclaringType!)) |
||||
|
{ |
||||
|
var targetProperty = expression.Type.GetProperty( |
||||
|
property.Name, |
||||
|
BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy); |
||||
|
|
||||
|
if (targetProperty == null) |
||||
|
{ |
||||
|
throw new NotSupportedException( |
||||
|
$"The property {property.Name} could not be found on the target type {expression.Type.FullName} and thus the expression cannot be overridden."); |
||||
|
} |
||||
|
return Expression.MakeMemberAccess(expression, targetProperty); |
||||
|
} |
||||
|
|
||||
|
return node.Expression == expression |
||||
|
? node |
||||
|
: Expression.MakeMemberAccess(expression, node.Member); |
||||
|
} |
||||
|
|
||||
|
protected override Expression VisitMethodCall(MethodCallExpression node) |
||||
|
{ |
||||
|
if (node.Method.IsGenericMethod) |
||||
|
{ |
||||
|
var oldArguments = node.Method.GetGenericArguments(); |
||||
|
var newArguments = oldArguments |
||||
|
.Select(a => _typeMap.TryGetValue(a, out var mapped) ? mapped : a) |
||||
|
.ToArray(); |
||||
|
|
||||
|
if (!oldArguments.SequenceEqual(newArguments)) |
||||
|
{ |
||||
|
var targetMethod = node.Method.GetGenericMethodDefinition().MakeGenericMethod(newArguments); |
||||
|
var instance = node.Object != null ? Visit(node.Object) : null; |
||||
|
var arguments = node.Arguments.Select(Visit).ToArray(); |
||||
|
return Expression.Call(instance, targetMethod, arguments!); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return base.VisitMethodCall(node); |
||||
|
} |
||||
|
|
||||
|
protected override Expression VisitUnary(UnaryExpression node) |
||||
|
{ |
||||
|
if (node.NodeType == ExpressionType.Quote) |
||||
|
{ |
||||
|
var operand = Visit(node.Operand); |
||||
|
return Expression.Quote(operand); |
||||
|
} |
||||
|
return base.VisitUnary(node); |
||||
|
} |
||||
|
|
||||
|
protected override Expression VisitConstant(ConstantExpression node) |
||||
|
{ |
||||
|
if (node.Value is Enum enumValue && _typeMap.TryGetValue(enumValue.GetType(), out var targetType)) |
||||
|
{ |
||||
|
return Expression.Constant(Enum.ToObject(targetType, System.Convert.ToInt64(enumValue)), targetType); |
||||
|
} |
||||
|
return base.VisitConstant(node); |
||||
|
} |
||||
|
|
||||
|
private static Dictionary<Type, Type> BuildDefaultTypeMap() |
||||
|
{ |
||||
|
return new Dictionary<Type, Type> |
||||
|
{ |
||||
|
[typeof(AuditLog)] = typeof(VoloAuditLog), |
||||
|
[typeof(AuditLogAction)] = typeof(Volo.Abp.AuditLogging.AuditLogAction), |
||||
|
[typeof(EntityChange)] = typeof(Volo.Abp.AuditLogging.EntityChange), |
||||
|
[typeof(EntityPropertyChange)] = typeof(Volo.Abp.AuditLogging.EntityPropertyChange), |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,48 @@ |
|||||
|
using Microsoft.EntityFrameworkCore; |
||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Linq.Dynamic.Core; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AuditLogging; |
||||
|
using Volo.Abp.AuditLogging.EntityFrameworkCore; |
||||
|
using Volo.Abp.EntityFrameworkCore; |
||||
|
using Volo.Abp.Specifications; |
||||
|
|
||||
|
using VoloAuditLog = Volo.Abp.AuditLogging.AuditLog; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore; |
||||
|
|
||||
|
public class EfCoreAuditLogRepository : Volo.Abp.AuditLogging.EntityFrameworkCore.EfCoreAuditLogRepository, IEfCoreAuditLogRepository |
||||
|
{ |
||||
|
public EfCoreAuditLogRepository( |
||||
|
IDbContextProvider<IAuditLoggingDbContext> dbContextProvider) : base(dbContextProvider) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<long> GetCountAsync( |
||||
|
ISpecification<VoloAuditLog> specification, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetQueryableAsync()) |
||||
|
.Where(specification.ToExpression()) |
||||
|
.LongCountAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
|
||||
|
public async virtual Task<List<VoloAuditLog>> GetListAsync( |
||||
|
ISpecification<VoloAuditLog> specification, |
||||
|
string? sorting = null, |
||||
|
int maxResultCount = 50, |
||||
|
int skipCount = 0, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
return await (await GetQueryableAsync()) |
||||
|
.IncludeDetails(includeDetails) |
||||
|
.Where(specification.ToExpression()) |
||||
|
.OrderBy(sorting.IsNullOrWhiteSpace() ? $"{nameof(VoloAuditLog.ExecutionTime)} DESC" : sorting) |
||||
|
.PageBy(skipCount, maxResultCount) |
||||
|
.ToListAsync(GetCancellationToken(cancellationToken)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,24 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Volo.Abp.AuditLogging; |
||||
|
using Volo.Abp.Specifications; |
||||
|
|
||||
|
using VoloAuditLog = Volo.Abp.AuditLogging.AuditLog; |
||||
|
|
||||
|
namespace LINGYUN.Abp.AuditLogging.EntityFrameworkCore; |
||||
|
|
||||
|
public interface IEfCoreAuditLogRepository : IAuditLogRepository |
||||
|
{ |
||||
|
Task<long> GetCountAsync( |
||||
|
ISpecification<VoloAuditLog> specification, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
|
||||
|
Task<List<VoloAuditLog>> GetListAsync( |
||||
|
ISpecification<VoloAuditLog> specification, |
||||
|
string? sorting = null, |
||||
|
int maxResultCount = 50, |
||||
|
int skipCount = 0, |
||||
|
bool includeDetails = false, |
||||
|
CancellationToken cancellationToken = default); |
||||
|
} |
||||
Loading…
Reference in new issue