Questo è il tutorial che sto seguendo per imparare Expression Tree.
Ho più di 35 colonne da visualizzare, ma l'utente può scegliere di visualizzare 10 colonne contemporaneamente. Così l'utente digita qualcosa nella casella di ricerca, voglio cercare solo le colonne che sono visibili all'utente.
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%')
Tornando a Linq, questo è il modo in cui sto provando a realizzarlo:
var result = db.Students
.Where(GetPredicate(id, listOfColumns))
.ToList();
Questo è il metodo privato:
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;
}
Come è sopra, sta funzionando bene. Tuttavia, il motivo per cui ho scritto questo metodo era di essere in grado di filtrare solo dalle colonne selezionate dall'utente.
Voglio essere in grado di comporre in base alla colonna che sono visibili nell'interfaccia utente.
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);
}
}
Sono completamente perso. So che ho bisogno di accedere al metodo Contiene della classe string, quindi non so cosa sia il prossimo. L'idea è di ottenere qualcosa come questo:
Where(d => d.Id == id && (d.FirstName.Contains(searchText)
|| d.LastName.Contains(searchText)
|| ...
|| d.State.Contains(searchText)))
Grazie dell'aiuto
Sei molto vicino, tranne che costruire la chiamata di Contains
non ha il lato destro:
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
);
Una volta che hai le tue espressioni di chiamata, OrElse
con OrElse
per produrre il corpo della tua lambda. Puoi farlo con i loop, oppure puoi usare 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);
}
Mi piace la classe PredicateBuilder per cose come questo scenario:
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);
}
}
Il codice che usa questo sarebbe simile a:
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.
}
Al termine, utilizzare l'istanza PredicateBuilder come argomenti per l'operatore Where nella query Linq.