【问题标题】:Using Dynamic Or Expressions in C#在 C# 中使用动态或表达式
【发布时间】:2015-04-06 08:18:07
【问题描述】:

我在我的程序中使用动态过滤器,如下解决方案:

public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
{
        var someIds = new int[]{1,2,3,4,5};
        var userId = 2;

        Expression<Func<T, bool>> predicate1 = e => someIds.Contains(e.Id);
        Expression<Func<T, bool>> predicate2 = e => e.UserId==userId;
        Expression<Func<T, bool>> predicate3 = e => e.CreatedDate != null;

        var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));
        var pred2 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(pred1, predicate3));


        var result = queryable
            .Where(pred2);

        return result;
    }

IModel是这样的:

Public interface IModel{
   int Id { get; set; }
   int UserId { get; set; }
   DateTime? CreatedDate { get; set; }
}

但是,我得到了这个错误:

二元运算符 Or 没有为类型“System.Func2[AnyClass,System.Boolean]' and 'System.Func2[AnyClass,System.Boolean]”定义。

在这一行:

var pred1 = Expression.Lambda<Func<T, bool>>(System.Linq.Expressions.Expression.Or(predicate1, predicate2));

我该如何解决这个问题?

感谢任何帮助

【问题讨论】:

  • 这个someIds.Contains(e.Id) 不正确。 IModel 不包含 Id 字段。
  • 你为什么使用 Expression 而不是 Func?
  • 你离你需要的太远了......你必须使用Expression.OrElse,因为Expression.Or|运算符。
  • 但真正的问题是您不能以这种方式加入 Lambda 表达式(predicate123)。连接表达式很复杂,因为predicate123 的输入参数不同(就像是e1e2e3 而不是e)和pred1pred2 将有一个e4e5
  • @AlexVoskresenskiy 因为他使用的是IQueryable,它只适用于Expression

标签: c# linq expression


【解决方案1】:

正如我在我的 cmets 中所写,有两个不同的问题:

  • 您必须使用Expression.OrElse,因为Expression.Or| 运算符。

  • 但真正的问题是您不能以这种方式加入 Lambda 表达式(predicate123)。连接表达式很复杂,因为predicate123 的输入参数不同(就像是e1e2e3 而不是e)和pred1pred2 将有一个e4e5 输入参数,因此您必须进行一些表达式替换。

解决办法:

// Note that we only need the Body!
Expression pred1 = Expression.OrElse(Expression.OrElse(predicate1.Body, predicate2.Body), predicate3.Body);

// We change all the predicate2.Parameters[0] to predicate1.Parameters[0] and
// predicate3.Parameters[0] to predicate1.Parameters[0]

var replacer = new SimpleExpressionReplacer(
    /* from */ new[] { predicate2.Parameters[0], predicate3.Parameters[0] }, 
    /* to */ new[] { predicate1.Parameters[0], predicate1.Parameters[0] });

pred1 = replacer.Visit(pred1);

// We use for the new predicate the predicate1.Parameters[0]
var pred2 = Expression.Lambda<Func<T, bool>>(pred1, predicate1.Parameters[0]);

var result = queryable.Where(pred2);

还有SimpleExpressionReplacer

// A simple expression visitor to replace some nodes of an expression 
// with some other nodes
public class SimpleExpressionReplacer : ExpressionVisitor
{
    public readonly Dictionary<Expression, Expression> Replaces;

    public SimpleExpressionReplacer(Dictionary<Expression, Expression> replaces)
    {
        Replaces = replaces;
    }

    public SimpleExpressionReplacer(IEnumerable<Expression> from, IEnumerable<Expression> to)
    {
        Replaces = new Dictionary<Expression, Expression>();

        using (var enu1 = from.GetEnumerator())
        using (var enu2 = to.GetEnumerator())
        {
            while (true)
            {
                bool res1 = enu1.MoveNext();
                bool res2 = enu2.MoveNext();

                if (!res1 || !res2)
                {
                    if (!res1 && !res2)
                    {
                        break;
                    }

                    if (!res1)
                    {
                        throw new ArgumentException("from shorter");
                    }

                    throw new ArgumentException("to shorter");
                }

                Replaces.Add(enu1.Current, enu2.Current);
            }
        }
    }

    public SimpleExpressionReplacer(Expression from, Expression to)
    {
        Replaces = new Dictionary<Expression, Expression> { { from, to } };
    }

    public override Expression Visit(Expression node)
    {
        Expression to;

        if (node != null && Replaces.TryGetValue(node, out to))
        {
            return base.Visit(to);
        }

        return base.Visit(node);
    }
}

【讨论】:

    【解决方案2】:

    使用PredicateBuilder

    要么从here 复制类的源代码,要么安装LINQKit NuGet package(它依赖于实体框架),以获取PredicateBuilder 类。将表达式与它动态链接起来很简单:

    var predicate = PredicateBuilder.False<int>();
    predicate = predicate.Or(x => x > 5);
    predicate = predicate.Or(x => x % 2 == 0);
    predicate = predicate.Or(x => x == 42 );
    
    var queryable = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }.AsQueryable();
    var result = queryable.Where(predicate);
    

    【讨论】:

      【解决方案3】:

      您可以创建一个没有表达式的谓词。或者:

      public static IQueryable<T> MyFilter<T>(this IQueryable<T> queryable) where T : class ,IModel
          {
              var someIds = new int[] { 1, 2, 3, 4, 5 };
              var userId = 2;
      
              var result = queryable
                  .Where(e => someIds.Contains(e.Id) || e.UserId == userId || e.CreatedDate != null);
      
              return result;
          }
      

      【讨论】:

      • 您甚至不需要中间的predicate 变量。将表达式放在Where 调用中会更短更简洁。
      • 好的,没错。但是我们有很多谓词应该添加到我的表达中。无论如何,你的回答是正确的,解决了我的问题,谢谢;)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-29
      • 2011-04-03
      • 2017-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多