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
替换一个表达式树中的参数以引用另一个(最终)表达式树中的参数。
我正在尝试为具有以下签名的 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
替换一个表达式树中的参数以引用另一个(最终)表达式树中的参数。