Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Building 'flat' rather than 'tree' LINQ expressions

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?

like image 761
Ian Gregory Avatar asked May 30 '10 19:05

Ian Gregory


2 Answers

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.

like image 121
Tomas Petricek Avatar answered Nov 07 '22 00:11

Tomas Petricek


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)
            ...
like image 42
Jon Skeet Avatar answered Nov 06 '22 23:11

Jon Skeet