如何在表达式Tree中编写string.Contains(someText)

时间:2017-05-26 19:42:47

标签: c# linq expression-trees

这是the tutorial我正在学习表达树。

我要显示超过35列,但用户可以选择一次显示10列。因此,用户在搜索框中键入内容,我只想搜索用户可见的列。

SELECT FirstName, LastName, Address, ..., State
FROM Students
WHERE Id == @Id col1 AND (
      FirstName LIKE '%@searchText%' OR 
      LastName LIKE '%@searchText%' OR 
      Address LIKE '%@searchText%' OR 
      ...
      State LIKE '%@searchText%')

回到Linq,我正在努力实现它:

var result = db.Students
    .Where(GetPredicate(id, listOfColumns))
    .ToList();

这是私有方法:

private Expression<Func<Student, bool>> GetPredicate(int id, List<string> listOfColumns)
{
   ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

   Expression left0 = Expression.Property(pe, "Id");
   Expression right0 = Expression.Constant(id);
   Expression e0 = Expression.Equal(left0, right0);

   //Here ... omitted code because it's not working...
   //

   var expr = Expression.Lambda<Func<Student, bool>>(e0, new ParameterExpression[] { pe });
        return expr;
}

如上所述,它的工作正常。但是,我甚至编写此方法的原因是只能通过用户选择的列进行过滤。

我希望能够根据UI中可见的列进行撰写。

if(!string.IsNullOrEmpty(searchText))
{
   foreach (string columnName in columnList)
   {
       Expression col = Expression.Property(pe, columnName);
       Expression left = Expression.Call(pe, typeof(string).GetMethod("Contains"));
       Expression right = Expression.Constant(searchText);
       Expression e = Expression.IsTrue(left, right);
   }
}

我完全迷失了。我知道我需要访问字符串类的Contains方法然后我不知道接下来是什么。想法是得到这样的东西:

Where(d => d.Id == id && (d.FirstName.Contains(searchText) 
        || d.LastName.Contains(searchText) 
        || ...
        || d.State.Contains(searchText)))

感谢您的帮助

2 个答案:

答案 0 :(得分:4)

你非常接近,除了构建Contains的呼叫没有右侧:

Expression col = Expression.Property(pe, columnName);
Expression contains = Expression.Call(
    pe
,   typeof(string).GetMethod("Contains") // Make a static field out of this
,   Expression.Constant(searchText)      // Prepare a shared object before the loop
);

获得调用表达式后,将它们与OrElse组合以生成lambda的主体。您可以使用循环执行此操作,也可以使用LINQ:

private static readonly MethodInfo Contains = typeof(string).GetMethod(nameof(string.Contains));

public static Expression<Func<Student,bool>> SearchPredicate(IEnumerable<string> properties, string searchText) {
    var param = Expression.Parameter(typeof(Student));
    var search = Expression.Constant(searchText);
    var components = properties
        .Select(propName => Expression.Call(Expression.Property(param, propName), Contains, search))
        .Cast<Expression>()
        .ToList();
    // This is the part that you were missing
    var body = components
        .Skip(1)
        .Aggregate(components[0], Expression.OrElse);
    return Expression.Lambda<Func<Student, bool>>(body, param);
}

答案 1 :(得分:0)

我喜欢这个场景中的PredicateBuilder类:

using System;
using System.Linq;
using System.Linq.Expressions;
using System.Collections.Generic;

public static class PredicateBuilder
{
  public static Expression<Func<T, bool>> True<T> ()  { return f => true;  }
  public static Expression<Func<T, bool>> False<T> () { return f => false; }

  public static Expression<Func<T, bool>> Or<T> (this Expression<Func<T, bool>> expr1,
                                                      Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.OrElse (expr1.Body, invokedExpr), expr1.Parameters);
  }

  public static Expression<Func<T, bool>> And<T> (this Expression<Func<T, bool>> expr1,
                                                       Expression<Func<T, bool>> expr2)
  {
    var invokedExpr = Expression.Invoke (expr2, expr1.Parameters.Cast<Expression> ());
    return Expression.Lambda<Func<T, bool>>
          (Expression.AndAlso (expr1.Body, invokedExpr), expr1.Parameters);
  }
}

使用此代码的代码如下:

var predicate = PredicateBuilder.True<Student>().And(i=>i.Id==id);
if(!string.IsNullOrEmpty(searchText))
{
    if (firstNameColumnVisible) {
       predicate = predicate.And(i=>i.FirstName.Contains(searchText));
    }
    if (lastNameColumnVisible) {
       predicate = predicate.And(i=>i.LastName.Contains(searchText));
    }
    // more columns here.
}

最后,使用PredicateBuilder实例作为Linq查询中Where运算符的参数。