How to Create an Expression Tree to Represent 'String.Contains("Term")' in C#

How do I create an expression tree to represent 'String.Contains(term)' in C#?

Something like:

class Foo
{
public string Bar { get; set; }
}
static void Main()
{
var lambda = GetExpression<Foo>("Bar", "abc");
Foo foo = new Foo { Bar = "aabca" };
bool test = lambda.Compile()(foo);
}
static Expression<Func<T, bool>> GetExpression<T>(string propertyName, string propertyValue)
{
var parameterExp = Expression.Parameter(typeof(T), "type");
var propertyExp = Expression.Property(parameterExp, propertyName);
MethodInfo method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
var someValue = Expression.Constant(propertyValue, typeof(string));
var containsMethodExp = Expression.Call(propertyExp, method, someValue);

return Expression.Lambda<Func<T, bool>>(containsMethodExp, parameterExp);
}

You might find this helpful.

How to write string.Contains(someText) in expression Tree

You are pretty close, except constructing the call of Contains does not have a right side:

Expression col = Expression.Property(pe, columnName);
Expression contains = Expression.Call(
pe
, typeof(string).GetMethod(nameof(string.Contains), new Type[] { typeof(string)}) // Make a static field out of this
, Expression.Constant(searchText) // Prepare a shared object before the loop
);

Once you have your call expressions, combine them with OrElse to produce the body of your lambda. You can do it with loops, or you can use LINQ:

private static readonly MethodInfo Contains = typeof(string)
.GetMethod(nameof(string.Contains), new Type[] { typeof(string)});

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

Building Expression Tree for string.Contains

You are almost there, but your parameter expression should be of type T, not String, you are also missing the expression that is getting the property from type T like name.

What you should roughly have is this

val -> Expression.Constant(typeof(string), rule.Field)
parameter -> Expression.Parameter(typeof(T), "p")
property -> Expression.Property(parameter, "PropertyName")
contains -> Expression.Call(property, containsmethod, val)
equals true -> Expression.True or equals, something like that

I am freehanding all of that, so it's likely somewhat different to be valid. The resulting expression should be something like this

p => p.Name.Contains(val)

Expression Tree for Any with Contains

This should do the trick:

public static class FilterExtensions
{
private readonly struct Node
{
public Node(ParameterExpression parameter, Expression body)
{
Parameter = parameter;
Body = body;
}

public ParameterExpression Parameter { get; }
public Expression Body { get; }
}

public static Expression<Func<T, bool>> BuildFilter<T>(string key, string value)
{
if (string.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key));
if (string.IsNullOrEmpty(value)) throw new ArgumentNullException(nameof(value));

var p = Expression.Parameter(typeof(T), "p");
Expression body = p;

var stack = new Stack<Node>();

foreach (string member in key.Split('.'))
{
if (body.Type.IsGenericType)
{
var genericArgs = body.Type.GetGenericArguments();
if (genericArgs.Length == 1 && typeof(IEnumerable<>)
.MakeGenericType(genericArgs)
.IsAssignableFrom(body.Type))
{
stack.Push(new Node(p, body));
p = Expression.Parameter(genericArgs[0], "s" + stack.Count);
body = p;
}
}

body = Expression.PropertyOrField(body, member);
}

var constant = Expression.Constant(value, typeof(string));
body = Expression.Call(body, nameof(string.Contains), null, constant);

while (stack.Count != 0)
{
var childFilter = Expression.Lambda(body, p);
var parent = stack.Pop();

body = Expression.Call(typeof(Enumerable),
nameof(Enumerable.Any),
new[] { p.Type },
parent.Body,
childFilter);

p = parent.Parameter;
}

return Expression.Lambda<Func<T, bool>>(body, p);
}
}

Usage:

IQueryable<Product> productsQuery = _context.Products
.Include(p => p.ProductEANs);

foreach (KeyValuePair<string, string> searchField in searchFields)
{
if (!string.IsNullOrWhiteSpace(searchField.Value))
{
var filter = FilterExtensions.BuildFilter<Product>(searchField.Key, searchField.Value);
productsQuery = productsQuery.Where(filter);
}
}

How to build expression tree for ContainsT

Basically you need to process the Method, Object(expression that represents the instance for instance method calls or null for static method calls) and Arguments(a collection of expressions that represent arguments of the called method) properties of the MethodCallExpression class.

Specifically for Contains, you need to avoid (or process differently if needed) the string.Contains method, and also handle static methods like Enumerable.Contains as well as instance methods like ICollection<T>.Contains, List<T>.Contains etc. In order to get the list values (when possible), you have to find some sort of a constant expression.

Here is a sample:

private bool ParseContainsExpression(MethodCallExpression expression)
{
// The method must be called Contains and must return bool
if (expression.Method.Name != "Contains" || expression.Method.ReturnType != typeof(bool)) return false;
var list = expression.Object;
Expression operand;
if (list == null)
{
// Static method
// Must be Enumerable.Contains(source, item)
if (expression.Method.DeclaringType != typeof(Enumerable) || expression.Arguments.Count != 2) return false;
list = expression.Arguments[0];
operand = expression.Arguments[1];
}
else
{
// Instance method
// Exclude string.Contains
if (list.Type == typeof(string)) return false;
// Must have a single argument
if (expression.Arguments.Count != 1) return false;
operand = expression.Arguments[0];
// The list must be IEnumerable<operand.Type>
if (!typeof(IEnumerable<>).MakeGenericType(operand.Type).IsAssignableFrom(list.Type)) return false;
}
// Try getting the list items
object listValue;
if (list.NodeType == ExpressionType.Constant)
// from constant value
listValue = ((ConstantExpression)list).Value;
else
{
// from constant value property/field
var listMember = list as MemberExpression;
if (listMember == null) return false;
var listOwner = listMember.Expression as ConstantExpression;
if (listOwner == null) return false;
var listProperty = listMember.Member as PropertyInfo;
listValue = listProperty != null ? listProperty.GetValue(listOwner.Value) : ((FieldInfo)listMember.Member).GetValue(listOwner.Value);
}
var listItems = listValue as System.Collections.IEnumerable;
if (listItems == null) return false;

// Do whatever you like with listItems

return true;
}

How can I create an Expression tree that compares properties in a child object?

You just need to get the property of the returned property value:

var child = Expression.Property(parameter, "Child");
var jobNumber = Expression.Property(child, propertyName);

var lambda =
Expression.Lambda<Func<ParentObject, bool>>(
comparisonMethod(jobNumber, Expression.Constant(propertyValue)),
parameter);

IQueryableT.Contains Expression Tree with Expression parameter

Try reusing parameter from selector expression (i.e. exp) instead of creating a new one for resulting expression :

Expression<Func<T, bool>> lambda = 
Expression.Lambda<Func<T, bool>>(a, exp.Parameters[0]);


Related Topics



Leave a reply



Submit