Linq 扩展表达式

Linq extending Expressions

我正在尝试为具有以下签名的 ServiceStack.OrmLite.SqlExpressionVisitor 编写通用通配符搜索:

public static SqlExpressionVisitor<T> WhereWildcardSearch<T> (this SqlExpressionVisitor<T> ev, Expression<Func<T,string>> field, string search)

其中 ev 是过滤器的其余部分,field 是 getter 作为搜索依据的字段,search 是输入的术语。

通常(非通用)我会写以下内容:

if(search.StartsWith('*') && search.EndsWith('*')) 
    ev = ev.Where(x => x.foo.Contains(search.Trim('*')));

当然还有 x.foo.StartsWith 或 EndsWith 的变体。

现在我正在搜索类似(伪代码:)

ev = ev.Where(x => field(x).Contains(search.Trim('*')));

当然我不能直接编译和调用表达式,因为这应该使用 Linq2Sql.

转换为 Sql

到目前为止,这是我的代码:

var getFieldExpression = Expression.Invoke (field, Expression.Parameter (typeof (T), "getFieldParam"));
var searchConstant = Expression.Constant (search.Trim('*'));

var inExp = Expression.Call (getFieldExpression, typeof(String).GetMethod("Contains"), searchConstant);
var param = Expression.Parameter (typeof (T), "object");
var exp = Expression.Lambda<Func<T, bool>> (inExp, param);

ev = ev.Where (exp);

请不要告诉我应该直接写 SQL 和 $"LIKE %search%" 之类的 - 我知道还有其他方法,但解决这个问题将有助于我理解 Linq 和表达式一般,当我无法解决时,它会困扰我。

这是如何完成的(我想你会很清楚你做错了什么,但如果没有 - 请随时要求澄清):

// extract property name from passed expression
var propertyName = ((MemberExpression)field.Body).Member.Name;            
var param = Expression.Parameter(typeof(T), "object");            
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");
// object.FieldName.Contains(searchConstant)
var inExp = Expression.Call(Expression.PropertyOrField(param, propertyName), contains, searchConstant);            
// object => object.FieldName.Contains(searchConstant)
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);

回复评论。您有两个表达式树:一个正在传递给您,另一个是您正在构建的 (exp)。在这个简单的例子中,它们都使用相同数量的参数,并且这些参数的类型相同 (T)。在这种情况下,您可以重用 field 表达式树中的参数,如下所示:

// use the same parameter
var param = field.Parameters[0];
var searchConstant = Expression.Constant(search.Trim('*'));
var contains = typeof(String).GetMethod("Contains");            
// note field.Body here. Your `field` expression is "parameter => parameter.Something"
// but we need just "parameter.Something" expression here
var inExp = Expression.Call(field.Body, contains, searchConstant);
// pass the same parameter to new tree
var exp = Expression.Lambda<Func<T, bool>>(inExp, param);

在更复杂的情况下,您可能需要使用 ExpressionVisitor 替换一个表达式树中的参数以引用另一个(最终)表达式树中的参数。