Friday, July 10, 2009

Combining Lambda Expressions

I'm working on custom fields for a GridView which can include custom filters. The idea is to persist these filters to the data layer, so that when a filter is applied, the query sent to the database includes all the appropriate "where" clauses.

I wanted to have an abstract parent class that deals with most of the tedium involved in extending the DataControlField class, and then only require the child classes to handle the cases specific to their custom filters. The parent class would know how to create the lambda expression required to get a given property off of the object that is being represented in the GridView, while the child class would know how to filter by that property. I was willing to make the parent class a little more messy, as long as it could easily be extended to make whatever kind of filterable columns we wanted. For example, if the child class was filtering based on string data types, I wanted to make the following syntax possible:
protected override IEnumerable<Expression<Func<string, bool>>> GetFilters()
{
if (filterTextBox.Text != null && filterTextBox.Text.Length > 0)
{
yield return s => s.StartsWith(filterTextBox.Text);
}
}
But I ran into a wall trying to get the parent class to build the full filter expression based on this partial filter expression. I needed a method that looked like this:
public Expression<Func<TrackedTime, bool>> mergeFilterExpressions(
Expression<Func<TrackedTime, T>> propGetter,
Expression<Func<T, bool>> filter)

So given one expression that could get the property from the TrackedTime, and another expression that could get a boolean value from the property, I needed to be able to create an Expression that gets a boolean value from the TrackedTime. In LINQ itself, there is of course the ability to Invoke the functions represented by my expressions, like this:
return tt => filter.Invoke(propGetter.Invoke(tt));
However, LINQ to Entities doesn't support the Invoke command. So although this would compile just fine, it would throw a runtime exception when the time came to build the SQL query to send to the database. It was driving me crazy.

After much searching, it was Joe Albahari, creator of the amazing LinqPad (which I use almost daily by the way), who had the solution. On his website, he provides a collection of extension methods called LinqKit, which allowed me to do the following:
public Expression<Func<TrackedTime, bool>> mergeFilterExpressions(
Expression<Func<TrackedTime, T>> propGetter,
Expression<Func<T, bool>> filter)
{
ParameterExpression param = propGetter.Parameters[0];
InvocationExpression getProp = Expression.Invoke(propGetter, param);
InvocationExpression invoke = Expression.Invoke(filter, getProp.Expand());
return Expression.Lambda<Func<TrackedTime, bool>>(invoke.Expand(), param);
}
How 'bout that! By Invoking and then Expanding the Expressions, I can now combine Lambda Expressions any way I want!

1 comment:

  1. For those of us that have just bumped into Expressions, and wondered what they are/what they are good for, could you recommend a good resource?

    ReplyDelete