Versuchen Sie, mithilfe von Ausdrucksbaumstrukturen einen Nullable-Typ zu filtern

c#-4.0 expression-trees lambda linq

Frage

Ich habe meine gesamte Test-App unten eingefügt. Es ist ziemlich kompakt, so hoffe ich, dass es kein Problem ist. Sie sollten es einfach ausschneiden und in eine Konsolen-App einfügen und ausführen können.

Ich muss in der Lage sein, nach einer oder mehreren der Eigenschaften der Person-Objekte zu filtern, und ich weiß nicht, welche (s) bis zur Laufzeit. Ich weiß, dass dies überall diskutiert wurde und ich habe Tools wie die PredicateBuilder & Dynamic Linq Library angeschaut und benutze sie auch, aber die Diskussion um sie herum konzentriert sich eher auf Sortierung und Ordnung, und jeder hat sich mit seinen Problemen herumgeschlagen eigene Probleme bei NULL-Typen. Daher dachte ich, dass ich versuchen würde, zumindest einen zusätzlichen Filter zu erstellen, der diese speziellen Szenarien angehen könnte.

Im folgenden Beispiel versuche ich die Familienmitglieder herauszufiltern, die nach einem bestimmten Datum geboren wurden. Der Kick besteht darin, dass das DateOfBirth-Feld für die Objekte, die gefiltert werden, eine DateTime-Eigenschaft ist.

Der letzte Fehler, den ich bekomme, ist

Zwischen den Typen 'System.String' und 'System.Nullable`1 [System.DateTime] ist kein Zwangsoperator definiert.

Was ist das Problem. Ich habe mehrere verschiedene Arten des Gießens und Umwandelns versucht, aber zu unterschiedlichen Graden des Versagens. Letztendlich wird dies auf eine EF-Datenbank angewendet, die auch auf Konvertierungsmethoden wie DateTime.Parse (-) verzichtet hat.

Jede Hilfe wäre sehr willkommen!

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            List<Person> people = new List<Person>();
        people.Add(new Person { FirstName = "Bob", LastName = "Smith", DateOfBirth = DateTime.Parse("1969/01/21"), Weight=207 });
        people.Add(new Person { FirstName = "Lisa", LastName = "Smith", DateOfBirth = DateTime.Parse("1974/05/09") });
        people.Add(new Person { FirstName = "Jane", LastName = "Smith", DateOfBirth = DateTime.Parse("1999/05/09") });
        people.Add(new Person { FirstName = "Lori", LastName = "Jones", DateOfBirth = DateTime.Parse("2002/10/21") });
        people.Add(new Person { FirstName = "Patty", LastName = "Smith", DateOfBirth = DateTime.Parse("2012/03/11") });
        people.Add(new Person { FirstName = "George", LastName = "Smith", DateOfBirth = DateTime.Parse("2013/06/18"), Weight=6 });

            String filterField = "DateOfBirth";
            String filterOper = "<=";
            String filterValue = "2000/01/01";

            var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue);

            var query = from p in people.AsQueryable().Where(oldFamily) 
                        select p;

            Console.ReadLine();
        }

        public static Expression<Func<T, bool>> ApplyFilter<T>(String filterField, String filterOper, String filterValue)
        {
            //
            // Get the property that we are attempting to filter on. If it does not exist then throw an exception
            System.Reflection.PropertyInfo prop = typeof(T).GetProperty(filterField);
            if (prop == null)
                throw new MissingMemberException(String.Format("{0} is not a member of {1}", filterField, typeof(T).ToString()));

            Expression convertExpression     = Expression.Convert(Expression.Constant(filterValue), prop.PropertyType);

            ParameterExpression parameter    = Expression.Parameter(prop.PropertyType, filterField);
            ParameterExpression[] parameters = new ParameterExpression[] { parameter };
            BinaryExpression body            = Expression.LessThanOrEqual(parameter, convertExpression);


            Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(body, parameters);


            return predicate;

        }
    }

    public class Person
    {

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public DateTime? DateOfBirth { get; set; }
        string Nickname { get; set; }
        public int? Weight { get; set; }

        public Person() { }
        public Person(string fName, string lName)
        {
            FirstName = fName;
            LastName = lName;
        }
    }
}

Update: 2013/02/01

Mein Gedanke war dann, den Nullase-Typ in seine Non-Nullable-Version zu konvertieren. In diesem Fall möchten wir die <Nullable> DateTime in einen einfachen DateTime Typ konvertieren. Ich habe vor dem Aufruf von Expression.Convert den folgenden Codeblock hinzugefügt, um den Typ des Nullable-Werts zu ermitteln und zu erfassen.

//
//
Type propType = prop.PropertyType;
//
// If the property is nullable we need to create the expression using a NON-Nullable version of the type.
// We will get this by parsing the type from the FullName of the type 
if (prop.PropertyType.IsGenericType && prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
    String typeName = prop.PropertyType.FullName;
    Int32 startIdx  = typeName.IndexOf("[[") + 2;
    Int32 endIdx    = typeName.IndexOf(",", startIdx);
    String type     = typeName.Substring(startIdx, (endIdx-startIdx));
    propType        = Type.GetType(type);
}

Expression convertExpression = Expression.Convert(Expression.Constant(filterValue), propType);

Dies funktionierte tatsächlich beim Entfernen der Nullable-ness von der DateTime, führte jedoch zu dem folgenden Zwangsfehler. Ich bleibe dabei verwirrt, da ich dachte, dass der Zweck der "Expression.Convert" -Methode genau das ist.

Zwischen den Typen System.String und System.DateTime ist kein Zwangsoperator definiert.

Beim Anschieben habe ich den Wert explizit zu einer DateTime geparst und diese in den Mix gesteckt ...

DateTime dt = DateTime.Parse(filterValue);
Expression convertExpression = Expression.Convert(Expression.Constant(dt), propType);

... was zu einer Ausnahme geführt hat, die mein Wissen über Ausdrücke, Lambdas und verwandte Themen übertrifft ...

ParameterExpression vom Typ 'System.DateTime' kann nicht für den Delegate-Parameter 'ConsoleApplication1.Person' verwendet werden

Ich bin mir nicht sicher, was noch zu versuchen ist.

Akzeptierte Antwort

Das Problem besteht darin, dass beim Erzeugen von binären Ausdrücken die Operanden kompatibel sein müssen. Wenn nicht, müssen Sie eine Konvertierung für eine (oder beide) durchführen, bis sie kompatibel sind.

Technisch können Sie eine DateTime mit einer DateTime? , der Compiler fördert implizit den einen zum anderen, was uns erlaubt, unsere Vergleiche zu machen. Da der Compiler nicht derjenige ist, der den Ausdruck erzeugt, müssen wir die Konvertierung selbst durchführen.

Ich habe Ihr Beispiel optimiert, um allgemeiner zu sein (und funktioniert: D).

public static Expression<Func<TObject, bool>> ApplyFilter<TObject, TValue>(String filterField, FilterOperation filterOper, TValue filterValue)
{
    var type = typeof(TObject);
    ExpressionType operation;
    if (type.GetProperty(filterField) == null && type.GetField(filterField) == null)
        throw new MissingMemberException(type.Name, filterField);
    if (!operationMap.TryGetValue(filterOper, out operation))
        throw new ArgumentOutOfRangeException("filterOper", filterOper, "Invalid filter operation");

    var parameter = Expression.Parameter(type);

    var fieldAccess = Expression.PropertyOrField(parameter, filterField);
    var value = Expression.Constant(filterValue, filterValue.GetType());

    // let's perform the conversion only if we really need it
    var converted = value.Type != fieldAccess.Type
        ? (Expression)Expression.Convert(value, fieldAccess.Type)
        : (Expression)value;

    var body = Expression.MakeBinary(operation, fieldAccess, converted);

    var expr = Expression.Lambda<Func<TObject, bool>>(body, parameter);
    return expr;
}

// to restrict the allowable range of operations
public enum FilterOperation
{
    Equal,
    NotEqual,
    LessThan,
    LessThanOrEqual,
    GreaterThan,
    GreaterThanOrEqual,
}

// we could have used reflection here instead since they have the same names
static Dictionary<FilterOperation, ExpressionType> operationMap = new Dictionary<FilterOperation, ExpressionType>
{
    { FilterOperation.Equal,                ExpressionType.Equal },
    { FilterOperation.NotEqual,             ExpressionType.NotEqual },
    { FilterOperation.LessThan,             ExpressionType.LessThan },
    { FilterOperation.LessThanOrEqual,      ExpressionType.LessThanOrEqual },
    { FilterOperation.GreaterThan,          ExpressionType.GreaterThan },
    { FilterOperation.GreaterThanOrEqual,   ExpressionType.GreaterThanOrEqual },
};

Dann um es zu benutzen:

var filterField = "DateOfBirth";
var filterOper = FilterOperation.LessThanOrEqual;
var filterValue = DateTime.Parse("2000/01/01"); // note this is an actual DateTime object

var oldFamily = ApplyFilter<Person>(filterField, filterOper, filterValue);

var query = from p in people.AsQueryable().Where(oldFamily) 
            select p;

Ich weiß nicht, ob das für alle Fälle so funktioniert wie es ist, aber es funktioniert sicherlich für diesen speziellen Fall.


Beliebte Antwort

Wenn Sie Ihren verhören body variabel, können Sie sehen , dass der Körper des Ausdrucks sind die Schaffung Sie ist im Wesentlichen DateOfBirth <= '2000/01/01' .

Auf dem Gesicht mag das richtig erscheinen, aber du versuchst, diesen Körper einer Funktion zuzuordnen, die eine Person (das wäre T in deinem Beispiel) und einen Bool zurückgibt. Sie müssen Ihre Logik so ändern, dass der DateOfBirth die Eingabe als Person Objekt widerspiegelt, auf die DateOfBirth Eigenschaft für diese Instanz der Person zugreift und anschließend den Vergleich durchführt.

Mit anderen Worten, der Körper Ihres Ausdrucks muss ein T , die richtige Eigenschaft darauf finden und dann vergleichen.



Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum
Lizenziert unter: CC-BY-SA with attribution
Nicht verbunden mit Stack Overflow
Ist diese KB legal? Ja, lerne warum