3

GroupJoin標準の/ SelectMany/DefaultIfEmptyアプローチを使用して、LINQ で左結合を実行するメソッドを作成しました。

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]です。なぜこれを生成するのですか?パフォーマンスに重大な影響を与える可能性はありますか? その場合、クエリを修正して削除できますか?

4

2 に答える 2

2

(免責事項: 私は LINQ についてあまり知りません。以下は、SQL に関する私の知識と、LINQ が何をしようとしているのかについての知識に基づいた推論に基づいています。)

なぜこれを生成するのですか?

の目的は、「 " に一致するレコードがない」と「 " に一致するレコードが 1 つある」1 AS [test]を区別するための明確でシンプル、一貫性のある、明確な方法を LINQ に提供することであると推測します。および他のフィールドを調べることでこれらを区別できると考えるかもしれませんが、それはおそらくあなたの場合に当てはまります。しかし、一般的なケースでは、レコードへの結合に成功したが、そのレコードから選択されたすべてのフィールドが null だった場合はどうなるでしょうか? (あなたの場合、それは不可能です。なぜなら、それは(私が推測する)null不可であるためです。ただし、LINQはそれを知っていますか?その点については、どのテーブル列がnull不可であるかを知らなくても、人間のクエリライターは回避できたはずですトップレベルのフィールドリストで使用することによるサブクエリ。PurchaseOrderLinePurchaseOrderLinePurchaseOrderLineGUIDLEFT JOINPurchaseOrderLineGUID[t2].[PurchaseOrderGuid] AS [test]ON[t2].[PurchaseOrderGuid]句は、一致が成功した場合に null になる可能性を防ぎます。しかし、それがLINQにとってどれほど明白かはわかりません。)

パフォーマンスに重大な影響を与える可能性はありますか?

あるべきではありません1 AS [test]クエリのセマンティクスに実際に影響を与える可能性のある場所 (たとえば、WHEREorONまたはGROUP BYor句) で使用されていないためHAVING、SQL Server は "述語のプッシュダウン" を実行して (ある意味で)ON条件をサブクエリに移動し、PurchaseOrderHeaderとの間の通常のインデックス付きハッシュ結合PurchaseOrderLineを使用して、必要なレコードを決定します。実際に選択されたレコードに対して1 AS [test]、結果セットを組み立てているときにのみ追加されます。PurchaseOrderLine

(私がこれを言うのは、SQL Server が述語のプッシュダウンに優れていることを知っているためです。それが悪いことが判明するまれなケースであっても)。また、上記のように、LINQこのケース. LINQ チームは自分たちが何をしているのかを知っていると思います. サブクエリがパフォーマンスの低下をもたらす可能性があると彼らが考えた場合、LINQ は特定のケースで本当にサブクエリが必要かどうかを判断するためにもっと一生懸命努力するでしょう.気にしないでください。おそらく問題ではないためです。)

于 2012-10-05T14:25:42.780 に答える
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));
}
于 2012-10-08T20:41:26.360 に答える