Comment écrire string.Contains (someText) dans l'arbre de l'expression

c# expression-trees linq

Question

Voici le tutoriel que je suis pour apprendre l'arbre d'expression.

J'ai plus de 35 colonnes à afficher, mais l'utilisateur peut choisir d'afficher 10 colonnes à la fois. Donc, si l’utilisateur saisit quelque chose dans la zone de recherche, je souhaite rechercher uniquement les colonnes visibles par l’utilisateur.

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

De retour à Linq, voici comment j'essaie de le faire:

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

C'est la méthode privée:

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

Comme ci-dessus, cela fonctionne très bien. Cependant, la raison pour laquelle j'ai même écrit cette méthode était pour pouvoir filtrer uniquement par les colonnes sélectionnées par l'utilisateur.

Je veux pouvoir composer en fonction des colonnes visibles dans l'interface utilisateur.

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

Je suis complètement perdu. Je sais que je dois accéder à la méthode Contains de la classe string alors je ne sais pas quelle est la suite. L'idée est d'obtenir quelque chose comme ça:

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

Merci pour ton aide

Réponse acceptée

Vous êtes assez proche, mais la construction de l'appel de Contains n'a pas de côté droit:

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

Une fois que vous avez vos expressions d'appel, combinez-les avec OrElse pour produire le corps de votre lambda. Vous pouvez le faire avec des boucles, ou vous pouvez utiliser 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);
}

Réponse populaire

J'aime la classe PredicateBuilder pour des choses comme ce scénario:

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

Le code utilisant ceci ressemblerait à ceci:

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

A la fin, utilisez l'instance PredicateBuilder comme arguments de votre opérateur Where dans la requête Linq.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow