6

I'm trying to build a dynamic LINQ query, in Entity Framework, from a user-provided set of collection criteria. Eventually, this will include more complex behaviors, but currently I just have a list of field names and values, and I want to return all the records in which the field names have those values.

My basic structure is this:

public IEnumerable<ThingViewModel> getMythings(SelectionCriteria selectionCriteria)
{
    var predicate = constructPredicate<Thing>(selectionCriteria);
    var things = this.dbContext.Things.Where(predicate).ToList();
    return Mapper.Map<List<Thing>, List<ThingViewModel>>(things);
}

Where all the interesting work is in constructPredicate():

private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    // using Pete Montgomery's PredicateBuilder:
    // http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/

    var predicate = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        // Accessing foreach values in closures can result in unexpected results.
        // http://stackoverflow.com/questions/14907987/access-to-foreach-variable-in-closure
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);

        var lambda = buildCompareLambda<T>(property, value, parameter);

        predicate = predicate.And(lambda);
    }

    return predicate;
}

All of that seems like a perfectly reasonable structure, but it's in the buildCompareLambda() that I'm having difficulties. I'm not seeing a way to do this in a generic way, I'm having to create different methods for different types. I'd started by handling strings, and that was simple enough. I next tried to handle integers, but it turns out that the integer fields in the database are nullable, which introduced a whole new class of complexity.

My buildCompareLambda(), so far:

private static Expression<Func<T, bool>> buildCompareLambda<T>(
    MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    Expression<Func<T, bool>> lambda = null;
    if (property.Type == typeof (string))
        lambda = buildStringCompareLambda<T>(property, value, parameter);
    else if (property.Type.IsGenericType && Nullable.GetUnderlyingType(property.Type) != null)
        lambda = buildNullableCompareLambda<T>(property, value, parameter);

    if (lambda == null)
        throw new Exception(String.Format("SelectrionCriteria cannot handle property type '{0}'", property.Type.Name));

    return lambda;
}

As I said, buildStringCompareLambda is simple enough:

private static Expression<Func<T, bool>> buildStringCompareLambda<T>(
    MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    var equalsMethod = typeof (string).GetMethod("Equals", 
            new[] {typeof (string), typeof (string)});
    var comparison = Expression.Call(equalsMethod, property, value);
    return Expression.Lambda<Func<T, bool>>(comparison, parameter);
}

But buildNullableCompareLambda() is getting ugly:

private static Expression<Func<T, bool>> buildNullableCompareLambda<T>(MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    var underlyingType = Nullable.GetUnderlyingType(property.Type);

    if (underlyingType == typeof (int) || underlyingType == typeof (Int16) || underlyingType == typeof (Int32) ||
        underlyingType == typeof (Int64) || underlyingType == typeof (UInt16) || underlyingType == typeof (UInt32) ||
        underlyingType == typeof (UInt64))
    {
        var equalsMethod = underlyingType.GetMethod("Equals", new[] {underlyingType});

        var left = Expression.Convert(property, underlyingType);
        var right = Expression.Convert(value, underlyingType);

        var comparison = Expression.Call(left, equalsMethod, new Expression[] {right});

        return Expression.Lambda<Func<T, bool>>(comparison, parameter);
    }

    return null;
}

It's my intent to add support for more types, in buildNullableCompareLambda(), and to move the handling of each type into a function, so that the same code can be called from buildCompareLambda() and from buildNullableCompareLambda(). But that's for the future - currently I'm stuck on comparing ints. Currently, I'm converting both the property and the value to the underlying type, since I don't want to have separate functions for each integer type, and I don't want the user to have to care whether the EF models a field into an Int16 or an Int32. And that's working, for non-null fields.

I've been browsing around SO, and finding some answers, which is how I've gotten as far as I have, but none of the answers I've seen on handling nullable types really work for me, because they don't really handle nulls.

In my case, if the user passes me a selection criteria with an item that is supposed to equal null, I would want to return the records for which that field are null, and this bit about converting both sides to a base type doesn't seem to work. I'm getting an "Object reference not set to an instance of an object" exception.

In SQL, what I'd want is a "WHERE field IS NULL", if the value is null, or "WHERE field = 'value'", if it is not. And I'm not seeing how to build that kind of alternative into an expression tree.

Any ideas?

Added: It has been suggested that I use Expression.Equal().

With that, my loop becomes:

private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    var predicate = PredicateBuilderEx.True<T>();
    var foo = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);

        var comparison = Expression.Equal(property, value);
        var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

        predicate = PredicateBuilderEx.And(predicate, lambda);
    }
    return predicate;
}

And that doesn't work. I get an exception:

The binary operator Equal is not defined for the types 'System.Nullable`1[System.Int16]' and 'System.Int16'.

BenG
  • 14,826
  • 5
  • 45
  • 60
Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • `Expression.Equals` already handles lifting over nullable types so you should use that instead of trying to do it manually with the `Equals` method. – Lee May 28 '14 at 19:50
  • Expression.Equal() returns a BinaryExpression. PredicateBuilder expects Expression>. Is there a way to convert? – Jeff Dege May 28 '14 at 20:17

2 Answers2

12

As is quite often the case, folks here may not quite come up with the answer, but they get most of the way, and close enough that I can work out the rest.

Expression.Equal requires both parameters to be of the same type. If one is nullable, they both need to be nullable. But that's not that hard to deal with:

private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    var predicate = PredicateBuilderEx.True<T>();
    var foo = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);
        var converted = Expression.Convert(value, property.Type);

        var comparison = Expression.Equal(property, converted);
        var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

        predicate = PredicateBuilderEx.And(predicate, lambda);
    }

    return predicate;
}

Thanks, all.

Jeff Dege
  • 11,190
  • 22
  • 96
  • 165
  • You are probably skipping the type checks here to avoid bloat but don't forget to actually test that T can indeed be converted to the property type. You basically have to check if the types only differ in nullability which is quite simple. – Farhad Alizadeh Noori May 28 '14 at 21:03
  • If it can't be converted, it'll throw an InvalidOperation exception. I.e: "No coercion operator is defined between types 'System.String' and 'System.Nullable`1[System.Int16]'". Since this is in an API that will be used by other programmers, not by users directly, that's all I need. – Jeff Dege May 28 '14 at 22:07
1

As Lee states in his comment, you don't need to implement buildNullableCompareLambda<T> for each type. There is already a method that checks the types of the left and right expressions and calls the Equals method on them if they are a user-defined type, and does the lifting and proper comparison if they are nullable types. See here.

Your lambda is basically:

var property = Expression.Property(parameter, fieldName);
var value = Expression.Constant(fieldValue);
var lambda = Expression.Equal(property, value);

Edit:

It seems to me that this is a bug. Eric Lippert thinks so(link). The documentation states the scenario where they are both of the same type and what to do then:

If left.Type and right.Type are both non-nullable, the node is not lifted. The type of the node is Boolean. If left.Type and right.Type are both nullable, the node is lifted. The type of the node is Boolean.

It doesn't exactly what would happen if one is nullable and the other isn't. In the same link referenced, Eric gives a workaround.

Community
  • 1
  • 1
Farhad Alizadeh Noori
  • 2,276
  • 17
  • 22
  • Assuming you mean Expression.Equal(), we have the same problem - it returns a BinaryExpression, not an Expression, so I can't compose it. – Jeff Dege May 28 '14 at 20:28