2010-08-10 60 views
6

當表達式的部分作爲參數傳遞時,如何構建表達式樹?在表達式樹中結合表達式

E.g.如果我想創建表達式目錄樹這樣的:

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

而是通過間接創建它們:

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

結果:

雖然樣品沒有多大意義(對不起但我試圖保持簡單),結果如下(謝謝Quartermeister)。

它可以與Linq-to-Sql一起使用來搜索以findText開頭或等於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)); 
} 

例如

var query=context.User; 

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

回答

5

您可以使用Expression.Invoke創建表示將一個表達到另一種表達,Expression.Lambda創建組合表達式的新lambda表達式。是這樣的:

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

內Expression.Invoke表示表達select(x)和外一個表示調用由select(x)返回的值y => y.StartsWith(find)

你也可以使用Expression.Call代表呼籲StartsWith不使用第二拉姆達:

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

謝謝,你的第一個答案正是我一直在尋找的! – laktak 2010-08-11 06:56:25

+0

這裏有一個重要的注意事項,那就是可以與LINQ2SQL和LINQ2Entities一起使用,但是對於EF-EF,由於自己最熟悉的原因,沒有實現'Expression.Invoke'。 – nicodemus13 2012-04-25 08:35:41

3

這個工程:

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

您可以使用它喜歡:

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

通常你不這樣做,你descirbed的方式(使用IQueryable的界面),但你寧願使用表達式如Expression<Func<TResult, T>>。話雖如此,您可以將高階函數(如whereselect)組合成一個查詢並傳遞表達式來「填充」所需的功能。

例如,考慮Enumerable.Where方法的簽名:

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

該函數採用一個委託作爲其被調用每個元素的第二個參數。從該委託返回的值指示高階函數是否應產生當前元素(將其包含在結果中)。

現在,讓我們來看看Queryable.Where

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

我們可以觀察到一個高階函數相同的模式,但不是一個Func<>委託它需要一個表達。表達式基本上是代碼的數據表示。編譯該表達式會給你一個真正的(可執行的)委託。編譯器做了很多繁重的工作來構建您指定給Expression<...>的lambda表達式樹。表達式樹使得可以針對不同的數據源(如SQL Server數據庫)編譯所描述的代碼。

回到你的例子,我認爲你正在尋找的是選擇器。選擇器接收每個輸入元素並返回其投影。它的簽名看起來像這樣:Expression<Func<TResult, T>>。例如,你可以指定這一個:

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

要傳遞的選擇,你的代碼需要看起來像這樣:

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

這個選擇將允許您項目輸入字符串所需類型。我希望我能正確地得到你的意圖,很難看到你想要達到的目標。