February 09, 2011 2:42 PM by Daniel Chambers
In my first post on dynamic queries using expression trees, I explained how one could construct an expression tree manually that would take an array (for example, {10,12,14}) and turn it into a query like this:
tag => tag.ID == 10 || tag.ID == 12 || tag.ID == 14
A reader recently wrote to me and asked whether one could form a similar query that instead queried across multiple properties, like this:
tag => tag.ID == 10 || tag.ID == 12 || tag.Name == "C#" || tag.Name == "Expression Trees"
The short answer is “yes, you can”, however the long answer is “yes, but it takes a bit of doing”! In this blog post, I’ll detail how to write a utility method that allows you to create these sorts of queries for any number of properties on an object. (If you haven’t read the previous post, please read it now.)
Previously we had defined a method with this signature (I’ve renamed the “convertBetweenTypes” parameter to “memberAccessExpression”; the original name sucked, frankly; this is a clearer name):
public static Expression<Func<TValue, bool>> BuildOrExpressionTree<TValue, TCompareAgainst>( IEnumerable<TCompareAgainst> wantedItems, Expression<Func<TValue, TCompareAgainst>> memberAccessExpression)
Now that we want to query multiple properties, we’ll need to change this signature to something that allows you to pass multiple wantedItems lists and a memberAccessExpression for each of them.
public static Expression<Func<TValue, bool>> BuildOrExpressionTree<TValue>( IEnumerable<Tuple<IEnumerable<object>, LambdaExpression>> wantedItemCollectionsAndMemberAccessExpressions)
Eeek! That’s a pretty massive new single parameter. What we’re now doing is passing in multiple Tuples (if you’re using .NET 3.5, make your own Tuple class), where the first component is the list of wanted items, and the second component is the member access expression. You’ll notice that a lot of the generic types have gone out the window and we’re passing IEnumerables of object and LambdaExpressions around; this is a price we’ll have to pay for having a more flexible method.
How would you call this monster method? Like this:
var wantedItemsAndMemberExprs = new List<Tuple<IEnumerable<object>, LambdaExpression>> { new Tuple<IEnumerable<object>, LambdaExpression>(new object[] {10, 12}, (Expression<Func<Tag, int>>)(t => t.ID)), new Tuple<IEnumerable<object>, LambdaExpression>(new[] {"C#", "Expression Trees"}, (Expression<Func<Tag, string>>)(t => t.Name)), }; Expression<Func<Tag, bool>> whereExpr = BuildOrExpressionTree<Tag>(wantedItemsAndMemberExprs);
Note having to explicitly specify “object[]” for the array of IDs; this is because, although you can now assign IEnumerable<ChildClass> to IEnumerable<ParentClass> (covariance) in C# 4, that only works for reference types. Value types are invariant, so you need to explicitly force int to be boxed as a reference type. Note also having to explicitly cast the member access lambda expressions; this is because the C# compiler won’t generate an expression tree for you unless it knows you explicitly want an Expression<T>; casting forces it to understand that you want an expression tree here and not just some anonymous delegate.
So how is the new BuildOrExpressionTree method implemented? Like this:
public static Expression<Func<TValue, bool>> BuildOrExpressionTree<TValue>( IEnumerable<Tuple<IEnumerable<object>, LambdaExpression>> wantedItemCollectionsAndMemberAccessExpressions) { ParameterExpression inputParam = null; Expression binaryExpressionTree = null; if (wantedItemCollectionsAndMemberAccessExpressions.Any() == false) throw new ArgumentException("wantedItemCollectionsAndMemberAccessExpressions may not be empty", "wantedItemCollectionsAndMemberAccessExpressions"); foreach (Tuple<IEnumerable<object>, LambdaExpression> tuple in wantedItemCollectionsAndMemberAccessExpressions) { IEnumerable<object> wantedItems = tuple.Item1; LambdaExpression memberAccessExpr = tuple.Item2; if (inputParam == null) inputParam = memberAccessExpr.Parameters[0]; else memberAccessExpr = new ParameterExpressionRewriter(memberAccessExpr.Parameters[0], inputParam).VisitAndConvert(memberAccessExpr, "BuildOrExpressionTree"); BuildBinaryOrTree(wantedItems, memberAccessExpr.Body, ref binaryExpressionTree); } return Expression.Lambda<Func<TValue, bool>>(binaryExpressionTree, new[] { inputParam }); }
As I explain this method, you may want to keep an eye on the expression tree diagram from the previous post, so you can visualise the expression tree structure easily. The method loops through each tuple that contains a wantedItems collection and a memberAccessExpression, and progressively builds an expression tree from all the items in all the collections. You’ll notice within the foreach loop that the ParameterExpression from the first memberAccessExpression is kept and used to “rewrite” subsequent memberAccessExpressions. Each memberAccessExpr is a separate expression tree, each with its own ParameterExpression, but since we’re now using multiple of them and combining them all into a single expression tree that still takes a single parameter, we need to ensure that those expressions use a common ParameterExpression. We do this by implementing an ExpressionVisitor that rewrites the expression and replaces the ParameterExpression it uses with the one we want it to use.
public class ParameterExpressionRewriter : ExpressionVisitor { private ParameterExpression _OldExpr; private ParameterExpression _NewExpr; public ParameterExpressionRewriter(ParameterExpression oldExpr, ParameterExpression newExpr) { _OldExpr = oldExpr; _NewExpr = newExpr; } protected override Expression VisitParameter(ParameterExpression node) { if (node == _OldExpr) return _NewExpr; else return base.VisitParameter(node); } }
The ExpressionVisitor uses the visitor pattern, so it recurses through an expression tree and calls different methods on the class depending on what node type it encounters and allows you to rewrite the tree by returning something different from the method. In the VisitParameter method above, we’re simply returning the new ParameterExpression when we encounter the old ParameterExpression in the tree. Note that ExpressionVisitor is new to .NET 4, so if you’re stuck in 3.5-land use this similar implementation instead. (For more information on modifying expression trees, see this MSDN page.)
Going back to the BuildOrExpressionTree method, we see the next thing we do is call the BuildBinaryOrTree method. Note that this method is slightly different to the implementation in the previous post, as I’ve changed it to be a faster iterative algorithm (rather than recursive) and it no longer is generic. The method should look pretty familiar:
private static void BuildBinaryOrTree( IEnumerable<object> items, Expression memberAccessExpr, ref Expression expression) { foreach (object item in items) { ConstantExpression constant = Expression.Constant(item, item.GetType()); BinaryExpression comparison = Expression.Equal(memberAccessExpr, constant); if (expression == null) expression = comparison; else expression = Expression.OrElse(expression, comparison); } }
As you can see, for each iteration in the main BuildBinaryOrExpressionTree, the existing binary OR tree is fed back into the BuildBinaryOrTree method and extended with more nodes, except each different call uses items from a different collection and a different memberAccessExpression to extend the tree. Once all Tuples have been processed, the binary OR tree is bound together with its ParameterExpression and turned into the LambdaExpression we need for use in an IQueryable Where method. We can use it like this:
Expression<Func<Tag, bool>> whereExpr = BuildOrExpressionTree<Tag>(wantedItemsAndMemberExprs); IQueryable<Tag> tagQuery = tags.Where(whereExpr);
In conclusion, we see that wanting to query those additional properties required us to add a whole bunch more code in order to make it work. However, in the end, it does work and works quite well, although admittedly the method is a little awkward to use. This could be cleaned up by wrapping it in a “builder”-style class that simplifies the API a little, but I’ll leave that as an exercise to the reader.
Submit Comment | Comments RSS Feed
Adalton
September 08, 2011 5:46 PM
This example is fantastic. Congratulations!!!
I have a doubt. In this example, you put only Equal condition.
In my scenario, I'd like put the LIKE Command. For example, tag.Name Like "abc".
Again, congratulations!!!