在GroupJoin C#和LINQ中丢失数据

f5emj3cl  于 2023-09-28  发布在  C#
关注(0)|答案(2)|浏览(155)

我试图连接两个表中的数据,但我得到了奇怪的行为,并丢失了所需的信息。我不得不使用GroupJoin,因为某些项可能不像其他表那样存在于一个表中。

var joinedItems = context.TableOne.GroupJoin(context.TableTwo,
                                    i => i.ItemID,
                                    j => j.ItemID,
                                    (i, j) => new
                                    {
                                        i,
                                        j,
                                    })
                                    .Select(x => new
                                    {
                                        ID = x.i.ItemID,
                                        MonthID = x.i.MonthID,
                                        Rating = x.i.Rating,
                                        ItemStatus = x.i.Status,
                                        Tier = x.j.FirstOrDefault().Tier,
                                        TierStatus = x.j.FirstOrDefault().Status,
                                    }).Where(x => prevMonths.Contains(x.MonthID ?? 0)).ToArray();

prevMonths只是一个数组,它包含我想要的项目的月份ID(在本例中,前3个月)。作为参考,其中一个项目导致我丢失信息的问题:

Table One
----------
ItemID     MonthID    Rating     Status   . . .
96        5         12      Available
{ MonthID 2 - 4 are just straight up missing from the table }
96        1         9       Available

Table Two
---------
ItemID     MonthID     Tier      Status   . . .
96        5          5      Available
96        4        NULL        N/A
96        3        NULL        N/A
96        2        NULL        N/A

当它被连接时,96的索引显示为

{ ID: 96, MonthID: 5, Rating: 12, ItemStatus: Available, Tier: , TierStatus: }

我不知道为什么它没有正确地将数据从TableTwo连接到TableOne。我唯一的假设是,GroupJoin在记录不存在时会有一些奇怪的行为,所以它不知道如何正确地将它们链接在一起。
这个问题涉及多个项目,这只是我关注的一个例子。

xriantvc

xriantvc1#

似乎你想通过LINQ执行左连接。最正确的方法是设置实体之间的关系,并使用导航属性(即类似于context.TableOne.Include(to => to.TableTwos)...)。
如果你仍然需要/想要通过LINQ执行左连接,你可以使用以下两种查询语法之一:

from i in context.TableOne.Where(x => prevMonths.Contains(x.MonthID ?? 0))
join j in context.TableTwo on i.ItemID equals j.ItemID  into ps
from p in ps.DefaultIfEmpty()
select new {...} // use i and p

对于方法语法,您应该尝试以下操作:

var joinedItems = context.TableOne
    .Where(x => prevMonths.Contains(x.MonthID ?? 0))
    .GroupJoin(context.TableTwo,
        i => i.ItemID,
        j => j.ItemID,
        (i, j) => new {i, js})
    .SelectMany(
        x => x.js.DefaultIfEmpty(),
        (x, j) => new { i = x.i, j=j}) // actual select here
    .ToArray();
oaxa6hgo

oaxa6hgo2#

您观察到的行为是由GroupJoin的工作方式引起的。当您使用GroupJoin时,它将根据您指定的键(ItemID)将TableOne中的项与TableTwo中的项连接起来。如果TableTwo中没有与TableOne中某个特定项匹配的项,则该项的结果将是一个空集合。
在本例中,对于ItemID 96,表1中有两个条目,分别对应MonthID 5和1。但是,在TableTwo中,您有MonthID为5、4、3和2的条目。GroupJoin将仅联接两个表中具有匹配ItemID的项。因此,对于ItemID 96和MonthID 5,您将获得匹配,但对于MonthID 1,在TableTwo中没有对应的条目,因此Tier和TierStatus值为null。
尝试基于ItemID在两个表之间执行 join,然后基于MonthID过滤结果。你可以这样做:

var joinedItems = context.TableOne
    .Join(context.TableTwo, i => i.ItemID, j => j.ItemID, (i, j) => new { i, j })
    .Where(x => x.i.MonthID == x.j.MonthID && prevMonths.Contains(x.i.MonthID))
    .Select(x => new
    {
        ID = x.i.ContractID,
        MonthID = x.i.MonthID,
        Rating = x.i.Rating,
        ItemStatus = x.i.Status,
        Tier = x.j.Tier,
        TierStatus = x.j.Status,
    })
    .ToArray();

这应该能够提供类似的结果,其中TableTwo中的数据基于ItemID和MonthID与TableOne连接。

  • 编辑版本2*

基于 u/CodedRoses 反馈,更新了代码:

var joinedItems = context.TableOne.GroupJoin(context.TableTwo,
                                    i => new { i.ItemID, i.MonthID },
                                    j => new { j.ItemID, j.MonthID },
                                    (i, j) => new
                                    {
                                        i,
                                        j = j.DefaultIfEmpty() // This ensures that you get a collection with a single null item if there's no match in TableTwo
                                    })
                                    .Where(x => prevMonths.Contains(x.i.MonthID))
                                    .Select(x => new
                                    {
                                        ID = x.i.ContractID,
                                        MonthID = x.i.MonthID,
                                        Rating = x.i.Rating,
                                        ItemStatus = x.i.Status,
                                        Tier = x.j.FirstOrDefault()?.Tier, // Use the null conditional operator to handle the case where the item is null
                                        TierStatus = x.j.FirstOrDefault()?.Status, // Same here
                                    })
                                    .ToArray();

通过使用DefaultIfEmpty(),您可以确保对于TableOne中的每个项(在TableTwo中没有匹配项),您都可以获得一个集合,其中包含一个空项。这允许您在结果中包含TableOne中的所有项。然后使用null条件运算符(?.)来处理来自TableTwo的项为null的情况。
关于在Select语句之前按月份过滤的问题,问题在于Where子句是在GroupJoin之后应用的,因此它会从TableOne中过滤掉在指定月份的TableTwo中没有匹配项的项。通过将Where子句移到GroupJoin之前,可以确保只联接TableOne中与指定月份匹配的项。

相关问题