简体   繁体   中英

Linq extending Expressions

I'm trying to write a generic wildcard Search for the ServiceStack.OrmLite.SqlExpressionVisitor that has the following signature:

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

where ev is the rest of the filter, field is the getter for the field to search by and search is the entered term.

Normally (non-generic) I would write the following:

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

and of course also variants for x.foo.StartsWith or EndsWith.

Now I am searching for something like (pseudocode:)

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

Of course I can't compile and call the expression directly, as this should be translated to Sql using Linq2Sql.

This is my code so far:

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

Please don't tell me that I should directly write SQL with $"LIKE %search%" or something - I know that there are other ways, but solving this would help my understanding of Linq and Expressions in general and it bugs me when I can't solve it.

Here is how it can be done (I think it will be clear for you without much additional explanations what you did wrong, but if not - feel free to request a clarification):

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

In response to comment. You have two expression trees: one is being passed to you and another one which you are building ( exp ). In this simple case they both use the same number of parameters and those parameters are of the same type ( T ). In this case you can reuse parameter from field expression tree, like this:

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

In more complicated cases you might need to use ExpressionVisitor to replace parameters in one expression tree to reference to parameters from another (final) expression tree.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM