【问题标题】:Left outer join using LINQ -- understanding the code使用 LINQ 的左外连接——理解代码
【发布时间】:2014-11-21 10:18:06
【问题描述】:

如果有人能在使用 LINQ 时解释术语 into 的含义,我将不胜感激。一般来说,我想了解如何在 C# 中制作 INNER JOINLEFT OUTER JOIN 等。

我有一个主表Students,它存储了一些外ID 键,然后在运行查询时用它们的名称替换它们。名称是从查找表中读取的,例如MarksSoftwareVersionsDepartments 等。除MarkID 外,所有字段都是必需的。我尝试在 LINQ 中构建的查询是这样的:

SELECT * FROM dbo.Students
INNER JOIN dbo.Departments ON dbo.Students.DepartmentID=dbo.Departments.DepartmentID
INNER JOIN dbo.SoftwareVersions ON dbo.Students.SoftwareVersionID=dbo.SoftwareVersions.SoftwareVersionID
INNER JOIN dbo.Statuses ON dbo.Students.StatusID=dbo.Statuses.StatusID
LEFT JOIN dbo.Marks ON dbo.Students.MarkID=dbo.Marks.MarkID
WHERE dbo.Students.DepartmentID=17;

在阅读了大量文章并观看了一些视频后,我以某种方式设法让下面的代码工作,但我觉得我对代码没有完全理解。让我感到困惑的是第 5 行以into 结尾,然后在下一行以from m ... 开头。我很困惑into 做了什么,以及from m ... 到底发生了什么。这是 LINQ 中的代码:

var result = from st in dbContext.Students where st.DepartmentID == 17
             join d in dbContext.Departments on st.DepartmentID equals d.DepartmentID
             join sv in dbContext.SoftwareVersions on st.SoftwareVersionID equals sv.SoftwareVersionID
             join stat in dbContext.Statuses on st.StatusID equals stat.StatusID
             join m in dbContext.Marks on st.MarkID equals m.MarkID into marksGroup
             from m in marksGroup.DefaultIfEmpty()
             select new
             {
                 student = st.StudentName,
                 department = p.DepartmentName,
                 software = sv.SoftwareVersionName,
                 status = st.StatusName,
                 marked = m != null ? m.MarkName : "-- Not marked --"
             };

【问题讨论】:

标签: c# sql linq


【解决方案1】:

我相信How to: Perform Left Outer Joins MSDN page 中的示例部分解释得很好。让我们将它投影到您的示例中。引用页面的第一段

产生两个集合的左外连接的第一步是 使用组连接执行内部连接。 (请参阅如何:执行 Inner Joins (C# Programming Guide) 对此的解释 过程。)在此示例中,Person 对象列表是内连接的 到基于匹配的 Person 对象的 Pet 对象列表 宠物主人。

因此,在您的情况下,第一步是根据Students 中的MarkID 对象匹配Marks 中的MarkID,执行Students 对象列表与Marks 对象列表的内部连接目的。从引用中可以看出,内部连接正在使用group join 执行。如果您查看 MSDN 页面中有关如何执行群组加入的 Note 部分,您可以看到

第一个集合的每个元素都出现在一个集合的结果集中 无论是否在 第二个集合。 在没有找到相关元素的情况下, 该元素的相关元素序列为空。这 结果选择器因此可以访问第一个元素的每个元素 收藏。

在您的示例上下文中,这意味着通过使用into,您将获得group joined 结果,其中您拥有所有Students 对象,以及Marks 对象的相关元素序列(如果有没有匹配的Marks 对象,序列将为空)。

现在让我们回到How to: Perform Left Outer Joins MSDN page,特别是第二段

第二步是包含第一个(左)的每个元素 即使该元素在结果集中没有匹配项 正确的收藏。这是通过调用 DefaultIfEmpty 来完成的 在来自组连接的每个匹配元素序列上。在这个 例如,在每个匹配的 Pet 序列上调用 DefaultIfEmpty 对象。该方法返回一个集合,其中包含一个, 如果匹配 Pet 对象的序列对于任何一个为空,则为默认值 Person 对象,从而确保每个 Person 对象都被表示 在结果集合中。

再次,为了将其投影到您的示例中,DefaultIsEmpty() 将在每个匹配的 Marks 对象序列上被调用。如上所述,如果匹配的Marks 对象序列对于任何Student 对象为空,则该方法返回一个包含单个默认值的集合,这确保每个Student 对象都将在结果集合中表示。结果,您拥有的是一组元素,其中包含所有 Student 对象和匹配的 Marks 对象,或者如果没有匹配的 Marks 对象,则默认值为 Marks,在这种情况下为 @ 987654346@.

【讨论】:

  • 好的。我觉得我有一个非常接近的地方。在into 之前(包括)我已经理解了所有内容。它根据Student 对结果进行分组。但是假设我可以为每个学生获得多个分数。我相信我有一个分组连接的结构化结果如下:Student1 and other fields from joins: List of 5 Marks for Student1Student2 ...: nullStudent3 ...: List of 2 marks 等。这如何投射到一个简单的 SQL 数据库表中,其中包含所有可能的重复记录 StudentX 987654353@ 为每个StudentX?
  • 很抱歉,但我不确定我是否理解您的问题。你想知道这个ef查询会生成什么sql查询吗?另外,您是否检查了grouped join 的示例,该示例包含以下情况,当左侧集合中的 1 个元素具有右侧集合中的多个关联元素时,可能与您的问题有关。
  • 很抱歉我的英语不是第一语言!
  • marksGroup 存储每个学生的分数集合,或在 MSDN 网站上存储每个人的宠物集合。当我从上面的代码中迭代result 时,我有一组简单的“扁平化”行,而不是像 GroupJoin 的结果那样结构化。这可能是我感到困惑的selectselect 是否将此查询中的所有可能行都投影到新类型上?感谢您的努力顺便说一句:)
  • 是的,它确实将所有可能的行投影到新的匿名类型实例。如果你想有结构,如group join 示例,你需要摆脱from m in marksGroup.DefaultIfEmpty(),并将你的选择重写为select new {..., marks = marksGroup};,这将为你提供1 个学生,以及所有可用的marks 学生每行。如果不存在标记,marks 将为空。
【解决方案2】:

我可以说的是,“进入 MarksGroup”将连接表的结果数据存储到临时(基于应用程序,而不是基于数据库)结果集(用 sql 术语:一个表,所以它是一个 SELECT INTO

在下一行中,您的代码然后从 Marksgroup 中选择包含您的数据的列(用 sql 术语表示:SELECT student, department, software, status, marked FROM Marksgroup

所以基本上,它是从数据库中获取您的数据,然后将其放在“Marksgroup”中,然后在下一步将 Marksgroup 重新放在您的手指中以取出您想要在 C# 代码中使用的数据。

尝试摆脱 Marksgroup,应该是可能的(没有用你的代码测试过 ist)。应该是这样的:

from st in dbContext.Students where st.DepartmentID == 17
             join d in dbContext.Departments on st.DepartmentID equals d.DepartmentID
             join sv in dbContext.SoftwareVersions on st.SoftwareVersionID equals sv.SoftwareVersionID
             join stat in dbContext.Statuses on st.StatusID equals stat.StatusID
             join m in dbContext.Marks on st.MarkID equals m.MarkID

             select new
             {
                 student = st.StudentName,
                 department = p.DepartmentName,
                 software = sv.SoftwareVersionName,
                 status = st.StatusName,
                 marked = m != null ? m.MarkName : "-- Not marked --"
             };

您对“m”的第二个问题:如果没有您的临时结果集“Marksgroup”,这也应该显示不同的行为

【讨论】:

  • 错了。您刚刚将外部联接变成了内部联接。
  • @Ziko,虽然您接受了这个答案,但请注意,此查询可能会产生与您的结果不同的结果。
  • 对不起,你是对的,我没有足够注意纯 SQL 代码
猜你喜欢
  • 2011-08-10
  • 1970-01-01
  • 2015-03-06
  • 1970-01-01
  • 1970-01-01
  • 2011-04-27
  • 2011-08-04
  • 2011-03-25
相关资源
最近更新 更多