Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement LessThan, etc., when building expressions on strings

I have a package in which I'm building expression trees, to use with EntityFramework, via PredicateBuilder:

public Expression<Func<T, bool>> constructaPredicate<T>(ExpressionType operation, string fieldName, Expression value)
{
    var type = typeof(T);
    var parameter = Expression.Parameter(type);
    var member = Expression.PropertyOrField(parameter, fieldName);
    Expression comparison = Expression.MakeBinary(operation, member, value);
    var expression = Expression.Lambda<Func<T, bool>>(comparison, parameter);
    return expression;
}

This works fine, except for when comparing strings with GreaterThan, etc. In that case, I get an exception:

The binary operator GreaterThan is not defined for the types 'System.String' and 'System.String'.

Which is simple enough. Browsing around, I've found only a few references to this issue, and none in the context of what I'm doing.

The problem, of course, is that there is no String.GreaterThan method. The usual answer is to use String.CompareTo(), but I've not figured out how to make that work.

I've been trying to use the overload of Expression.MakeBinary that takes a methodinfo object, but I've not figured it out.

Help?

Added

So, I've tried to special-case String.GreaterThan, etc., and I'm still getting the same error:

Expression comparison = null;

if (value.Type == typeof (string))
{
    if (operation == ExpressionType.GreaterThanOrEqual ||
        operation == ExpressionType.GreaterThan ||
        operation == ExpressionType.LessThanOrEqual ||
        operation == ExpressionType.LessThan)
    {
        var method = value.Type.GetMethod("CompareTo", new[] {typeof (string)});
        var zero = Expression.Constant(0);

        var result = Expression.Call(member, method, converted);

        comparison = Expression.MakeBinary(operation, result, zero);
    }
}

if (comparison == null)
    comparison = Expression.MakeBinary(operation, member, converted);

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

But I'm still seeing the exact same exception. Which makes no sense to me, because if I'm doing what I think I'm doing, the only GreaterThan in the expression is comparing Int32 to Int32.

More Added

I found it very odd that I'd see the same error, after having removed GreaterThan from my expression tree.

I've been running this code as a part of a unit test, with Entity Framework connected to an in-memory database called Effort. So I tried it against SqlServer.

My original code, that didn't special-case string, but used GreaterThan for everything, threw the "GreaterThan not defined" exception, when run against SqlServer, and when run against Effort.

My modified code, that special-cased string, worked just fine against SqlServer, but threw the "GreaterThan not defined" exception when run against Effort.

It seems that when Effort sees a CompareTo() in an expression tree, it converts it to a GreaterThan, and that results in our familiar exception.

Yet More Added

In continuing to explore the issue, I've determined that there is a bug in Effort, that can be revealed with a very much simpler example:

var foos = myDbContext.Foos.Where(f => f.fooid.CompareTo("Z") > 0).ToList();

This works fine, when myDbContext is connected to a SqlServer database, it throws our favorite exception when connected to an Effort database. I've filed a bug report on the Effort discussion forum.

For those who are reading this, my second attempt, in my first "Added" section, above, is the correct solution. It works against SqlServer, and that it doesn't against Effort is due to a bug in Effort.

Addendum

The question was asked, what does "converted" refer to, in the above.

In truth, I hardly remember.

What's happening in my code, is that I have a expression tree that I'm applying these comparisons to. I'm using Expression.Convert() to convert this to the underlying type.

I'm not sure the complete method will make much sense, absent the rest of the class, but here it is:

public Expression<Func<T, bool>> constructSinglePredicate<T>(object context)
{
    var type = typeof(T);
    var parameter = Expression.Parameter(type);
    var member = this.getMember<T>(type, parameter);
    var value = this.constructConstantExpression<T>(this.rightHandSide, context);
    ExpressionType operation;
    if (!operationMap.TryGetValue(this.selectionComparison, out operation))
        throw new ArgumentOutOfRangeException("selectionComparison", this.selectionComparison, "Invalid filter operation");

    try
    {
        var converted = (value.Type != member.Type)
            ? (Expression)Expression.Convert(value, member.Type)
            : (Expression)value;

        Expression comparison = null;

        if (value.Type == typeof(string))
        {
            if (operation == ExpressionType.GreaterThanOrEqual ||
                operation == ExpressionType.GreaterThan ||
                operation == ExpressionType.LessThanOrEqual ||
                operation == ExpressionType.LessThan)
            {
                MethodInfo method = value.Type.GetMethod("CompareTo", new[] { typeof(string) });
                var zero = Expression.Constant(0);
                var result = Expression.Call(member, method, converted);
                comparison = Expression.MakeBinary(operation, result, zero);
            }
        }

        if (comparison == null)
            comparison = Expression.MakeBinary(operation, member, converted);

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

        return lambda;
    }
    catch (Exception)
    {
        throw new InvalidOperationException(
            String.Format("Cannot convert value \"{0}\" of type \"{1}\" to field \"{2}\" of type \"{3}\"", this.rightHandSide,
                value.Type, this.fieldName, member.Type));
    }
}
like image 874
Jeff Dege Avatar asked Jul 17 '14 22:07

Jeff Dege


1 Answers

This works:

Expression comparison = null;

if (value.Type == typeof (string))
{
    if (operation == ExpressionType.GreaterThanOrEqual ||
        operation == ExpressionType.GreaterThan ||
        operation == ExpressionType.LessThanOrEqual ||
        operation == ExpressionType.LessThan)
    {
        var method = value.Type.GetMethod("CompareTo", new[] {typeof (string)});
        var zero = Expression.Constant(0);

        var result = Expression.Call(member, method, converted);

        comparison = Expression.MakeBinary(operation, result, zero);
    }
}

if (comparison == null)
    comparison = Expression.MakeBinary(operation, member, converted);

var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);
like image 171
Jeff Dege Avatar answered Oct 26 '22 05:10

Jeff Dege