2012-10-05 161 views
3

我寫執行左邊LINQ加入的方法,採用標準GroupJoin/SelectMany/DefaultIfEmpty方法:額外子查詢加入

public static IQueryable<TResult> LeftJoin<TLeft, TRight, TKey, TResult>(
    this IQueryable<TLeft> left, 
    IEnumerable<TRight> right, 
    Expression<Func<TLeft, TKey>> leftKeySelector, 
    Expression<Func<TRight, TKey>> rightKeySelector, 
    Expression<Func<TLeft, TRight, TResult>> resultSelector) 
{ 
    var paramL = Expression.Parameter(typeof(TLeft), "l"); 
    var paramR = Expression.Parameter(typeof(TRight), "r"); 
    var paramRs = Expression.Parameter(typeof(IEnumerable<TRight>), "rs"); 

    var expr = Expression.Lambda<Func<TLeft, IEnumerable<TRight>, IEnumerable<TResult>>>(
     Expression.Call(
      typeof(Enumerable), 
      "Select", 
      new [] { typeof(TRight), typeof(TResult) }, 
      Expression.Call(typeof(Enumerable), "DefaultIfEmpty", new[] { typeof(TRight) }, paramRs), 
      Expression.Lambda<Func<TRight, TResult>>(
       Expression.Invoke(resultSelector, paramL, paramR), 
       paramR)), 
     paramL, 
     paramRs 
    ); 

    return left 
     .GroupJoin(
      right, 
      leftKeySelector, 
      rightKeySelector, 
      expr) 
     .SelectMany(x => x); 
} 

我測試從而:

var q = myDB.PurchaseOrderHeaders 
    .LeftJoin(
     myDB.PurchaseOrderLines, 
     po => po.PurchaseOrderGUID, 
     line => line.PurchaseOrderGUID, 
     (po, line) => new { PO = po, Line = line } 
    ); 

var e = q.AsEnumerable(); 

我期望這樣的SQL:

SELECT [t0].[PurchaseOrderGUID], ..., [t1].[PurchaseOrderLineGUID], ... 
FROM [dbo].[PurchaseOrderHeader] AS [t0] 
LEFT OUTER JOIN [dbo].[PurchaseOrderLine] AS [t1] 
    ON [t0].[PurchaseOrderGUID] = [t1].[PurchaseOrderGUID] 

但是得到了這個:

SELECT [t0].[PurchaseOrderGUID], ..., [t2].[test], [t2].[PurchaseOrderLineGUID], ... 
FROM [dbo].[PurchaseOrderHeader] AS [t0] 
LEFT OUTER JOIN (
    SELECT 1 AS [test], [t1].[PurchaseOrderLineGUID], ... 
    FROM [dbo].[PurchaseOrderLine] AS [t1] 
    ) AS [t2] ON [t0].[PurchaseOrderGUID] = [t2].[PurchaseOrderGUID] 

區別在於子查詢與SELECT 1 as [test]。它爲什麼產生這個?它是否可能對性能有重大影響?如果是這樣,我可以修改查詢來消除它嗎?

回答

2

(聲明:我不十分了解LINQ的下面是根據我的SQL的知識,什麼是LINQ試圖做一個有教養的推斷。)

爲什麼產生這個?

我猜想的1 AS [test]的目的是給LINQ清晰,簡單,一致和明確的方式,從「在PurchaseOrderLine一個匹配的記錄」區分「在PurchaseOrderLine沒有符合條件的記錄」。你可能會認爲你可以通過檢查PurchaseOrderLineGUID和其他字段來區分這些,你的情況可能是這樣;但是在一般情況下,如果LEFT JOIN成功加入記錄,但從該記錄中選擇的所有字段都爲空,會發生什麼情況? (在你的情況,這是不可能的,因爲PurchaseOrderLineGUID是(我認爲),非空的,但LINQ知道嗎?雖然對於這個問題,甚至不知道該表列非空的,一個人的查詢,作家可以有通過在頂級字段列表中使用[t2].[PurchaseOrderGuid] AS [test]來避免子查詢,因爲ON子句可防止如果匹配成功,[t2].[PurchaseOrderGuid]將爲空的可能性;但我不確定對LINQ有多明顯。)

它是否可能對性能有任何顯着影響?

There should not be any;由於1 AS [test]不會在真正影響查詢語義的任何地方使用(例如,在WHEREON或或HAVING子句中),SQL Server應該能夠執行「謂詞下推」(某種意義上)將ON條件移入子查詢,並在PurchaseOrderHeaderPurchaseOrderLine之間執行常規索引散列連接以確定它需要哪些記錄。 1 AS [test]將僅在組裝結果集時添加,實際選擇的PurchaseOrderLine記錄纔會添加。

(我說這部分是因爲我知道,SQL Server是善於謂詞下推— even in the rare cases where that turns out to be a bad thing —,部分是因爲,如上所述,LINQ 可能避免了建立在這種情況下,子查詢。我認爲LINQ團隊知道他們在做什麼,如果他們認爲子查詢可能會有性能損失,我猜測LINQ會盡力確定給定的案例是否真的需要子查詢。由於LINQ不打擾,這可能是因爲它並不重要。)

0

LinqKit有助於解決這些問題。以下擴展創建很好的sql。

public static IQueryable<TResult> LeftJoin<TOuter, TInner, TKey, TResult>(
    this IQueryable<TOuter> outer, 
    IQueryable<TInner> inner, 
    Expression<Func<TOuter, TKey>> outerKeySelector, 
    Expression<Func<TInner, TKey>> innerKeySelector, 
    Expression<Func<TOuter, TInner, TResult>> result) { 

    return outer.GroupJoin(
      inner, 
      outerKeySelector, 
      innerKeySelector, 
      (a, b) => new { a, b }).AsExpandable() 
     .SelectMany(
      z => z.b.DefaultIfEmpty(), 
      (z, b) => result.Invoke(z.a, b)); 
}