Expression.GreaterThan завершается ошибкой, если один операнд имеет тип NULL, а другой не имеет значения NULL. - PullRequest
28 голосов
/ 18 января 2010

Я создаю динамический 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 ]

55 голосов
/ 18 января 2010

Проблема здесь в том, что библиотека выражений выдает исключение, если дано два аргумента с несовпадающей обнуляемостью.Вот простое повторение:

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и так далее;он не может следовать всем правилам всех возможных языков.

Но независимо от этого, похоже, что это может быть ошибкой, поэтому я отправлю его в команду дерева выражений и посмотрю, что они скажут.В то же время вы можете легко обойти это, определив свой собственный вспомогательный метод, который исправляет операнды.Быстрый набросок будет выглядеть следующим образом:

    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 и ненулевое двойное выражение, произойдут плохие вещи. Я оставляю это в качестве упражнения для реализации лучшей логики, которая проверяет, имеют ли два выражения тип, отличающийся только обнуляемостью.

2 голосов
/ 07 июня 2011

Я нашел решение, которое работает в рамках .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).

2 голосов
/ 18 января 2010

где бы вы ни находились, измените сравнение следующим образом:

(nullableDT >= DT)

Для

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

Edit:

Согласно вашему комментарию, напишите функцию, которая принимает 2 объекта, внутри функции проверьте, являются ли они обнуляемыми типами, и проверьте на нулевое значение, затем сравните значения. Эта функция, вероятно, будет использовать код, подобный ^.

Хотя, это начинает звучать так, как будто у вас есть основная проблема. Либо вы получаете неверные данные (т. Е. Ваш код в другом месте возвращает данные, которых не должно быть, не проблема в коде, но проблема в вашей логике), либо ваше предположение о том, что вы можете сравнивать эти объекты, неверно. Что, опять же, является логической ошибкой.

Я думаю, вам, возможно, придется сделать шаг назад на минуту. Если вы опубликуете больше кода, мы сможем вам помочь.

2 голосов
/ 18 января 2010

Я точно не знаю, какой у вас код, но чтобы получить ненулевую версию Nullable, вызовите его .Value member.

0 голосов
/ 18 января 2010

Это смешно,

Я пробовал оба варианта этого кода:

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

и

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

и оба они работали без ошибок.

Тогда я перечитал ваш вопрос. На самом деле ответ по-прежнему на свойство «Значение». Инициализируйте переменную, если все в порядке, но если вы делаете:

(сейчас> = DateTime.Now) в запросе Linq вы получите ошибку. Должно быть (now.Value> = DateTime.Now)

...