3

GroupJoin我使用标准的//方法编写了一个在 LINQ 中执行左连接的方法SelectManyDefaultIfEmpty

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 AS [test]是为 LINQ 提供一种清晰、简单、一致且明确的方式来区分“没有匹配的记录PurchaseOrderLine”和“有一个匹配的记录PurchaseOrderLine”。您可能认为您可以通过检查PurchaseOrderLineGUID和其他字段来区分这些,这在您的情况下可能是正确的;但在一般情况下,如果LEFT JOIN成功加入记录,但从该记录中选择的所有字段均为空,会发生什么情况?(在您的情况下,这是不可能的,因为PurchaseOrderLineGUID(我假设)是不可为空的,但是 LINQ 知道吗?尽管如此,即使不知道哪些表列不可为空,人类查询编写者也可以避免[t2].[PurchaseOrderGuid] AS [test]在顶级字段列表中使用子查询,因为ON[t2].[PurchaseOrderGuid]子句防止匹配成功时为空的可能性;但我不确定这对 LINQ 有多明显。)

它可能对性能产生任何重大影响吗?

应该有;因为1 AS [test]没有在任何可能真正影响查询语义的地方使用(例如,在WHEREor ONor GROUP BYorHAVING子句中),SQL Server 应该能够执行“谓词下推”以(在某种意义上)将ON条件移动到子查询中,并执行PurchaseOrderHeader和之间的常规索引哈希连接PurchaseOrderLine以确定它需要哪些记录。1 AS [test]只有在组装结果集时才会为实际选择的记录PurchaseOrderLine添加 。

(我这么说部分是因为我知道 SQL Server 擅长谓词下推——即使在极少数情况下这被证明是一件坏事——部分是因为,如上所述,LINQ可以避免在这种情况。我想 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 回答