35

I am creating some dynamic linq and am having problems with the following exception:

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

I get why, because my field type is nullable and Im passing in DateTime.Now essentially.

So in trying to resolve this issue I've tried

System.Nullable<DateTime> now;
now = DateTime.Now;

But the resulting type is a non-nullable object and hence still giving me the above exception.

Any suggestions?!

Update: For more clarification the now variable becomes a non-nullable type when it is set rather than staying as a nullable DateTime so the match throws an exception

Update: The actual code can be seen in the CodePlex project:

http://webquarters.codeplex.com/SourceControl/changeset/view/36529#574700

The offending line is ~145

fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight);
Vaccano
  • 78,325
  • 149
  • 468
  • 850
Anthony Main
  • 6,039
  • 12
  • 64
  • 89
  • 2
    This sounds like a possible bug in the underlying library. The overload resolution algorithm should take a non-nullable T and a nullable T and choose the lifted-to-nullable version of the operator. Can you post a small, simple, self-contained repro of the problem? I'll also need the version number of the compiler you're using. I can then pass it along to the developers and testers responsible for that feature. If you'd rather email it directly to me, there's a "contact me" link on my blog, blogs.msdn.com/ericlippert. Thanks! – Eric Lippert Jan 18 '10 at 18:34
  • Oh joy :( not really what I wanted to hear, have emailed you a sample – Anthony Main Jan 18 '10 at 19:06
  • Show your query, and mention which provider you're using. – Craig Stuntz Jan 18 '10 at 19:22
  • Thats the problem its dynamic Im building the LINQ on the fly, but essential Im using Linq-to-SQL but its not getting that far anyway – Anthony Main Jan 18 '10 at 19:42
  • 2
    +1 Thanks for this question. I had a similar problem and wasted hours trying to convert one side of an expression to non-nullable when actually you need to convert the other side to nullable. – Stephan Keller May 31 '12 at 13:21
  • btw. converting the non nullable variable to a nullable one and use that, works in dotnet core. – Christian Schmitt Aug 09 '19 at 09:04

6 Answers6

73

The problem here is that the expression library is throwing an exception when given two arguments of mismatched nullability. Here's a simple repro:

Expression<Func<DateTime?>> ex1 = ()=>DateTime.Now;
Expression<Func<DateTime>> ex2 = ()=>DateTime.Now;
var ex3 = Expression.GreaterThan(ex1.Body, ex2.Body);

It is not clear to me whether this is a bug or not; the rules of C# require that in this scenario, the non-nullable operand is converted to nullable, and the lifted-to-nullable form of the comparison is used. However, the expression tree library is not required to follow the rules of C# because of course the expression tree library can be used to represent C# expressions, Python expressions, JScript expressions, VB expressions and so on; it cannot possibly follow all the rules of every possible language.

But regardless, this looks like it might be a bug, so I'll submit it to the expression tree team and see what they say. In the meanwhile, you can easily work around it by defining your own helper method that fixes the operands up. A quick sketch would be:

    static Expression MyGreaterThan(Expression e1, Expression e2)
    {
        if (IsNullableType(e1.Type) && !IsNullableType(e2.Type))
            e2 = Expression.Convert(e2, e1.Type);
        else if (!IsNullableType(e1.Type) && IsNullableType(e2.Type))
            e1 = Expression.Convert(e1, e2.Type);
        return Expression.GreaterThan(e1, e2);
    }
    static bool IsNullableType(Type t)
    {
        return t.IsGenericType && t.GetGenericTypeDefinition() == typeof(Nullable<>);
    }

However, notice that this does not check that e1 and e2's type differ only in nullability; if you pass in a nullable int and a non-nullable double expression, bad things happen. I leave it as an exercise to implement the better logic that checks whether the two expressions are of type that only differs by nullability.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 2
    A [LINQPad](http://www.linqpad.net/) example with some checks added: https://gist.github.com/kappa7194/21c3390130f3e8d54fc2 – Albireo Sep 30 '14 at 09:03
2

I'm not sure exactly what your code is, but to get the non-nullable version of a Nullable, call its .Value member.

John Feminella
  • 303,634
  • 46
  • 339
  • 357
2

where ever your compare is, change the compare like this:

(nullableDT >= DT)

To

(nullableDT != null && nullableDT.Value >= DT)

Edit:

As per you comment, Write a function that takes 2 objects, inside the function check if they are nullable types, and check for null, then compare values. This function will probably use code similar to ^.

Though, this is starting to sound like you have a bigger underlying problem. Either you are getting incorrect data, (ie. your code elsewhere is returning data that it shouldn't be, not a problem in the code, but a problem in your logic,) or your assumption that you can compare these objects is invalid. Which, once again, is a logic error.

I think you might need to take a step back on this one for a minute. If you post more code, we might be able to help you some more.

FallenAvatar
  • 4,079
  • 1
  • 21
  • 24
  • 1
    I cant, as I said this is a dynamic linq query so I never know which side of the equations do or do not consist of nullable members, without some additional reflection – Anthony Main Jan 18 '10 at 19:08
  • yes you are probably right, hence posting here. putting in a method like that will be quite a bit of code in this case, but I will definately have to consider it – Anthony Main Jan 18 '10 at 19:33
2

I found a solution that works within the .Net framework. Here a method that's still in progress but it does work, and works for Linq to Entities:

public PagedViewModel<T> Filter<TValue>(Expression<Func<T, TValue>> predicate, FilterType filterType = FilterType.Equals) {
        var name = (predicate.Body as MemberExpression ?? ((UnaryExpression)predicate.Body).Operand as MemberExpression).Member.Name;            
        var value = Expression.Constant(ParamsData[name].To<TValue>(), typeof (T).GetProperty(name).PropertyType);                        

        // If nothing has been set for filter, skip and don't filter data.
        ViewData[name] = m_QueryInternal.Distinct(predicate.Compile()).ToSelectList(name, name, ParamsData[name]);
        if (string.IsNullOrWhiteSpace(ParamsData[name]))
            return this;

        var nameExpression = Expression.Parameter(typeof(T), name);
        var propertyExpression = Expression.Property(nameExpression, typeof(T).GetProperty(name));

        // Create expression body based on type of filter
        Expression expression;
        MethodInfo method;
        switch(filterType) {
            case FilterType.Like:
                method = typeof(string).GetMethod("Contains", new[] { typeof(string) });
                expression = Expression.Call(propertyExpression, method, value); 
                break;
            case FilterType.EndsWith:
            case FilterType.StartsWith:
                method = typeof(string).GetMethod(filterType.ToString(), new[] { typeof(string) });
                expression = Expression.Call(propertyExpression, method, value);
                break;
            case FilterType.GreaterThan:
                expression = Expression.GreaterThan(propertyExpression, value);                    
                break;
            case FilterType.Equals:
                expression = Expression.Equal(propertyExpression, value);
                break;
            default:
                throw new ArgumentException("Filter Type could not be determined");
        }            

        // Execute the expression against Query.
        var methodCallExpression = Expression.Call(
            typeof (Queryable),
            "Where",
            new[] { Query.ElementType },
            Query.Expression,
            Expression.Lambda<Func<T, bool>>(expression, new[] { nameExpression }));

        // Filter the current Query data.
        Query = Query.Provider.CreateQuery<T>(methodCallExpression);            

        return this;
    }

What happens here is the Expression Constant value is cast to the predicate type. For clarity this method is called by:

var paramsData = new NameValueCollection { { "CreatedOn", DateTime.Today.ToString() } };
        var model = m_data.ToPagedList(new ViewDataDictionary(), paramsData, 1, 10, null, x => x.LastName)
                          .Filters(Criteria<TrainerProfile>.New(x => x.CreatedOn, FilterType.GreaterThan))
                          .Setup();

This code is MVC 3 based, hence some of the ParamsData[""] which is (Request.Params).

Novak
  • 21
  • 1
1

This is pretty easy and doesn't need a long explaination as from what I have seen. Everything beyond this is uneccessary.

Expression.Constant() takes a Type parameter.

So for example, with ints to int?. Instead of Expression.Constant(someInt) use Expression.Constant(someInt, typeof(int?)) This will cast someInt to int? before executing.

Feel free to use generics and/or reflection for your types if need be.

This would be full code to create a GreaterThan expression using generics and reflection. Using this, it doesn't matter if it is nullable or not

var greaterThan = Expression.GreaterThan(
        Expression.Property(Expression.Parameter(typeof(T)), property),
        Expression.Constant(inValue, property.PropertyType));
JSON
  • 1,113
  • 10
  • 24
-1

That is funny,

I've tried both variation of this code:

    System.Nullable<DateTime> now = new System.Nullable<DateTime>();
    now = DateTime.Now;

and

    System.Nullable<DateTime> now;
    now = DateTime.Now;

and both of them worked without errors.

Then I re-read your question. Actually the answer is still on the "Value" property. Initializing the variable if fine, but if you do:

(now >= DateTime.Now) in the Linq query you will get an error. It should be (now.Value >= DateTime.Now)

Wagner Silveira
  • 1,576
  • 11
  • 9