Net Core:如何将多个筛选器表达式组合为一个筛选器表达式



我有一个基本过滤器类,它负责检查实体集合是否与其子类中定义的过滤器条件匹配,如:

public abstract class FilterInputBase<TEntity> : IFilterInput<TEntity>
{
public Expression<Func<TEntity, bool>> MatchesFilter()
{
var input = Expression.Variable(typeof(TEntity), "entity");

var expressions = GetFilterExpressions().ToList();
if(expressions.Count == 0)
expressions.Add(e => true);

// checks if the input satisfies all the filter conditions
var resultExpression = expressions.Aggregate(
(l, r) => Expression.Lambda<Func<TEntity, bool>>(
Expression.AndAlso(Expression.Invoke(l, input), Expression.Invoke(r, input)), input));

return resultExpression;
}
/// <summary>
/// Returns a list of filter conditions converted into Expressions
/// </summary>
/// <returns></returns>
protected abstract IEnumerable<Expression<Func<TEntity, bool>>> GetFilterExpressions();
}

此类被用作:

public static IQueryable<TEntity> Filter<TEntity>(this IQueryable<TEntity> query, IFilterInput<TEntity> filterInput) => 
query.Where(filterInput?.MatchesFilter() ?? (x => true));

顾名思义,GetFilterExpressions方法返回一组基于筛选器参数的筛选器表达式。这些过滤条件在MatchesFilter方法中聚合以形成单个过滤表达式:resultExpression

此解决方案最多可使用两个筛选器表达式。现在我需要两个以上的表达式。但如果有更多的表达式,则查询在第二次尝试时失败。它适用于第一次运行。我得到了以下错误,添加了更多的表达式:

"message": "An item with the same key has already been added. Key: entity",
"stackTrace": "   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)n   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareInvocation(InvocationExpression a, InvocationExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareBinary(BinaryExpression a, BinaryExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareLambda(LambdaExpression a, LambdaExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareUnary(UnaryExpression a, UnaryExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareExpressionList(IReadOnlyList`1 a, IReadOnlyList`1 b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.CompareMethodCall(MethodCallExpression a, MethodCallExpression b)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.ExpressionComparer.Compare(Expression left, Expression right)n   at Microsoft.EntityFrameworkCore.Query.ExpressionEqualityComparer.Equals(Expression x, Expression y)n   at Microsoft.EntityFrameworkCore.Query.CompiledQueryCacheKeyGenerator.CompiledQueryCacheKey.Equals(CompiledQueryCacheKey other)n   at Microsoft.EntityFrameworkCore.Query.RelationalCompiledQueryCacheKeyGenerator.RelationalCompiledQueryCacheKey.Equals(RelationalCompiledQueryCacheKey other)n   at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerCompiledQueryCacheKeyGenerator.SqlServerCompiledQueryCacheKey.Equals(SqlServerCompiledQueryCacheKey other)n   at Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlServerCompiledQueryCacheKeyGenerator.SqlServerCompiledQueryCacheKey.Equals(Object obj)n   at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValueInternal(TKey key, Int32 hashcode, TValue& value)n   at System.Collections.Concurrent.ConcurrentDictionary`2.TryGetValue(TKey key, TValue& value)n   at Microsoft.Extensions.Caching.Memory.MemoryCache.TryGetValue(Object key, Object& result)n   at Microsoft.Extensions.Caching.Memory.CacheExtensions.TryGetValue[TItem](IMemoryCache cache, Object key, TItem& value)n   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)n   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)n   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)n   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)n   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()n   at HotChocolate.Types.Pagination.QueryableOffsetPagingHandler`1.ExecuteQueryableAsync(IQueryable`1 queryable, CancellationToken cancellationToken)n   at HotChocolate.Types.Pagination.QueryableOffsetPagingHandler`1.ResolveAsync(IResolverContext context, IQueryable`1 queryable, OffsetPagingArguments arguments)n   at HotChocolate.Types.Pagination.OffsetPagingHandler.HotChocolate.Types.Pagination.IPagingHandler.SliceAsync(IResolverContext context, Object source)n   at HotChocolate.Types.Pagination.PagingMiddleware.InvokeAsync(IMiddlewareContext context)n   at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)n   at HotChocolate.Data.ToListMiddleware`1.InvokeAsync(IMiddlewareContext context)n   at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<<UseDbContext>b__4>d.MoveNext()n--- End of stack trace from previous location where exception was thrown ---n   at HotChocolate.Types.EntityFrameworkObjectFieldDescriptorExtensions.<>c__DisplayClass2_1`1.<<UseDbContext>b__4>d.MoveNext()n--- End of stack trace from previous location where exception was thrown ---n   at HotChocolate.AspNetCore.Authorization.AuthorizeMiddleware.InvokeAsync(IDirectiveContext context)n   at HotChocolate.Utilities.MiddlewareCompiler`1.ExpressionHelper.AwaitTaskHelper(Task task)n   at HotChocolate.Execution.Processing.ResolverTask.ExecuteResolverPipelineAsync(CancellationToken cancellationToken)n   at HotChocolate.Execution.Processing.ResolverTask.TryExecuteAsync(CancellationToken cancellationToken)"

我认为expressions.Aggregate()可能不是正确的方法。有更好的方法吗?此外,我需要清除任何缓存的词典吗?

您可以递归地创建BinaryExpressions,然后用更多的BinaryExpressions将它们包装起来

例如,您有以下内容:

x => x > 1 
x => x == 2
x => x < 3
x => x != 4

我们基本上取最后两个表达式,并从中创建一个二进制表达式

var lastExpression = Expression.MakeBinary(ExpressionType.AndAlso,
expression3, expression4);

然后添加第二个和第一个

lastExpression = Expression.MakeBinary(ExpressionType.AndAlso, 
expression2, lastExpression);
lastExpression = Expression.MakeBinary(ExpressionType.AndAlso, 
expression1, lastExpression);

通过这种方式,您将创建类似以下表达式的内容:

x => (x > 1 && (x == 2 && (x < 3 && x != 4)))

你只需要调用

Expression.invoke(lastExpression, input);

如果有人遇到类似的问题,下面是我找到的对我有效的解决方案

public Expression<Func<TEntity, bool>> MatchesFilter()
{
var expressions = GetFilterExpressions().ToList();
if (expressions.Count <= 1)
return expressions.FirstOrDefault() ??  (x => true);
var input = Expression.Parameter(typeof(TEntity), "x");

var resultExpression = Expression.AndAlso(Expression.Invoke(expressions[0], input), Expression.Invoke(expressions[1], input));
resultExpression = expressions.Skip(2).Aggregate(resultExpression, (current, expression) =>
Expression.AndAlso(current, Expression.Invoke(expression, input)));
return Expression.Lambda<Func<TEntity, bool>>(resultExpression, input);
}

最新更新