繁体   English   中英

LINQ to SQL group by显示错误的外键计数

[英]LINQ to SQL group by shows wrong count of distinct foreign key

表和所需数据在以下SQL代码中,使用此SQL代码查看我所需的报告输出。 我想在C#LINQ中实现相同的报告。

IF OBJECT_ID('Tbl_GrandParent') IS NOT NULL
BEGIN
  DROP TABLE Tbl_GrandParent;
END
GO
CREATE TABLE Tbl_GrandParent (
  GPID int IDENTITY (1, 1) PRIMARY KEY,
  GP_Name nvarchar(75),
  GP_Wealth float
);
GO

GO
SET IDENTITY_INSERT [dbo].[Tbl_GrandParent] ON
INSERT [dbo].[Tbl_GrandParent] ([GPID], [GP_Name], [GP_Wealth])
  VALUES (1, N'GP_A TO D', 100)
INSERT [dbo].[Tbl_GrandParent] ([GPID], [GP_Name], [GP_Wealth])
  VALUES (2, N'GP_E TO H', 100)
INSERT [dbo].[Tbl_GrandParent] ([GPID], [GP_Name], [GP_Wealth])
  VALUES (3, N'GP_I TO L', 100)
SET IDENTITY_INSERT [dbo].[Tbl_GrandParent] OFF
GO

GO
IF OBJECT_ID('Tbl_Parent') IS NOT NULL
BEGIN
  DROP TABLE Tbl_Parent;
END
GO
CREATE TABLE Tbl_Parent (
  PID int IDENTITY (1, 1) PRIMARY KEY,
  GPID int REFERENCES Tbl_GrandParent (GPID) NOT NULL,
  P_Name nvarchar(75),
  P_Wealth float
);
GO

GO
SET IDENTITY_INSERT [dbo].[Tbl_Parent] ON
INSERT [dbo].[Tbl_Parent] ([PID], [GPID], [P_Name], [P_Wealth])
  VALUES (1, 1, N'P_B', 50)
INSERT [dbo].[Tbl_Parent] ([PID], [GPID], [P_Name], [P_Wealth])
  VALUES (2, 1, N'P_c', 50)
INSERT [dbo].[Tbl_Parent] ([PID], [GPID], [P_Name], [P_Wealth])
  VALUES (3, 2, N'P_E', 50)
SET IDENTITY_INSERT [dbo].[Tbl_Parent] OFF
GO

GO
IF OBJECT_ID('Tbl_Child') IS NOT NULL
BEGIN
  DROP TABLE Tbl_Child;
END
GO
CREATE TABLE Tbl_Child (
  CID int IDENTITY (1, 1) PRIMARY KEY,
  PID int REFERENCES Tbl_Parent (PID) NOT NULL,
  C_Name nvarchar(75),
  C_Wealth float
);
GO

GO
SET IDENTITY_INSERT [dbo].[Tbl_Child] ON
INSERT [dbo].[Tbl_Child] ([CID], [PID], [C_Name], [C_Wealth])
  VALUES (1, 1, N'C_P_1.1', 25)
INSERT [dbo].[Tbl_Child] ([CID], [PID], [C_Name], [C_Wealth])
  VALUES (2, 1, N'C_P_1.2', 25)
INSERT [dbo].[Tbl_Child] ([CID], [PID], [C_Name], [C_Wealth])
  VALUES (3, 2, N'C_P_2.1', 25)
SET IDENTITY_INSERT [dbo].[Tbl_Child] OFF
GO

GO
IF OBJECT_ID('Tbl_WealthSpent') IS NOT NULL
BEGIN
  DROP TABLE Tbl_WealthSpent;
END
GO
CREATE TABLE Tbl_WealthSpent (
  WSID int IDENTITY (1, 1) PRIMARY KEY,
  CID int REFERENCES Tbl_Child (CID) NOT NULL,
  FromTime datetime NOT NULL,
  ToTime datetime NOT NULL
);
GO

GO
SET IDENTITY_INSERT [dbo].[Tbl_WealthSpent] ON
INSERT [dbo].[Tbl_WealthSpent] ([WSID], [CID], [FromTime], [ToTime])
  VALUES (1, 1, CAST(0x0000A7D4009450C0 AS datetime), CAST(0x0000A7D400A4CB80 AS datetime))
INSERT [dbo].[Tbl_WealthSpent] ([WSID], [CID], [FromTime], [ToTime])
  VALUES (3, 1, CAST(0x0000A7D400A4CB80 AS datetime), CAST(0x0000A7D400B54640 AS datetime))
INSERT [dbo].[Tbl_WealthSpent] ([WSID], [CID], [FromTime], [ToTime])
  VALUES (4, 1, CAST(0x0000A7D400B54640 AS datetime), CAST(0x0000A7D400C5C100 AS datetime))
INSERT [dbo].[Tbl_WealthSpent] ([WSID], [CID], [FromTime], [ToTime])
  VALUES (5, 3, CAST(0x0000A7D4009450C0 AS datetime), CAST(0x0000A7D400A4CB80 AS datetime))
INSERT [dbo].[Tbl_WealthSpent] ([WSID], [CID], [FromTime], [ToTime])
  VALUES (7, 3, CAST(0x0000A7D400A4CB80 AS datetime), CAST(0x0000A7D400B54640 AS datetime))
SET IDENTITY_INSERT [dbo].[Tbl_WealthSpent] OFF
GO


GO

SELECT
  gp.GPID,
  gp.GP_Name,
  gp.GP_Wealth,
  COUNT(DISTINCT (p.PID)) ChildCount,
  COUNT(DISTINCT (c.CID)) GrandChildCount,
  CAST((SUM(DATEDIFF(SS, WS.FromTime, WS.ToTime)) / 3600) AS decimal(10, 2)) WealthSpent
FROM Tbl_GrandParent AS gp
LEFT JOIN Tbl_Parent AS p
  ON p.GPID = gp.GPID
LEFT JOIN Tbl_Child AS c
  ON c.PID = p.PID
LEFT JOIN Tbl_WealthSpent AS ws
  ON ws.CID = c.CID
GROUP BY gp.GPID,
         gp.GP_Name,
         gp.GP_Wealth

MS SQL Join Query结果的屏幕截图:

在此输入图像描述

我想在C#LINQ中实现这个结果,

我的实际C#LINQ代码及其数据结果:

 var details = (from gp in dc.Tbl_GrandParents
                join p in dc.Tbl_Parents on gp.GPID equals p.GPID into p_join
                from p in p_join.DefaultIfEmpty()
                join c in dc.Tbl_Childs on p.PID equals c.PID into c_join
                from c in c_join.DefaultIfEmpty()
                join ws in dc.Tbl_WealthSpents on c.CID equals ws.CID into ws_join
                from ws in ws_join.DefaultIfEmpty()
                group new { gp, p, c, ws } by new
                 {
                    GPID = gp.GPID,
                    GPName = gp.GP_Name,
                    GPWealth = gp.GP_Wealth
                 } into g
                select new 
                {
                    GPID = g.Key.GPID,
                    GPName = g.Key.GPName,
                    GPWealth = g.Key.GPWealth,
                    ChildCount = g.Select(s=>s.p.PID != null).Distinct().Count(),
                    GrandChildCount = g.Select(s => s.c.CID != null).Distinct().Count(),
                    WealthSpent = ((g.Select(t => (t.ws.ToTime - t.ws.FromTime).TotalSeconds).Sum() / 3600) != null) ? ((decimal)g.Select(t => (t.ws.ToTime - t.ws.FromTime).TotalSeconds).Sum() / 3600) : (decimal)0.0,
                }).ToList();

LINQ实现了数据报告截图:

在此输入图像描述

预期数据:

GPID | GP_Name | GP_Wealth | ChildCount | GrandChildCount | WealthSpent
-----|---------|-----------|------------|-----------------|-----------
1    |GP_A TO D|100        |2           |3                |5.00
2    |GP_E TO H|100        |1           |0                |NULL
3    |GP_I TO L|100        |0           |0                |NULL

实际数据:

GPID | GP_Name | GP_Wealth | ChildCount | GrandChildCount | WealthSpent
-----|---------|-----------|------------|-----------------|-----------
1    |GP_A TO D|100        |1           |1                |5.00
2    |GP_E TO H|100        |1           |1                |0.00
3    |GP_I TO L|100        |1           |1                |0.00

与SQL Join输出相比,此处获得的ChildCount和GrandChildCount值是错误的。

我怀疑你当前的代码没有做你认为它正在做的事情。

可能认为它正在做的是计算不同的PID(或SID)的数量。 实际上它正在做的是计算spPID != null的不同结果的spPID != null

现在, spPID != null计算的结果是bool(即true / false) - 由于数据和查询的性质,它将始终返回true 所以,你有一组包含true的数据。 你在它上面运行Distinct ,并得到一个true 然后你Count那个单一的true ,你得到1。

要解决此问题,您可能需要更改:

ChildCount = g.Select(s=>s.p.PID != null).Distinct().Count(),
GrandChildCount = g.Select(s => s.c.CID != null).Distinct().Count(),

类似于:

ChildCount = g.Select(s=>s.p.PID).Where(z => z != null).Distinct().Count(),
GrandChildCount = g.Select(s => s.c.CID).Where(z => z != null).Distinct().Count(),

.Where(z => z != null)位是可选的 - 如果您愿意,可以将其删除。 我把它包含在那里只是为了尝试复制你可能尝试用原始代码做的事情(即排除空PID和SID)。

我很惊讶您当前的代码完全正常工作。 我原本期望一个空引用异常,因为你正在对可能不存在的id进行连续的左连接。

我还发现Linq通常会将操作分解为两个或更多辅助步骤。 它帮助我无论如何都能解决正在发生的事情,即使它对编译器没有帮助。 在存在各种子级别的连接的情况下尤其如此。

请注意,出于测试目的,我只是将值直接放入List中,但您可以轻松更改此值。

private class tbl_GrandParent
{
    public int GPID { get; set; }
    public string GP_Name { get; set; }
    public double GP_Wealth { get; set; }
}
private class tbl_Parent
{
    public int PID { get; set; }
    public int GPID { get; set; }
    public string P_Name { get; set; }
    public double P_Wealth { get; set; }
}
private class tbl_Child
{
    public int CID { get; set; }
    public int PID { get; set; }
    public string C_Name { get; set; }
    public double C_Wealth { get; set; }
}
private class tbl_WealthSpent
{
    public int WSID { get; set; }
    public int CID { get; set; }
    public DateTime FromTime { get; set; }
    public DateTime ToTime { get; set; }
}
private void Test()
{

    try
    {
        List<tbl_GrandParent> gps = new List<tbl_GrandParent>();
        gps.Add(new tbl_GrandParent { GPID = 1, GP_Name = "GP_A TO D", GP_Wealth = 100 });
        gps.Add(new tbl_GrandParent { GPID = 2, GP_Name = "GP_E TO H", GP_Wealth = 100 });
        gps.Add(new tbl_GrandParent { GPID = 3, GP_Name = "GP_I TO L", GP_Wealth = 100 });
        List<tbl_Parent> ps = new List<tbl_Parent>();
        ps.Add(new tbl_Parent { PID = 1, GPID = 1, P_Name = "P_B", P_Wealth = 50 });
        ps.Add(new tbl_Parent { PID = 2, GPID = 1, P_Name = "P_c", P_Wealth = 50 });
        ps.Add(new tbl_Parent { PID = 3, GPID = 2, P_Name = "P_E", P_Wealth = 50 });
        List<tbl_Child> cs = new List<tbl_Child>();
        cs.Add(new tbl_Child { CID = 1, PID = 1, C_Name = "C_P_1.1", C_Wealth = 25 });
        cs.Add(new tbl_Child { CID = 2, PID = 1, C_Name = "C_P_1.2", C_Wealth = 25 });
        cs.Add(new tbl_Child { CID = 3, PID = 2, C_Name = "C_P_2.1", C_Wealth = 25 });
        List<tbl_WealthSpent> wss = new List<tbl_WealthSpent>();
        wss.Add(new tbl_WealthSpent { WSID = 1, CID = 1, FromTime = new DateTime(2017, 8, 19, 9, 0, 0), ToTime = new DateTime(2017, 8, 19, 10, 0, 0) });
        wss.Add(new tbl_WealthSpent { WSID = 3, CID = 1, FromTime = new DateTime(2017, 8, 19, 10, 0, 0), ToTime = new DateTime(2017, 8, 19, 11, 0, 0) });
        wss.Add(new tbl_WealthSpent { WSID = 4, CID = 1, FromTime = new DateTime(2017, 8, 19, 11, 0, 0), ToTime = new DateTime(2017, 8, 19, 12, 0, 0) });
        wss.Add(new tbl_WealthSpent { WSID = 5, CID = 3, FromTime = new DateTime(2017, 8, 19, 9, 0, 0), ToTime = new DateTime(2017, 8, 19, 10, 0, 0) });
        wss.Add(new tbl_WealthSpent { WSID = 7, CID = 3, FromTime = new DateTime(2017, 8, 19, 10, 0, 0), ToTime = new DateTime(2017, 8, 19, 11, 0, 0) });

        var details = from gp in gps
                        join p in ps on gp.GPID equals p.GPID into p_join
                        from p in p_join.DefaultIfEmpty()
                        join c in cs on p?.PID ?? 0 equals c.PID into c_join
                        from c in c_join.DefaultIfEmpty()
                        join ws in wss on c?.CID ?? 0 equals ws.CID into ws_join
                        from ws in ws_join.DefaultIfEmpty()
                        select new
                        {
                            GPID = gp.GPID,
                            GP_Name = gp.GP_Name,
                            GP_Wealth = gp.GP_Wealth,
                            PID = p?.PID ?? null,
                            CID = c?.CID ?? null,
                            FromTime = ws?.FromTime ?? null,
                            ToTime = ws?.ToTime ?? null
                        };

        var grp = details.GroupBy(x => new { x.GPID, x.GP_Name, x.GP_Wealth })
                            .Select(g => new
                            {
                                GPID = g.Key.GPID,
                                GP_Name = g.Key.GP_Name,
                                GP_Wealth = g.Key.GP_Wealth,
                                ChildCount = g.Where(x => x.PID != null).Select(x => x.PID).Distinct().Count(),
                                GrandChildCount = g.Where(x => x.CID != null).Select(x => x.CID).Distinct().Count(),
                                WealthSpent = g.Where(x => x.FromTime != null).Sum(t => ((TimeSpan)(t.ToTime - t.FromTime)).TotalSeconds / 3600)
                            });
    }
    catch (Exception ex)
    {
        MessageBox.Show("Exception thrown; " + ex.Message);
    }
}

扩展结果在调试器中的grp上查看

-       Results View    Expanding the Results View will enumerate the IEnumerable   
+       [0] { GPID = 1, GP_Name = "GP_A TO D", GP_Wealth = 100, ChildCount = 2, GrandChildCount = 3, WealthSpent = 5 }  <Anonymous Type>
+       [1] { GPID = 2, GP_Name = "GP_E TO H", GP_Wealth = 100, ChildCount = 1, GrandChildCount = 0, WealthSpent = 0 }  <Anonymous Type>
+       [2] { GPID = 3, GP_Name = "GP_I TO L", GP_Wealth = 100, ChildCount = 0, GrandChildCount = 0, WealthSpent = 0 }  <Anonymous Type>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM