5

I'm using some code (available here on MSDN) to dynamically build LINQ expressions containing multiple OR 'clauses'.

The relevant code is

var equals = values.Select(value => (Expression)Expression.Equal(valueSelector.Body, Expression.Constant(value, typeof(TValue))));

var body = equals.Aggregate<Expression>((accumulate, equal) => Expression.Or(accumulate, equal));

This generates a LINQ expression that looks something like this:

(((((ID = 5) OR (ID = 4)) OR (ID = 3)) OR (ID = 2)) OR (ID = 1))

I'm hitting the recursion limit (100) when using this expression, so I'd like to generate an expression that looks like this:

(ID = 5) OR (ID = 4) OR (ID = 3) OR (ID = 2) OR (ID = 1)

How would I modify the expression building code to do this?

Glorfindel
  • 21,988
  • 13
  • 81
  • 109
Ian Gregory
  • 5,770
  • 1
  • 29
  • 42

2 Answers2

6

You need to modify the generation so that it builds a ballanced tree instead of a sequence of ORs where the left sub-tree is a single expression and the right sub-tree contains all remaining elements. Graphically:

 Your code               Better
 ---------              --------
    OR                     OR
 #1    OR              OR      OR
     #2  OR          #1  #2  #3  #4
       #3  #4

As you can see, even in this simple case, the better approach is not as deeply (recursively nested). The code to generate the better expression tree can be written as a recursive method in C#:

Expression GenerateTree(List<Expression> exprs, int start, int end) {
  // End of the recursive processing - return single element
  if (start == end) return exprs[start];

  // Split the list between two parts of (roughly the same size)
  var mid = start + (end - start)/2;
  // Process the two parts recursively and join them using OR
  var left = GenerateTree(exprs, start, mid);
  var right = GenerateTree(exprs, mid+1, end);
  return Expression.Or(left, right);
}

// Then call it like this:
var equalsList = equals.ToList();
var body = GenerateTree(equalsList, 0, equalsList.Length);

I didn't try the code, so there may be some minor mistakes, but it should show the idea.

Tomas Petricek
  • 240,744
  • 19
  • 378
  • 553
1

If this is truly LINQ to Objects as per your tags, why are you building expression trees at all? You can use delegates very easily, and they won't have a recursion limit.

However, more to the point: if you just want to see whether an ID is in some particular collection, why aren't you using something like:

var query = from item in source
            where idCollection.Contains(item.Id)
            ...
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Apologies, my tagging was incorrect. I'm using WCF Data Services, which is where the recursion limit is being encountered. – Ian Gregory May 30 '10 at 20:29
  • @Ian: Does WCF Data Services not allow you to use Contains? That would still be the preferred approach IMO... – Jon Skeet May 30 '10 at 21:14
  • It doesn't in .NET 3.5. It can't convert Contains into URI syntax. – Ian Gregory May 30 '10 at 22:20
  • Also with LinqToSql there's a problem with Contains and ANSI strings, so I've needed to use ORs to handle that also. – Giedrius Aug 22 '12 at 12:11
  • @Giedrius: What do you mean by "ANSI strings" exactly? – Jon Skeet Aug 22 '12 at 12:22
  • @JonSkeet varchar type columns in sql database, problem description and solution is here http://stackoverflow.com/questions/1699382/linq-to-sql-nvarchar-problem but we went with rewriting expression tree instead changing dbCommand parameters. – Giedrius Aug 22 '12 at 12:29