How to express All have Any with Expression Trees

c# expression-trees linq

Question

For a given search tags filter, the expected outcome is an expression representing entities with All tags in a given list of tags ids.

A Lambda might express this as:

class Tag 
{
   public long TagId { get; set; }
}

class Taggable 
{
   ICollection<Tag> Tags { get; set; }
}

...

IEnumerable<long> searchTags = new List<long>() { 1, 2, 3 };
Func<Taggable, bool> filter = taggable => searchTags.All(qtag => taggable.Tags.Any(tag => tag.TagId == qtag));

An attempt to represent this as an expression tree fails:

var tagParam = Expression.Parameter(typeof(Tag), "tag");    
var taggableParam = Expression.Parameter(typeof(Taggable), "taggable");
MemberExpression tagsProperty = Expression.Property(taggableParam, "Tags");
ConstantExpression searchTagsConstant = Expression.Constant(searchTags);

var containsCall = Expression.Call(
      typeof(Enumerable), "Contains",
      new[] { typeof(long) },
      searchTagsConstant,
      Expression.Property(tagParam, "TagID")
);

var anyCall = Expression.Call(
     typeof(Enumerable), "Any",
     new[] { typeof(Tag) },
     tagsProperty,
     Expression.Lambda(containsCall, tagParam)
);

// FAILS HERE
var allCall = Expression.Call(
    typeof(Enumerable), "All",
    new[] { typeof(long) },
    searchTagsConstant,
    anyCall
);

No generic method 'All' on type 'System.Linq.Enumerable' is compatible with the supplied type arguments and arguments. No type arguments should be provided if the method is non-generic.

It was thought it would work as the Enumerable.All<TSource, Func<TSource, bool>> should be satisfied by the searchTagsConstant and anyCall?

1
1
6/23/2018 2:55:05 AM

Accepted Answer

t was thought it would work as the Enumerable.All<TSource, Func<TSource, bool>> should be satisfied by the searchTagsConstant and anyCall

Nope. anyCall is not lambda expression (Func<TSource, bool>), only a potential body of such expression.

Let start from your target:

IEnumerable<long> searchTags = new List<long>() { 1, 2, 3 };

Expression<Func<Taggable, bool>> lambda = 
    taggable => searchTags.All(searchTag => taggable.Tags.Any(tag => tag.TagId == searchTag));

The easiest way to learn how to build expression tree is to create a sample expression at compile time and examine the generated code via some decompiler or the expression tree at runtime via debugger.

Anyway, note that the above expression has 3 lambda parameters, while in your attempt you have only 2. Also there is no Contains call, not sure why you've put in there.

Building the above expression can be like this:

var taggable = Expression.Parameter(typeof(Taggable), "taggable");
var tag = Expression.Parameter(typeof(Tag), "tag");
var searchTag = Expression.Parameter(typeof(long), "searchTag");
// tag.TagId == searchTag
var anyCondition = Expression.Equal(
    Expression.Property(tag, "TagId"),
    searchTag);
// taggable.Tags.Any(tag => tag.TagId == searchTag)
var anyCall = Expression.Call(
    typeof(Enumerable), nameof(Enumerable.Any), new[] { typeof(Tag) },
    Expression.Property(taggable, "Tags"), Expression.Lambda(anyCondition, tag));
// searchTags.All(searchTag => taggable.Tags.Any(tag => tag.TagId == searchTag))
var allCall = Expression.Call(
    typeof(Enumerable), nameof(Enumerable.All), new[] { typeof(long) },
    Expression.Constant(searchTags), Expression.Lambda(anyCall, searchTag));
// taggable => searchTags.All(searchTag => taggable.Tags.Any(tag => tag.TagId == searchTag))
var lambda = Expression.Lambda(allCall, taggable);
5
6/23/2018 8:29:36 AM


Related Questions





Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow