2010-08-10 6 views
6

Jak utworzyć drzewo wyrażeń, gdy części wyrażenia są przekazywane jako argumenty?Łączenie wyrażeń w drzewie wyrażeń

E.g. co gdybym chciał tworzyć wyrażenia drzew, takie jak:

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

ale tworząc je pośrednio:

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

Wynik:

Podczas gdy próbki nie ma większego sensu (przepraszam ale starałem się zachować prostotę), oto wynik (dzięki Quartermeister).

Może być używany z Linq-to-Sql do wyszukiwania ciągu zaczynającego się od lub jest równy findText.

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

np.

var query=context.User; 

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

Odpowiedz

5

Można użyć Expression.Invoke stworzyć wyrażenie oznacza zastosowanie jednej wyraz na inny, a Expression.Lambda stworzyć nowe wyrażenie lambda dla połączonego wyrażenia. Coś takiego:

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

Wewnętrzna Expression.Invoke oznacza wyrażenie select(x) a zewnętrzna jeden reprezentuje wywołanie y => y.StartsWith(find) na wartości zwracanej przez select(x).

Można również użyć Expression.Call do reprezentowania wywołanie startswith bez użycia drugiego lambda:

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

Dzięki, twoja pierwsza odpowiedź była dokładnie tym, czego szukałem! – laktak

+0

Ważną informacją jest to, że będzie działać z LINQ2SQL i LINQ2Entities, ale nie z EF-EF, z powodów najlepiej znanych sobie, nie implementuje 'Wyrażenie.Inwojka'. – nicodemus13

3

to działa:

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

go używać jak:

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

Zazwyczaj nie rób tego w sposób descirbed (za pomocą interfejsu IQueryable), ale raczej użyć Wyrażenia takie jak Expression<Func<TResult, T>>. Powiedziawszy to, komponujesz funkcje wyższego rzędu (takie jak where lub select) do zapytania i przekazuj wyrażenia, które "wypełnią" pożądaną funkcjonalność.

Rozważmy na przykład podpis metody Enumerable.Where:

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

Funkcja przyjmuje delegata jako drugi argument, który nazywany jest w każdym elemencie. Wartość, którą zwrócisz z tego delegata, wskazuje na funkcję wyższego rzędu, jeśli powinna ona dać obecny element (uwzględnić go w wyniku lub nie).

Teraz rzućmy okiem na Queryable.Where:

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

Możemy obserwować ten sam wzór funkcji wyższego rzędu, ale zamiast Func<> delegata trwa wyrażenia. Wyrażenie to w zasadzie reprezentacja danych Twojego kodu.Kompilowanie tego wyrażenia da ci prawdziwego (wykonywalnego) delegata. Kompilator wykonuje wiele ciężkich czynności, aby budować drzewa ekspresji z lambdas przypisanych do Expression<...>. Drzewa wyrażeń umożliwiają kompilację opisanego kodu do różnych źródeł danych, takich jak baza danych SQL Server.

Aby wrócić do swojej przykład, co myślę, że szukasz jest selektor. Selektor przyjmuje każdy element wejściowy i zwraca jego rzut. Jego podpis wygląda następująco: Expression<Func<TResult, T>>. Na przykład można określić to jedno:

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

Aby przejść w selektora, kod musiałby wyglądać tak:

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

selektor To pozwala rzutować ciąg wejściowy do żądanego rodzaj. Mam nadzieję, że spełniłem waszą intencję w nieodpowiedni sposób, trudno zobaczyć, co chcecie osiągnąć.