linq C#中此GroupJoin返回的是什么

pvcm50d1  于 2023-04-27  发布在  C#
关注(0)|答案(2)|浏览(145)

我在理解C#中的GroupJoin时遇到了一些问题,想知道是否有人可以澄清一下。
假设我有这个对象的两个列表

public class Item
{
     public int Id { get; set;}

     public decimal Amount { get; set;}
}

var itemListOne = new List<Item>(){
    new Item{
        Id = 1,
        Amount = 100
    },
    new Item{
        Id = 2,
        Amount = 200
    },
    new Item{
        Id = 3,
        Amount = 300
    },
}

var itemListTwo = new List<Item>(){
    new Item{
        Id = 1,
        Amount = 400
    },
    new Item{
        Id = 2,
        Amount = 500
    }
}

然后调用一个LINQ查询,如下所示

var result = (from listOneItem in itemListOne
              join listTwoItem in itemListTwo on listOneItem.Id equals listTwoItem.id into joinedItems
              from joinedItem in joinedItems.DefaultIfEmpty()
              select joinedItem != null ? joinedItem.Amount : 0).ToArray();

我在这段代码中寻找的是连接两个列表的Id字段。一旦连接,我将从第二个列表中选择金额值,而第一个列表中的剩余条目将不会连接,因此它将返回0到列表中。
这段代码实际上是有效的。当我测试它的时候,我可以看到前两个条目是400和500。第三个条目是0,因为第一个列表中的第三个条目不能加入第二个列表。但我不明白为什么它有效。
我阅读到GroupJoins,我想它提到返回值将是外部列表中的条目。所以我希望有一个100,200和300的数组,因为外部列表是itemListOne,对吗?但看起来它实际上给了我第二个列表中的值。

iklwldmw

iklwldmw1#

您的Linq表达式...

var result = (
        from listOneItem in itemListOne
        join listTwoItem in itemListTwo on listOneItem.Id equals listTwoItem.id into joinedItems
        from joinedItem in joinedItems.DefaultIfEmpty()
        select joinedItem != null ? joinedItem.Amount : 0
    )
    .ToArray();

...相当于这样:

decimal[] result = itemListOne
    .GroupJoin(
        inner: itemListTwo,
        outerKeySelector: i1 => i1.Id,
        innerKeySelector: i2 => i2.Id,
        resultSelector  : ( Item i1, IEnumerable<Item> i2Items ) => new { i1, i2Items }
    )
    .SelectMany(
        collectionSelector: t => t.i2Items.DefaultIfEmpty(),     // `t` is the Anonymous Type above.
        resultSelector    : ( t, i2 ) => i2?.Amount ?? 0M
    )
    .ToArray();

注意i1对象是如何传递到GroupJoinresultSelector中的,resultSelector将它们输出到一个新的匿名类型对象中,然后SelectMany将其展平。
一旦加入,我将从第二个项目列表中选择Amount,第一个列表中的剩余条目将不会加入,因此它将返回0到列表中。
它会的,除了你使用的是.DefaultIfEmpty(),所以对于最后一项,表达式i2Items.DefaultIfEmpty()的计算结果是null,而不是空的IEnumerable<Item>
因此,如果删除.DefaultIfEmpty??位,它仍然可以工作,但只输出2个decimal值,而不是3个。

decimal[] result = itemListOne
    .GroupJoin(
        inner: itemListTwo,
        outerKeySelector: i1 => i1.Id,
        innerKeySelector: i2 => i2.Id,
        resultSelector  : ( Item i1, IEnumerable<Item> i2Items ) => new { i1, i2Items }
    )
    .SelectMany(
        collectionSelector: t => t.i2Items,     // `t` is the Anonymous Type above.
        resultSelector    : ( t, i2 ) => i2.Amount
    )
    .ToArray();

通过这种简化,以下是每个Linq步骤之后的中间结果:

  • GroupJoin
new[] {
    new
    {
        i1      = Item( id: 1, Amount: 100 ),
        i2Items = new[] { Item( id: 1, Amount: 400 ), }
    },
    new
    {
        i1      = Item( id: 2, Amount: 200 ),
        i2Items = new[] { Item( id: 2, Amount: 500 ), }
    },
    new
    {
        i1      = Item( id: 3, Amount: 300 ),
        i2Items = new[] { /* empty */ }
    },
}
  • SelectMany
new[] {
    400M,
    500M
}

GroupJoin匹配完成后,似乎没有使用任何i1对象,您可以通过从GroupJoin的输出中省略i1来进一步简化它:

decimal[] result = itemListOne
    .GroupJoin(
        inner: itemListTwo,
        outerKeySelector: i1 => i1.Id,
        innerKeySelector: i2 => i2.Id,
        resultSelector  : ( Item i1, IEnumerable<Item> i2Items ) => i2Items
    )
    .SelectMany( i2Items => i2Items )
    .Select( i2 => i2.Amount )
    .ToArray();

...并将.Select( i2 => i2.Amount )向上移动到resultSelector:

decimal[] result = itemListOne
    .GroupJoin(
        inner: itemListTwo,
        outerKeySelector: i1 => i1.Id,
        innerKeySelector: i2 => i2.Id,
        resultSelector  : ( Item i1, IEnumerable<Item> i2Items ) => i2Items.Select( i2 => i2.Amount )
    )
    .SelectMany( amounts => amounts )
    .ToArray();
s8vozzvw

s8vozzvw2#

这个查询基本上表示了SQL left join的等价物,其中joinedItem表示第二个(右)连接部分,所以实际上会得到null(因此需要空检查-joinedItem != null)join(Id == 3)的(左)部分。如果你想访问左部分的数据-使用listOneItem。例如对于LINQ-to-Objects:

var result = (from listOneItem in itemListOne
    join listTwoItem in itemListTwo on listOneItem.Id equals listTwoItem.Id into joinedItems
    from joinedItem in joinedItems.DefaultIfEmpty()
    select new {
        Id = listOneItem.Id,
        LeftAmount = listOneItem.Amount,
        RightAmount = joinedItem?.Amount ?? 0})
    .ToArray();

将导致以下结果:
| 身份证|左金额|RightAmount|
| --------------|--------------|--------------|
| 1|一百|四百|
| 二|两百|五百|
| 三|三百|0|

相关问题