Ekspresja.GreaterThan fails, if one operand is nullable type, other is non-nullable

Tworzę jakiś dynamiczny linq i mam problemy z następującym wyjątkiem:

Operator binarny GreaterThanOrEqual nie jest zdefiniowana dla typów "System.Nullable " 1 [System.DateTime]" i ' System.DateTime "

Rozumiem dlaczego, ponieważ mój typ pola jest nullable i mi przechodzi w DateTime.W zasadzie teraz.

Więc próbując rozwiązać ten problem próbowałem

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

Ale wynikowy typ jest obiektem Nie nullable i dlatego nadal daje mnie powyższy wyjątek.

Jakieś sugestie?!

Update: dla lepszego wyjaśnienia teraz zmienna staje się typem Nie-nullable, gdy jest ustawiona, a nie pozostaje jako nullable DateTime, więc dopasowanie rzuca wyjątek

Aktualizacja: rzeczywisty kod można zobaczyć w projekcie CodePlex:

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

Linia jest ~145

fExp = Expression.GreaterThanOrEqual(fExpLeft, fExpRight);
Author: Vaccano, 2010-01-18

5 answers

Problem polega na tym, że biblioteka wyrażeń rzuca wyjątek, gdy podano dwa argumenty o niedopasowanej nullability. Oto prosty repro:

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

Nie jest dla mnie jasne, czy jest to błąd, czy nie; reguły W C# wymagają, aby w tym scenariuszu operand nie-nullable był konwertowany do nullable, a porównywana jest forma lifted-to-nullable. jednak, biblioteka drzewa wyrażeń jest nie wymaga przestrzegania zasad C # ponieważ oczywiście biblioteka drzewa wyrażeń może być używana do reprezentowania wyrażeń C#, wyrażeń Pythona, wyrażeń JScript, wyrażeń VB i tak dalej; nie może być zgodna ze wszystkimi regułami każdego możliwego języka.

Ale niezależnie od tego, wygląda na to, że to może być błąd, więc prześlę go do zespołu drzewa wyrażeń i zobaczę, co powiedzą. W międzyczasie możesz łatwo obejść go, definiując własną metodę pomocniczą, która naprawia operandy. Szybki szkic 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<>);
    }

Zauważ jednak, że to nie sprawdza, czy typy e1 i e2 różnią się tylko pod względem nullability; jeśli przekażesz nullable int i non-nullable double expression, złe rzeczy się zdarzają. zostawiam to jako ćwiczenie, aby zaimplementować lepszą logikę, która sprawdza, czy oba wyrażenia są typu, który różni się tylko nullability.

 52
Author: Eric Lippert,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 20:07:47

Gdzie jest twoje porównanie, Zmień porównanie TAK:

(nullableDT >= DT)

Do

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

Edit:

Zgodnie z Twoim komentarzem, napisz funkcję, która zajmuje 2 obiekty, wewnątrz funkcji sprawdź, czy są to typy null, i sprawdź, czy są null, a następnie porównaj wartości. Ta funkcja będzie prawdopodobnie używać kodu podobnego do ^.

To zaczyna brzmieć, jakbyś miał większy problem. Albo otrzymujesz nieprawidłowe dane, (tj. Twój kod w innym miejscu zwraca dane że nie powinno być, nie problem w kodzie, ale problem w logice,) lub twoje założenie, że możesz porównać te obiekty jest nieprawidłowe. Co po raz kolejny jest błędem logicznym. Myślę, że powinieneś się na chwilę cofnąć. Jeśli dodasz więcej kodu, może będziemy w stanie Ci pomóc.
 2
Author: FallenAvatar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 19:24:03

Znalazłem rozwiązanie, które działa w. Net framework. Tutaj metoda, która jest nadal w toku, ale działa i działa dla LINQ do encji:

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

To, co się tutaj dzieje, to wartość stała wyrażenia jest rzucana do typu predykatu. Dla jasności metoda ta jest wywoływana przez:

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

Ten kod jest oparty na MVC 3, stąd niektóre z ParamsData [""], które jest (Request.Params).

 2
Author: Novak,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-06-07 04:10:05

Nie jestem pewien dokładnie jaki jest Twój kod, ale aby uzyskać nie-nullable wersję Nullable, zadzwoń do jego .Value członka.

 1
Author: John Feminella,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 18:28:53

To zabawne.]}

Próbowałem obu wariantów tego kodu:

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

I

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

I obie pracowały bez błędów.

Więc ponownie przeczytałem twoje pytanie. W rzeczywistości odpowiedź jest nadal na właściwości "Value". Inicjalizacja zmiennej if fine, but if you do:

(now > = DateTime.Teraz) w zapytaniu Linq pojawi się błąd. Powinno być (teraz.Wartość > = DateTime.Teraz)

 0
Author: Wagner Silveira,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-01-18 19:54:12