如果一个操作数是可空类型,则Expression.GreaterThan失败,另一个操作数是非可空的

时间:2010-01-18 18:25:46

标签: c# .net c#-3.0

我正在创建一些动态linq,并且遇到以下异常的问题:

  

二元运算符GreaterThanOrEqual   没有为类型定义   'System.Nullable`1 [System.DateTime的]'   和'System.DateTime'

我明白了,因为我的字段类型可以为空并且我在DateTime.Now中传递了。

因此,在尝试解决此问题时,我尝试了

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

但是结果类型是一个不可为空的对象,因此仍然给我上述异常。

有什么建议吗?!

更新:为了进一步说明, now 变量在设置时变为非可空类型,而不是保留为可空的DateTime,因此匹配会抛出异常

更新:可以在CodePlex项目中看到实际代码:

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

违规行是~145

fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight);

5 个答案:

答案 0 :(得分:55)

这里的问题是,当给出两个不匹配的可为空性的参数时,表达式库会引发异常。这是一个简单的复制品:

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

我不清楚这是不是一个错误; C#的规则要求在这种情况下,非可空操作数转换为可空,并使用提升到可空的比较形式。 但是,表达式树库不需要遵循C#的规则,因为表达式树库当然可以用来表示C#表达式,Python表达式,JScript表达式, VB表达式等;它不可能遵循所有可能语言的所有规则。

但无论如何,这看起来可能是一个bug,所以我会将它提交给表达式树队,看看他们说了什么。同时,您可以通过定义自己的辅助方法来轻松解决操作,修复操作数。快速草图将是:

    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<>);
    }

但是,请注意,这并不会检查e1和e2的类型是否仅在可空性方面有所不同;如果你传入一个可以为空的int和一个不可为空的双重表达式,就会发生不好的事情。我把它留作练习来实现更好的逻辑,检查这两个表达式是否只有可空性的类型。< / p>

答案 1 :(得分:2)

我不确定你的代码究竟是什么,但要获得Nullable的非可空版本,请调用其.Value成员。

答案 2 :(得分:2)

无论你的比较是什么,改变这样的比较:

(nullableDT >= DT)

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

编辑:

根据你的评论,编写一个带有2个对象的函数,在函数内检查它们是否为可空类型,并检查是否为null,然后比较值。此函数可能使用类似于^。

的代码

尽管如此,这开始听起来像你有一个更大的潜在问题。要么你得到的数据不正确,(你的代码在其他地方返回的数据不应该是,代码中不是问题,而是逻辑上的问题),或者你可以比较这些对象的假设是无效的。再一次,这是一个逻辑错误。

我想你可能需要退一步这一分钟。如果您发布更多代码,我们可能会为您提供更多帮助。

答案 3 :(得分:2)

我找到了一个在.Net框架内运行的解决方案。这里有一个方法仍在进行中但它确实有效,并且适用于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;
    }

这里发生的是表达式常量值被强制转换为谓词类型。为清楚起见,此方法称为:

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();

此代码基于MVC 3,因此一些ParamsData [“”]即(Request.Params)。

答案 4 :(得分:0)

这很有趣,

我已尝试过此代码的两种变体:

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

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

并且他们都没有错误地工作。

然后我重新阅读你的问题。实际上答案仍然是“价值”属性。如果正常则初始化变量,但如果你这样做:

Linq查询中的

(现在&gt; = DateTime.Now)您将收到错误消息。它应该是(now.Value&gt; = DateTime.Now)