【问题标题】:.Net Core: How to combine multiple filter expressions into a single one.Net Core:如何将多个过滤器表达式组合成一个
【发布时间】:2021-09-02 04:21:39
【问题描述】:

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

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() 可能不是正确的做法。有没有更好的方法来解决这个问题?另外,我需要清除任何缓存的字典吗?

【问题讨论】:

    标签: c# .net-core entity-framework-core expression


    【解决方案1】:

    你可以做的是递归地创建 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);
    

    【讨论】:

    • 谢谢。我尝试了您的方法并将我的聚合代码更新为var resultExpression = expressions.Aggregate((l,r) =&gt; Expression.Lambda&lt;Func&lt;TEntity, bool&gt;&gt;( Expression.MakeBinary(ExpressionType.AndAlso, l, r), input)); 但现在我收到以下错误:二进制运算符 AndAlso 未定义为类型 'System.Func2[GraphQL.Api.Data.Models.User,System.Boolean]' and 'System.Func2[GraphQL.Api.Data.Models.User ,System.Boolean]'。我错过了什么吗?
    • 不,不,我没有编写您需要做的代码。您必须删除 .Aggregate 并递归构建二进制表达式。我只做了一个想法的布局,而不是整个代码,因为它的工作量太大
    • 对不起,您的建议仍然出现同样的错误,但是,我在您的启发下找到了解决方案。谢谢。
    【解决方案2】:

    如果有人遇到类似问题,这里是我想出的真正适合我的解决方案。

        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);
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-06
      • 1970-01-01
      • 2020-02-07
      • 2018-09-24
      • 2013-03-21
      • 1970-01-01
      相关资源
      最近更新 更多