2010-08-10 25 views
6

Wie kann ich einen Ausdrucksbaum erstellen, wenn Teile des Ausdrucks als Argumente übergeben werden?Ausdrücke in einem Ausdrucksbaum kombinieren

z. was ist, wenn ich wollte Ausdruck Bäume wie diese erstellen:

IQueryable<LxUser> test1(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=query.Where(x => x.Foo.StartsWith(foo)); 
    return query.Where(x => x.Bar.StartsWith(bar)); 
} 

aber von ihnen zu schaffen indirekt:

IQueryable<LxUser> test2(IQueryable<LxUser> query, string foo, string bar) 
{ 
    query=testAdd(query, x => x.Foo, foo); 
    return testAdd(query, x => x.Bar, bar); 
} 

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Where(x => select(x) .. y => y.StartsWith(find)); 
} 

Ergebnis:

Während die Proben nicht viel Sinn machen (sorry aber ich habe versucht es einfach zu halten), hier ist das Ergebnis (danke Quartermeister).

Es kann mit Linq-zu-Sql verwendet werden, um nach einer Zeichenfolge zu suchen, die mit findText beginnt oder gleich ist.

public static IQueryable<T> WhereLikeOrExact<T>(IQueryable<T> query, 
    Expression<Func<T, string>> selectField, string findText) 
{ 
    Expression<Func<string, bool>> find; 
    if (string.IsNullOrEmpty(findText) || findText=="*") return query; 

    if (findText.EndsWith("*")) 
    find=x => x.StartsWith(findText.Substring(0, findText.Length-1)); 
    else 
    find=x => x==findText; 

    var p=Expression.Parameter(typeof(T), null); 
    var xpr=Expression.Invoke(find, Expression.Invoke(selectField, p)); 

    return query.Where(Expression.Lambda<Func<T, bool>>(xpr, p)); 
} 

z.B.

var query=context.User; 

query=WhereLikeOrExact(query, x => x.FirstName, find.FirstName); 
query=WhereLikeOrExact(query, x => x.LastName, find.LastName); 

Antwort

5

Sie Expression.Invoke verwenden können, um einen Ausdruck zu erstellen, die ein Ausdruck zum anderen stellt die Anwendung, und Expression.Lambda zu erstellen neuer Lambda-Ausdruck für den kombinierten Ausdruck. Etwas wie folgt aus:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    Expression<Func<string, bool>> startsWith = y => y.StartsWith(find); 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Invoke(
       startsWith, 
       Expression.Invoke(select, parameter)), 
      parameter)); 
} 

Der innere Expression.Invoke stellt den Ausdruck select(x) und die äußere repräsentiert y => y.StartsWith(find) auf den Wert von select(x) zurück aufrufen.

Sie auch Expression.Call nutzen könnten, den Anruf zu Starts darzustellen, ohne eine zweite Lambda mit:

IQueryable<T> testAdd<T>(IQueryable<T> query, 
    Expression<Func<T, string>> select, string find) 
{ 
    var parameter = Expression.Parameter(typeof(T), null); 
    return query.Where(
     Expression.Lambda<Func<T, bool>>(
      Expression.Call(
       Expression.Invoke(select, parameter), 
       "StartsWith", 
       null, 
       Expression.Constant(find)), 
      parameter)); 
} 
+0

Danke, Ihre erste Antwort war genau das, was ich gesucht habe! – laktak

+0

Ein wichtiger Hinweis hier ist, dass mit LINQ2SQL und LINQ2Entities funktioniert, aber keine mit EF-EF, aus Gründen, die am besten bekannt sind, implementiert nicht "Expression.Invoke". – nicodemus13

3

Dieses Werk:

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector1, 
        Expression<Func<T, string>> Selector2, string data1, string data2) 
{ 
    return Add(Add(query, Selector1, data1), Selector2, data2); 
} 

public IQueryable<T> Add<T>(IQueryable<T> query, Expression<Func<T, string>> Selector, string data) 
{ 
    var row = Expression.Parameter(typeof(T), "row"); 
    var expression = 
     Expression.Call(
      Expression.Invoke(Selector, row), 
      "StartsWith", null, Expression.Constant(data, typeof(string)) 
     ); 
    var lambda = Expression.Lambda<Func<T, bool>>(expression, row); 
    return query.Where(lambda); 
} 

verwenden Sie es mögen:

IQueryable<XlUser> query = SomehowInitializeIt(); 
query = Add(query, x => x.Foo, y => y.Bar, "Foo", "Bar"); 
1

Normalerweise tun Sie nicht, dass in der Art und Weise Sie descirbed (die IQueryable Interface), aber Sie lieber verwenden Ausdrücke wie Expression<Func<TResult, T>>. Nachdem Sie das gesagt haben, schreiben Sie Funktionen höherer Ordnung (wie where oder select) in eine Abfrage und übergeben Ausdrücke, die die gewünschte Funktionalität "ausfüllen".

Man betrachte beispielsweise die Unterschrift des Enumerable.Where Methode:

Where<TSource>(IEnumerable<TSource>, Func<TSource, Boolean>) 

Die Funktion nimmt einen Delegierten als zweites Argument, das auf jedes Element genannt wird. Der Wert, den Sie von diesem Delegat zurückgeben, weist auf die Funktion höherer Ordnung hin, wenn sie das aktuelle Element liefern soll (es in das Ergebnis aufnehmen).

Nun lassen Sie uns bei Queryable.Where einen Blick:

Queryable.Where<TSource>-Methode (IQueryable<TSource>, Expression<Func<TSource, Boolean>>) 

Wir können das gleiche Muster einer Funktion höherer Ordnung beobachten, aber anstelle eines Func<> Delegat dauert es ein Ausdruck. Ein Ausdruck ist im Grunde eine Datendarstellung Ihres Codes.Wenn Sie diesen Ausdruck kompilieren, erhalten Sie einen echten (ausführbaren) Delegaten. Der Compiler macht eine Menge Schwerarbeit, um Ausdrucksbäume aus Lambda zu erstellen, die Sie Expression<...> zuweisen. Ausdrucksbäume ermöglichen es, den beschriebenen Code gegen verschiedene Datenquellen wie eine SQL Server-Datenbank zu kompilieren.

Um zu Ihrem Beispiel zurückkommen, was ich denke, Sie suchen ein Selektor ist. Ein Selektor nimmt jedes Eingabeelement und gibt eine Projektion davon zurück. Die Signatur sieht so aus: Expression<Func<TResult, T>>. Zum Beispiel könnten Sie geben diese:

Expression<Func<int, string>> numberFormatter = (i) => i.ToString(); // projects an int into a string 

Um in einem Wahl zu passieren, würde der Code wie folgt aussehen müssen:

IQueryable<T> testAdd<T>(IQueryable<T> query, Expression<Func<string, T>> selector, string find) 
{ 
    // how can I combine the select expression with StartsWith? 
    return query.Select(selector) // IQueryable<string> now 
       .Where(x => x.StartsWith(find)); 
} 

Dieser Selektor Ihnen erlauben würde, die Eingabezeichenfolge auf den gewünschten Projekt Art. Ich hoffe, ich habe deine Absicht richtig verstanden, es ist schwer zu erkennen, was du erreichen willst.

Verwandte Themen