Tuesday, April 12, 2011

Lambda Wrangling For Fun : Part 2 – Filtering Single Columns

 

In the previous post, we developed a way of dynamically generating a lambda based on a property name to pass through to the OrderBy or OrderByDescending LINQ function in order to sort a collection of items by the specified property.

We can do the same thing to dynamically generate a filter predicate to use in several LINQ extension methods that accept predicates.

Anatomy of a Filter

A filter predicate has three parts:

  • A constant value or expression to compare against
  • A comparison operator
  • A target property on the object which should be compared with the constant value

We already know how to express property selectors – we used them here.

Let’s approach the rest of the problem in stages:

Ceteris paribus…

Let’s fix the comparison operator to be the equality operator  - “ == “.

Then we can write a naive lambda generator thus:

public static Func<TSource, bool> GetPropertyFilterLambda<TSource>(this TSource _this, string propertyName, object filterValue)
{
// Given that propertyName = "Foo" and filterValue = blah
// we want the lambda expression * _ => _.Foo == blah *

// This gives us the * _ => * part
var parameterExpression = Expression.Parameter(typeof (TSource), "_");

// This gives us the * .Foo * part - see the interfaces and inheritance blog post
var propertyInfo = typeof(TSource).GetProperty(propertyName, true);

// This gives us the * _.Foo * part
var accessorExpression = Expression.Property(parameterExpression, propertyInfo);

// This gives us the * blah * part
var valueExpression = Expression.Convert(Expression.Constant(filterValue), propertyInfo.PropertyType);

// This gives us the * _.Foo == blah * part
var equalityComparisionExpression = Expression.Equal(accessorExpression, valueExpression);

// This gives us the * _ => _.Foo == blah * part
return Expression.Lambda<Func<TSource, bool>>(equalityComparisionExpression, parameterExpression).Compile();
}

We need to incorporate the type conversion into the constant expression because it’s very likely that the constant value is a string (passed in from an ASP.NET MVC Controller, for example) while the property is an integer property.


You’re not my Type!



By happy coincidence, the C# language does the right thing for the equality operator on strings and compares values even though strings are a reference type. But other reference types will pose a problem since the equality operator only compares references.


So let’s strongly type the constant value:

public static Func<TSource, bool> GetPropertyFilterLambda<TSource, TValue>(this TSource _this, string propertyName, TValue filterValue)
{
// Given that propertyName = "Foo" and filterValue = blah
// we want the lambda expression * _ => _.Foo == blah *

// This gives us the * _ => * part
var parameterExpression = Expression.Parameter(typeof (TSource), "_");

// This gives us the * .Foo * part - see the interfaces and inheritance blog post
var propertyInfo = typeof (TSource).GetProperty(propertyName, true);

// ensure the property is of the same type as the value
if (!propertyInfo.PropertyType.IsAssignableFrom(typeof (TValue)))
{
throw new MemberAccessException(String.Format("Property {0}.{1} is not of type {2}",
typeof (TSource).Name,
propertyInfo.Name,
typeof (TValue).Name));
}

// This gives us the * _.Foo * part
var accessorExpression = Expression.Property(parameterExpression, propertyInfo);

// This gives us the * blah * part
var valueExpression = Expression.Convert(Expression.Constant(filterValue), propertyInfo.PropertyType);

// This gives us the * _.Foo == blah * part
var equalityComparisionExpression = Expression.Equal(accessorExpression, valueExpression);

// This gives us the * _ => _.Foo == blah * part
return Expression.Lambda<Func<TSource, bool>>(equalityComparisionExpression, parameterExpression).Compile();
}

So far so good.


sed ceteris non paribus…



But there’s no way to specify a different operation than simple equality comparison with this. There is not even a way to do a deep equality comparison on non-string reference objects.


What we really need is a way to provide our own comparison functions, which will give us the flexibility to compare two objects and decide if they satisfy the predicate.


Here is such a function:

public static Expression<Func<TSource, bool>> GetPropertyFilterLambda<TSource, TValue>(this TSource _this,
string propertyName,
TValue filterValue,
Func<TValue, TValue, bool> compareFunc)
{
// Given that propertyName = "Foo" and filterValue = blah
// we want the lambda expression * _ => compareFunc(_.Foo, blah) *

// This gives us the * _ => * part
var parameterExpression = Expression.Parameter(typeof (TSource), "_");

// This gives us the * .Foo * part - see the interfaces and inheritance blog post
var propertyInfo = typeof (TSource).GetProperty(propertyName, true);

// ensure the property is of the same type as the value
if (!propertyInfo.PropertyType.IsAssignableFrom(typeof (TValue)))
{
throw new MemberAccessException(String.Format("Property {0}.{1} is not of type {2}",
typeof (TSource).Name,
propertyInfo.Name,
typeof (TValue).Name));
}

// This gives us the * _.Foo * part
var accessorExpression = Expression.Property(parameterExpression, propertyInfo);

// This gives us the * blah * part
var valueExpression = Expression.Convert(Expression.Constant(filterValue), propertyInfo.PropertyType);

// This gives us the * compareFunc(_.Foo, blah) * part
var compareExpression = Expression.Call(null, compareFunc.Method, accessorExpression, valueExpression);

// This gives us the * _ => compareFunc(_.Foo, blah) * part
return Expression.Lambda<Func<TSource, bool>>(compareExpression, parameterExpression);
}


which can be invoked thus:

<…>.Where(default(IIngredient)
.GetPropertyFilterLambda(
"Name",
"Cheese",
(_l, _r) => _l.Contains(_r)));


so we aren’t constrained to an equality operation any more!


We raise the stakes one more level and curry the constant into the comparison lambda thus:

public static Expression<Func<TSource, bool>> GetPropertyFilterLambda<TSource, TValue>(this TSource _this,
string propertyName,
Func<TValue, bool> predicate)
{
// Given that propertyName = "Foo"
// we want the lambda expression * _ => predicate(_.Foo) *

// This gives us the * _ => * part
var parameterExpression = Expression.Parameter(typeof(TSource), "_");

// This gives us the * .Foo * part - see the interfaces and inheritance blog post
var propertyInfo = typeof(TSource).GetProperty(propertyName, true);

// ensure the property is of the same type as the value
if (!propertyInfo.PropertyType.IsAssignableFrom(typeof(TValue)))
{
throw new MemberAccessException(String.Format("Property {0}.{1} is not of type {2}",
typeof(TSource).Name,
propertyInfo.Name,
typeof(TValue).Name));
}

// This gives us the * _.Foo * part
var accessorExpression = Expression.Property(parameterExpression, propertyInfo);

// This gives us the * predicate(_.Foo) * part
var compareExpression = Expression.Call(null, predicate.Method, accessorExpression);

// This gives us the * _ => predicate(_.Foo) * part
return Expression.Lambda<Func<TSource, bool>>(compareExpression, parameterExpression);
}


which can be invoked like this:

<…>.Where(default(IIngredient)
.GetPropertyFilterLambda(
"Name",
(string _l) => _l.Contains("Cheese"));


Neat-o!

No comments:

Post a Comment