2017-06-16 148 views
1

假设数组是一个整数数组:将x => array.Contains(x)表达式转换为x => x == 1 || X == 2

var array = new [] { 1, 2 } 

而且,我们说有一个对象名一些,与性能:

public class Some 
{ 
    public int Id { get; set;} 
} 

我需要一种方法来转换:

Expression<Func<Some, bool>> exp = x => array.Contains(x.Id) 

表达成:

Expression<Func<Some, bool>> exp = x => x.Id == 1 || x.Id == 2 

UPDATE

我已经在列表中的扩展方法,从列表生成所需的结果:我所问的是,给定的表达式1我怎么可能将其转换为表达2.我不希望推动其他团队成员使用扩展而不是普通的包含方法。

我的扩展方法:

array.SafeContainsExpression<Some, string>(nameof(Some.Id)); 

,代码:

public static Expression<Func<TModel, bool>> SafeContainsExpression<TModel, TValue>(
     this IEnumerable<TValue> list, string propertyName) 
    { 
     var argParam = Expression.Parameter(typeof(TModel), "x"); 
     var selector = Expression.Property(argParam, propertyName); 

     Expression left = null; 
     foreach (var value in list) 
     { 
      var valueExpression = Expression.Constant(value, typeof(TValue)); 
      var right = Expression.Equal(selector, valueExpression); 

      if (left == null) 
       left = right; 

      left = Expression.OrElse(left, right); 
     } 

     return Expression.Lambda<Func<TModel, bool>>(left, argParam); 
    } 

溶液(来自接受的答案)

public class SafeExpressionsVisitor : LinqKit.ExpressionVisitor 
{ 
    protected override Expression VisitMethodCall(MethodCallExpression m) 
    { 
     if (m.Method.Name == "Contains" && m.Arguments.Count == 2) 
     { 
      var list = Expression.Lambda<Func<IEnumerable>>(m.Arguments[0]).Compile()(); 
      var propertyExpression = (MemberExpression)m.Arguments[1]; 
      Expression left = null; 
      foreach (var value in list) 
      { 
       var valueExpression = Expression.Constant(value); 
       var right = Expression.Equal(propertyExpression, valueExpression); 

       if (left == null) 
       { 
        left = right; 
        continue; 
       } 

       left = Expression.OrElse(left, right); 
      } 

      return left; 
     } 
     return base.VisitMethodCall(m); 
    } 
} 


public class ExpressionTests 
{ 
    [Fact] 
    public void Shoul_Convert_With_Visitor() 
    { 
     var array = new[] { 1, 2 }; 

     Expression<Func<A, bool>> exp = x => array.Contains(x.Id); 

     var safeExp = Expression.Lambda<Func<A, bool>>(
      new SafeExpressionsVisitor().Visit(exp.Body), 
      exp.Parameters); 

     var func = safeExp.Compile(); 

     Assert.True(func(new A { Id = 1 })); 
     Assert.True(func(new A { Id = 2 })); 
     Assert.False(func(new A { Id = 3 })); 
    } 
} 
+0

你是什么意思转换?为什么不更换? –

+4

为什么?如果这是某种优化传递,那么最好用'HashSet.Contains'代替它。 –

+0

@SergeyBerezovskiy这是一个伪代码,数组的值在运行时改变。我认为我需要一个'ExpressionVisitor'来获取数组的值,并为每个值从'Expression.Equal'创建表达式,并用'Expression.OrElse'链接它们。但我无法弄清楚如何去做。 –

回答

1

转换可以工作,你可以建立一个表达式像这样。

Expression<Func<A, bool>> exp2 = Expression.Lambda<Func<A, bool>>(
       array.Select(i=>Expression.Equal(Expression.Property(p1,"Id"),Expression.Constant(i))).Aggregate((a,i)=> a == null? i:Expression.OrElse(a,i)),p1); 

EDIT 这将转换的例子,但对于更通用的东西,你需要支付的表达式树的更多情况。

var array = new[] { 1, 2 }; 

      Expression<Func<A, bool>> exp = x => array.Contains(x.Id); 
      Expression<Func<A, bool>> exp2 = x => x.Id == 1 || x.Id == 2; 


      var p1 = Expression.Parameter(typeof(A)); 

      var exp3 = Expression.Lambda<Func<A, bool>>(ExpressionVisitor.Visit(new []{ exp.Body }.ToList().AsReadOnly(), (m) => { 
       if (m.NodeType == ExpressionType.Call) 
       { 
        var method = (MethodCallExpression)m; 
        if (method.Method.Name == "Contains" && method.Arguments.Count == 2) 
        { 
         var items = Expression.Lambda<Func<object>>(method.Arguments[0]).Compile()(); 
         var prop = ((MemberExpression)method.Arguments[1]); 
         return ((IEnumerable<int>)items).Select(i => Expression.Equal(Expression.Property(prop.Expression, prop.Member.Name), Expression.Constant(i))).Aggregate((a, i) => a == null ? i : Expression.OrElse(a, i)); 

        } 
       } 
       return m; 
      })[0],exp.Parameters); 


      var func = exp3.Compile(); 

      Console.WriteLine(func(new A { Id = 1 })); 
      Console.WriteLine(func(new A { Id = 2 })); 
      Console.WriteLine(func(new A { Id = 3 })); 
+0

感谢您的回答,我根据您的回答制作了ExpressionVisitor,像魅力一样工作))将更新我的问题以显示解决方案。 –

+0

@MerdanGochmuradov只需要注意一下,可能会有一些边缘案例女巫没有覆盖,所以做了很多测试。 –

1

而不是

Expression<Func<Some, bool>> exp = x => array.Contains(x.Id) 

你可以写:

var filters = array.Select<int, Expression<Func<Some, bool>>>(i => 
    x => x.Id == i); 

Expression<Func<Some, bool>> exp = filters.OrTheseFiltersTogether(); 

用我的经典方法:

public static Expression<Func<T, bool>> OrTheseFiltersTogether<T>(
    this IEnumerable<Expression<Func<T, bool>>> filters) 
{ 
    Expression<Func<T, bool>> firstFilter = filters.FirstOrDefault(); 
    if (firstFilter == null) 
    { 
     Expression<Func<T, bool>> alwaysTrue = x => true; 
     return alwaysTrue; 
    } 

    var body = firstFilter.Body; 
    var param = firstFilter.Parameters.ToArray(); 
    foreach (var nextFilter in filters.Skip(1)) 
    { 
     var nextBody = Expression.Invoke(nextFilter, param); 
     body = Expression.OrElse(body, nextBody); 
    } 
    Expression<Func<T, bool>> result = Expression.Lambda<Func<T, bool>>(body, param); 
    return result; 
} 
+0

很少有查询提供者实际上支持'Invoke'。几乎所有人只是在碰到它时才抛出。 – Servy

相关问题