Search code examples
c#expression-trees

Convert lambda with any | contains to expression tree


How can the following simple lambda be represented using Expression Tree syntax?

v.Tags.Any(t => searchTags.Contains(t.ID));

Where :

  • searchTags = List of long
  • v = Class with Tags navigational property
  • t = Tag class with ID property

Variations on the following have been tried:

 var anyInfo = typeof(Queryable).GetMethods()
         .Where(m => m.Name == "Any")
         .Single(m => m.GetParameters().Length == 2)
         .MakeGenericMethod(typeof(Tag));


 var toQueryable = typeof(Queryable).GetMethods()
         .Where(m => m.Name == "AsQueryable")
         .Single(m => m.IsGenericMethod)
         .MakeGenericMethod(typeof(Tag));

 var containsInfo = typeof(List<long>).GetMethod("Contains", new Type[] { typeof(long) });

 var list = Expression.Constant(searchTags);
 var mcvalue = Expression.Property(v, "Tags");
 ParameterExpression tagParam = Expression.Parameter(typeof(Tag), "mt");

 var tagID = Expression.Property(tagParam, "ID");
 var st = Expression.Call(list, containsInfo, tagID);
 return Expression.Call(null, anyInfo, Expression.Call(null, toQueryable, mcvalue), st);

Solution

  • Here is the C# equivalent of the result of your return statement:

    v.Tags.AsQueryable().Any(searchTags.Contains(t.ID))
    

    Aside from the redundant AsQueryable() call, the main issue is that neither the result, nor the Any call argument are lambda expressions, but expressions representing a potential lambda expression body. In order to convert them to lambda expressions (and connect with the associated parameter expressions), you need to use one of the Expression.Lambda method overloads.

    So building lambda expression like

    v => v.Tags.Any(t => searchTags.Contains(t.ID));
    

    could be done with something like this:

    static Expression<Func<T, bool>> SampleLambda<T>(List<long> searchTags)
    {
        var v = Expression.Parameter(typeof(T), "v");
    
        var t = Expression.Parameter(typeof(Tag), "t");
    
        var containsCall = Expression.Call(
            typeof(Enumerable), "Contains", new [] { typeof(long) },
            Expression.Constant(searchTags),
            Expression.Property(t, "ID")
        );
    
        var anyCall = Expression.Call(
            typeof(Enumerable), "Any", new [] { typeof(Tag) },
            Expression.Property(v, "Tags"),
            Expression.Lambda(containsCall, t)
        );
    
        return Expression.Lambda<Func<T, bool>>(anyCall, v);
    }