'Expression.GreaterThan fails if one operand is nullable type, other is non-nullable
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);
Solution 1:[1]
I'm not sure exactly what your code is, but to get the non-nullable version of a Nullable, call its .Value member.
Solution 2:[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.
Solution 3:[3]
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).
Solution 4:[4]
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)
Solution 5:[5]
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));
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | John Feminella |
| Solution 2 | |
| Solution 3 | Novak |
| Solution 4 | |
| Solution 5 | JSON |
