.Net Core:如何将多个过滤器表达式组合成一个
.Net Core: How to combine multiple filter expressions into a single one
我有一个基础过滤器 class,它负责检查实体集合是否匹配其子 classes 中定义的过滤条件:
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();
}
此 class 用作:
public static IQueryable<TEntity> Filter<TEntity>(this IQueryable<TEntity> query, IFilterInput<TEntity> filterInput) =>
query.Where(filterInput?.MatchesFilter() ?? (x => true));
顾名思义,GetFilterExpressions
方法returns基于过滤参数的过滤表达式集合。这些筛选条件在 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);
}
我有一个基础过滤器 class,它负责检查实体集合是否匹配其子 classes 中定义的过滤条件:
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();
}
此 class 用作:
public static IQueryable<TEntity> Filter<TEntity>(this IQueryable<TEntity> query, IFilterInput<TEntity> filterInput) =>
query.Where(filterInput?.MatchesFilter() ?? (x => true));
顾名思义,GetFilterExpressions
方法returns基于过滤参数的过滤表达式集合。这些筛选条件在 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);
}