这是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)))
感谢您的帮助
答案 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运算符的参数。