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

4

75

I am just getting started with expression trees so I hope this makes sense. I am trying to create an expression tree to represent:

t => t.SomeProperty.Contains("stringValue");

So far I have got:

    private static Expression.Lambda<Func<string, bool>> GetContainsExpression<T>(string propertyName, string propertyValue)
    {
        var parameterExp = Expression.Parameter(typeof(T), "type");
        var propertyExp = Expression.Property(parameter, propertyName);
        var containsMethodExp = Expression.*SomeMemberReferenceFunction*("Contains", propertyExp) //this is where I got lost, obviously :)
        ...
        return Expression.Lambda<Func<string, bool>>(containsMethodExp, parameterExp); //then something like this
    }

I just don't know how to reference the String.Contains() method.

Help appreciated.

Cladophyll answered 10/11, 2008 at 18:11 Comment(0)
M
163

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.

Mehitable answered 10/11, 2008 at 18:20 Comment(7)
What if I want to call does not startWith "substring" ?Metternich
@AhmD Expression.Not around the Expression.Call, and change the GetMethod to StartsWith?Mehitable
If you are using this code to provide an expression to an ORM like linq2db, you need to hoist the propertyValue into a class to have it turned into a SQL parameter. Otherwise you might have a SQL-injection vulnerability if the user is supplying the value from the UI. Something like var someValue = Expression.Property(Expression.Constant(new {Value = propertyValue}), "Value");Munroe
@Steve any Expression-based ORM that doesn't correctly either escape or parameterize a ConstantExpression is hopelessly broken. This by itself will not risk a SQL injection vulnerability. If you use hopelessly broken tools, then you're already at risk of ... anything. Do you have an actual example where this applies? This sounds like "FUD".Mehitable
@MarcGravell Found this behaviour in linq2db when connecting to a Firebird database. I've added an issue to their repo to see what the authors think (github.com/linq2db/linq2db/issues/296)Munroe
@Steve but do you have an example where it doesn't actually escape. It can be valid not to use parameters.... if the orm correctly escapes the input. Personally, I'd use a parameter for simplicity, but that is a design choice.Mehitable
@MarcGravell You're right. linq2db does escape the text correctly before including it in the SQL. I was stuck on the thought that parameters were necessary. Many thanks for your comments to correct that! Have closed the linq2db issue I created.Munroe
S
10

To perform a search like:

ef.Entities.Where(entity => arr.Contains(entity.Name)).ToArray();

which the trace string will be:

SELECT .... From Entities ... Where Name In ("abc", "def", "qaz")

I use the method I created below:

ef.Entities.Where(ContainsPredicate<Entity, string>(arr, "Name")).ToArray();

public Expression<Func<TEntity, bool>> ContainsPredicate<TEntity, T>(T[] arr, string fieldname) where TEntity : class {
  ParameterExpression entity = Expression.Parameter(typeof(TEntity), "entity");
  MemberExpression member = Expression.Property(entity, fieldname);

  var containsMethods = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public)
  .Where(m => m.Name == "Contains");
  MethodInfo method = null;
  foreach (var m in containsMethods) {
    if (m.GetParameters().Count() == 2) {
      method = m;
      break;
    }
  }
  method = method.MakeGenericMethod(member.Type);
  var exprContains = Expression.Call(method, new Expression[] { Expression.Constant(arr), member });
  return Expression.Lambda<Func<TEntity, bool>>(exprContains, entity);
}
Socratic answered 13/3, 2012 at 4:12 Comment(0)
H
7

How about this:

Expression<Func<string, string, bool>> expFunc = (name, value) => name.Contains(value);

In the client code:

    bool result = expFunc.Compile()("FooBar", "Foo");   //result true
    result = expFunc.Compile()("FooBar", "Boo");        //result false
Heteropterous answered 15/7, 2009 at 6:34 Comment(0)
R
2

Here is how to create an expression tree of string.Contains.

var method = typeof(Enumerable)
    .GetRuntimeMethods()
    .Single(m => m.Name == nameof(Enumerable.Contains) && m.GetParameters().Length == 2);
var containsMethod = method.MakeGenericMethod(typeof(string));
var doesContain = Expression
.Call(containsMethod, Expression.Constant(criteria.ToArray()),
 Expression.Property(p, "MyParam"));

Actual usage at https://raw.githubusercontent.com/xavierjohn/Its.Cqrs/e44797ef6f47424a1b145d69889bf940b5581eb8/Domain.Sql/CatchupEventFilter.cs

Rickrack answered 7/12, 2017 at 6:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.