Linq extensions

I'm trying to write a generic wildcard for 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, the field is the recipient for the search field, and the search term is the entered term.

Usually (not generic) I would write the following:

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

      

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

Now I'm looking for something like (pseudocode :)

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

      

Of course, I cannot compile and call the expression directly, as this has to be translated to Sql using Linq2Sql.

This is my code:

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 I have to write SQL directly using $"LIKE %search%"

or something like that. I know there are other ways, but solving this will help me understand Linq and expressions in general, and it hurts me when I can Solve it.

+3


source to share


1 answer


Here's how to do it (I think it will be clear to you, without any further explanation, what you did wrong, but if not, feel free to ask for 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 a comment. You have two expression trees, one passed to you and the other that you are building ( exp

). In this simple case, both use the same number of parameters, and these parameters are of the same type ( T

). In this case, you can reuse the parameter from the expression tree field

, for example:



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

      

For more complex cases, you may need to use ExpressionVisitor

parameter replacements in one expression tree to reference parameters from another (final) expression tree.

+3


source







All Articles