简体   繁体   English

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

[英]How to write string.Contains(someText) in expression Tree

This is the tutorial I'm following to learn Expression Tree. 这是我正在学习表达式树的教程

I've more than 35 columns to display, but the user can chose to display 10 columns at once. 我要显示超过35列,但用户可以选择一次显示10列。 So one the user type something in the search box, I want to search only the columns that are visible to the user. 因此,用户在搜索框中键入内容,我只想搜索用户可见的列。

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%')

Back to Linq, this is how I'm trying to accomplish it: 回到Linq,这是我试图完成它的方式:

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

This the private method: 这个私有方法:

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

As it is above, it's working just fine. 如上所述,它的工作正常。 However, the reason I even wrote this method was to be able to filter only by the user-selected columns. 但是,我甚至编写此方法的原因是只能通过用户选择的列进行过滤。

I want to be able to compose based on the column that are visible in the UI. 我希望能够根据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);
   }
}

I'm completely lost. 我完全迷失了。 I know that I need to access the Contains method of the string class then I don't know what next. 我知道我需要访问字符串类的Contains方法然后我不知道接下来是什么。 The Idea is to get something like this: 想法是得到这样的东西:

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

Thanks for helping 谢谢你的帮助

You are pretty close, except constructing the call of Contains does not have a right side: 你非常接近,除了构建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
);

Once you have your call expressions, combine them with OrElse to produce the body of your lambda. 获得调用表达式后,将它们与OrElse结合使用以生成lambda的主体。 You can do it with loops, or you can use LINQ: 你可以用循环来做,或者你可以使用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);
}

I like the PredicateBuilder class for stuff like this scenario: 对于像这样的场景,我喜欢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);
  }
}

Code using this would look like: 使用此代码看起来像:

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.
}

At the end, use the PredicateBuilder instance as arguments to your Where operator in the Linq query. 最后,使用PredicateBuilder实例作为Linq查询中Where运算符的参数。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM