0

的代碼塊回答了這個問題:「How do you perform a left outer join using linq extension methods?如何使用MethodCallExpressions編寫左外連接?下面

var qry = Foo.GroupJoin(
     Bar, 
     foo => foo.Foo_Id, 
     bar => bar.Foo_Id, 
     (x,y) => new { Foo = x, Bars = y }) 
.SelectMany(
     x => x.Bars.DefaultIfEmpty(), 
     (x,y) => new { Foo = x, Bar = y}); 

怎麼寫這個羣組加入和作爲的SelectMany MethodCallExpressions?我發現的All of the examples是使用DynamicExpressions將字符串轉換爲lambdas(another example)編寫的。如果可能,我喜歡避免依賴該庫。

上面的查詢可以使用表達式和相關方法編寫嗎?

我知道如何使用ParameterExpressions MemberExpressions和Expression.Lambda()構造像foo => foo.Foo_Id這樣的基本lambda表達式,但是如何構造(x,y) => new { Foo = x, Bars = y }) ???能夠構建必要的參數來創建這兩個調用?

MethodCallExpression groupJoinCall = 
     Expression.Call(
      typeof(Queryable), 
      "GroupJoin", 
      new Type[] { 
        typeof(Customers), 
        typeof(Purchases), 
        outerSelectorLambda.Body.Type, 
        resultsSelectorLambda.Body.Type 
       }, 
          c.Expression, 
          p.Expression, 
          Expression.Quote(outerSelectorLambda), 
          Expression.Quote(innerSelectorLambda), 
          Expression.Quote(resultsSelectorLambda) 
         ); 
MethodCallExpression selectManyCall = 
    Expression.Call(typeof(Queryable), 
    "SelectMany", new Type[] { 
     groupJoinCall.ElementType, 
     resultType, 
     resultsSelectorLambda.Body.Type 
    }, groupJoinCall.Expression, Expression.Quote(lambda), 
    Expression.Quote(resultsSelectorLambda))); 

最終,我需要創建一個可重複的過程,將n連接到Foo。因爲我們有一個垂直數據結構,所以需要一個左連接的查詢來返回表示爲Bars的內容,以允許用戶對Foo進行排序。要求是允許用戶按10條排序,但我不指望它們使用超過三條。我試圖編寫一個過程,在上面的第一個塊中鏈接代碼達10次,但是一旦我通過了,Visual Studio 2012開始減慢速度,大約7秒鎖定它。

因此,我現在正在嘗試編寫一個方法,該方法返回selectManyCall並按照用戶請求的次數遞歸調用自身。

根據下面的查詢在LinqPad中的工作,需要重複的過程只需要手動處理Expression對象中的透明標識符。查詢排序返回按條排序的Foos(在這種情況下爲3條)。

附註。這個過程在OrderBy委託中進行連接要容易得多,但是,它生成的查詢包括T-SQL「OUTER APPLY」,這是Oracle不支持的必需的。

對於如何將投影寫入匿名類型或任何其他可能適用的現成想法,我很感激。謝謝。

var q = Foos 
     .GroupJoin (
      Bars, 
      g => g.FooID, 
      sv => sv.FooID, 
      (g, v) => 
       new 
       { 
        g = g, 
        v = v 
       } 
     ) 
     .SelectMany (
      s => s.v.DefaultIfEmpty(), 
      (s, v) => 
       new 
       { 
        s = s, 
        v = v 
       } 
     ) 
     .GroupJoin (
      Bars, 
      g => g.s.g.FooID, 
      sv => sv.FooID, 
      (g, v) => 
       new 
       { 
        g = g, 
        v = v 
       } 
     ) 
     .SelectMany (
      s => s.v.DefaultIfEmpty(), 
      (s, v) => 
       new 
       { 
        s = s, 
        v = v 
       } 
     ) 
     .GroupJoin (
      Bars, 
      g => g.s.g.s.g.FooID, 
      sv => sv.FooID, 
      (g, v) => 
       new 
       { 
        g = g, 
        v = v 
       } 
     ) 
     .SelectMany (
      s => s.v.DefaultIfEmpty(), 
      (s, v) => 
       new 
       { 
        s = s, 
        v = v 
       } 
     ) 
     .OrderBy (a => a.s.g.s.g.v.Text) 
     .ThenBy (a => a.s.g.v.Text) 
     .ThenByDescending (a => a.v.Date) 
     .Select (a => a.s.g.s.g.s.g); 

回答

1

如果您在確定如何生成表達式時遇到困難,您總是可以從編譯器獲得幫助。你可以做的是用你要查詢的類型聲明一個lambda表達式並寫入lambda。編譯器將爲您生成表達式,您可以檢查它以查看組成表達式樹的表達式。

例如,你的表情就相當於這個使用查詢語法(或者如果你願意,你可以使用方法調用的語法)

Expression<Func<IQueryable<Foo>, IQueryable<Bar>, IQueryable>> expr = 
    (Foo, Bar) => 
     from foo in Foo 
     join bar in Bar on foo.Foo_Id equals bar.Foo_Id into bars 
     from bar in bars.DefaultIfEmpty() 
     select new 
     { 
      Foo = foo, 
      Bar = bar, 
     }; 

要回答你的問題,你不能真正產生一個創建匿名對象的表達式,實際類型在編譯時並不知道。你可以通過創建一個虛擬對象來作弊,並使用GetType()來獲得它的類型,然後你可以使用它來創建合適的新表達式,但這更多的是一個骯髒的黑客,我不會推薦這樣做。這樣做,您將無法生成強類型表達式,因爲您不知道匿名類型的類型。

例如,

var dummyType = new 
{ 
    foo = default(Foo), 
    bars = default(IQueryable<Bar>), 
}.GetType(); 

var fooExpr = Expression.Parameter(typeof(Foo), "foo"); 
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars"); 
var fooProp = dummyType.GetProperty("foo"); 
var barsProp = dummyType.GetProperty("bars"); 
var ctor = dummyType.GetConstructor(new Type[] 
{ 
    fooProp.PropertyType, 
    barsProp.PropertyType, 
}); 
var newExpr = Expression.New(
    ctor, 
    new Expression[] { fooExpr, barsExpr }, 
    new MemberInfo[] { fooProp, barsProp } 
); 
// the expression type is unknown, just some lambda 
var lambda = Expression.Lambda(newExpr, fooExpr, barsExpr); 

每當你需要生成涉及一個匿名對象的表情,做正確的事情是創造一種已知類型和使用,在地方匿名類型。它的用途是有限的,但它是處理這種情況的更簡潔的方法。那麼至少你可以在編譯時獲得類型。

// use this type instead of the anonymous one 
public class Dummy 
{ 
    public Foo foo { get; set; } 
    public IQueryable<Bar> bars { get; set; } 
} 
var dummyType = typeof(Dummy); 

var fooExpr = Expression.Parameter(typeof(Foo), "foo"); 
var barsExpr = Expression.Parameter(typeof(IQueryable<Bar>), "bars"); 
var fooProp = dummyType.GetProperty("foo"); 
var barsProp = dummyType.GetProperty("bars"); 
var ctor = dummyType.GetConstructor(Type.EmptyTypes); 

var newExpr = Expression.MemberInit(
    Expression.New(ctor), 
    Expression.Bind(fooProp, fooExpr), 
    Expression.Bind(barsProp, barsExpr) 
); 
// lambda's type is known at compile time now 
var lambda = Expression.Lambda<Func<Foo, IQueryable<Bar>, Dummy>>(
    newExpr, 
    fooExpr, 
    barsExpr); 

或者,而不是創建和使用虛擬式,你也許可以在表達式中使用的元組來代替。

static Expression<Func<T1, T2, Tuple<T1, T2>>> GetExpression<T1, T2>() 
{ 
    var type1 = typeof(T1); 
    var type2 = typeof(T2); 
    var tupleType = typeof(Tuple<T1, T2>); 

    var arg1Expr = Expression.Parameter(type1, "arg1"); 
    var arg2Expr = Expression.Parameter(type2, "arg2"); 
    var arg1Prop = tupleType.GetProperty("Item1"); 
    var arg2Prop = tupleType.GetProperty("Item2"); 
    var ctor = tupleType.GetConstructor(new Type[] 
    { 
     arg1Prop.PropertyType, 
     arg2Prop.PropertyType, 
    }); 

    var newExpr = Expression.New(
     ctor, 
     new Expression[] { arg1Expr, arg2Expr }, 
     new MemberInfo[] { arg1Prop, arg2Prop } 
    ); 
    // lambda's type is known at compile time now 
    var lambda = Expression.Lambda<Func<T1, T2, Tuple<T1, T2>>>(
     newExpr, 
     arg1Expr, 
     arg2Expr); 
    return lambda; 
} 

然後使用它:

var expr = GetExpression<Foo, IQueryable<Bar>>(); 
+0

謝謝傑夫。我已經在我的問題中添加了一些細節,以解釋爲什麼我迄今尚未預測到已定義的類型,並最終達到了我想要完成的目標。我不熟悉在dummyType中實現的屬性的「默認」使用。我會試試看。你認爲這可以實現使用像Dummy這樣的定義類型嗎?我非常感謝你的觀點和反饋。 – cResults 2013-02-24 14:44:32

+0

而不是使用你自己定義的類型,也許你可以嘗試使用一個元組?只要你使用構造函數來創建它們(而不是'Tuple.Create()'工廠),我認爲你的LINQ提供者會支持它。只要虛擬類型只是簡單的對象,它們只保存值,虛擬類型就會工作。由於大多數(如果不是全部的話)供應商不會支持,所以添加任何其他方法都不行。 – 2013-02-24 16:32:39

+0

我讀過關於元組,但從未使用。投影到'tuple.Item1 = g,tuple.Item2 = v'?如果這是正確的,我將如何在後續項目和OrderBy子句中訪問它們? – cResults 2013-02-25 22:45:58