Dynamic Expression Trees
I have been working on a project that uses the Entity Framework as the persistence store. I am implementing a global search capability that returns results from the various entities that have a string property that start with a given query string. So I started out with writing code for each entity that did something like the following:
One method for Customers:
1: public IQueryable<Customers> Search(string query)
2: {
3: return entities.Customers.Where(x => x.Name.StartsWith(query);
4: }
And another for Products:
1: public IQueryable<Products> Search(string query)
2: {
3: return entities.Products.Where(x => x.ProductName.StartsWith(query);
4: }
Of course, the search logic in my project is a little more complicated than what is shown above, but we are keeping this simple to show the ideas. Now imagine that I am implementing this same method for about 10 entities that I am working with. Some of the issues that come up are code duplication and increased maintenance. If I change a part of the search logic, I have to change it in 10 different places. Wouldn’t it be nice if I could write a generic search method that would work across all of the entity types. I started digging into expression trees and here is the solution that I came up with.
The parameter in the Where methods above is an example of an expression. I am telling the query what I want it to do when the enumeration is run. In the case above, it will select each entity where the property (Name for Customers and ProductName for Products) starts with the string specified in the query.
Using a generic expression builder, we can replace the two (or 10) method calls above with a single method that will return the search results for a given entity. Here is the code for that (I will explain in a minute):
1: public static IQueryable<T> Search<T>(IQueryable<T> entity, string query, Expression<Func<T, string>> inclusion)
2: {
3: // Get the parameter name from the inclusion expression
4: string inclusionString = ((MemberExpression)(inclusion.Body)).Member.Name;
5:
6: // Get the method information for the String.StartsWith() method
7: MethodInfo mi = typeof(String).GetMethod("StartsWith", new Type[] { typeof(String) });
8:
9: // Build the parameter for the expression
10: ParameterExpression X = Expression.Parameter(typeof(T), "x");
11:
12: // Build the member that was specified for the expression
13: MemberExpression field = Expression.PropertyOrField(X, inclusionString);
14:
15: // Call the String.StartsWith() method on the member
16: MethodCallExpression startsWith = Expression.Call(field, mi, Expression.Constant(query));
17:
18: // Build the expression
19: Expression<Func<T, bool>> expression = Expression.Lambda<Func<T, bool>>(startsWith, X);
20:
21: // Perform the search logic and return the results
22: return entity.Where(expression);
23: }
First, this may seem like more code, but remember that we are working with 10 entities here. Also, the logic for the search is now contained in one place, so if we need to make changes we only have to do it in one place.
This can now be called by using the following code, which will use the common method to return the Products and Customers that start with the letter “J”:
1: Search<Customers>(entities.Customers, "J", x => x.Name);
2: Search<Products>(entities.Products, "J", x => x.ProductName);
So what does the Search<T> method do? It builds a dynamic expression based on the parameters provided. We are passing the IQueryable to search, the query string to search for, and the string property of the IQueryable to search on. Using the Expression class, we are then dynamically building an expression that looks like the following (in the case of the Customers entity):
x => x.Name.StartsWith("J");
This is exactly the same expression that we used in the initial methods at the top of this post. You can take a look at the generated expression by inserting the following code on line 20 in the Search<T> method body above:
Console.WriteLine(expression.ToString());
This is my first attempt at really digging into expressions in .Net. I find it very interesting and can think of a lot of uses for dynamic expressions.
Comments
Anonymous
February 26, 2009
Very helpful for a expression trees newbie!Anonymous
September 02, 2009
Thank you very much, exactly what I was looking for!Anonymous
July 20, 2010
Some of the code is truncated, can it be reformated so we can see it all?