执行分组联接
分组联接对于生成分层数据结构十分有用。 它将第一个集合中的每个元素与第二个集合中的一组相关元素进行配对。
例如,一个名为 Student
的类或关系数据库表可能包含两个字段:Id
和 Name
。 另一个名为 Course
的类或关系数据库表可能包含两个字段:StudentId
和 CourseTitle
。 这两个数据源的分组联接(基于匹配 Student.Id
和 Course.StudentId
)会对具有 Course
对象集合(可能为空)的每个 Student
进行分组。
注意
第一个集合的每个元素都会出现在分组联接的结果集中(无论是否在第二个集合中找到关联元素)。 在未找到任何相关元素的情况下,该元素的相关元素序列为空。 因此,结果选择器有权访问第一个集合的每个元素。 这与非分组联接中的结果选择器不同,后者无法访问第一个集合中在第二个集合中没有匹配项的元素。
警告
Enumerable.GroupJoin 在传统关系数据库术语中没有直接等效项。 但是,此方法实现了内部联接和左外部联接的超集。 这两个操作都可以按照分组联接进行编写。 有关详细信息,请参阅联接操作和 Entity Framework Core,GroupJoin。
本文的第一个示例演示如何执行分组联接。 第二个示例演示如何使用分组联接创建 XML 元素。
备注
本主题中的示例使用执行内部联接中的 Person
和 Pet
数据类。
示例 - 分组联接
下面的示例基于与 Pet.Owner
属性匹配的 Person
,来执行类型 Person
和 Pet
的对象的分组联接。 与非分组联接(会为每个匹配生成元素对)不同,分组联接只为第一个集合的每个元素生成一个结果对象(在此示例中为 Person
对象)。 第二个集合中的对应元素(在此示例中为 Pet
对象)会分组到集合中。 最后,结果选择器函数会为每个匹配都创建一种匿名类型,其中包含 Person.FirstName
和 Pet
对象集合。
Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
List<Person> people = new() { magnus, terry, charlotte, arlene };
List<Pet> pets = new()
{
new(Name: "Barley", Owner: terry),
new("Boots", terry),
new("Whiskers", charlotte),
new("Blue Moon", terry),
new("Daisy", magnus),
};
// Create a list where each element is an anonymous type
// that contains the person's first name and a collection of
// pets that are owned by them.
var query =
from person in people
join pet in pets on person equals pet.Owner into gj
select new
{
OwnerName = person.FirstName,
Pets = gj
};
foreach (var v in query)
{
// Output the owner's name.
Console.WriteLine($"{v.OwnerName}:");
// Output each of the owner's pet's names.
foreach (var pet in v.Pets)
{
Console.WriteLine($" {pet.Name}");
}
}
/* Output:
Magnus:
Daisy
Terry:
Barley
Boots
Blue Moon
Charlotte:
Whiskers
Arlene:
*/
示例 - 用于创建 XML 的分组联接
分组联接非常适合于使用 LINQ to XML 创建 XML。 下面的示例类似于上面的示例,不过结果选择器函数不会创建匿名类型,而是创建表示联接对象的 XML 元素。
// using System.Xml.Linq;
Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
List<Person> people = new() { magnus, terry, charlotte, arlene };
List<Pet> pets = new()
{
new(Name: "Barley", Owner: terry),
new("Boots", terry),
new("Whiskers", charlotte),
new("Blue Moon", terry),
new("Daisy", magnus),
};
XElement ownersAndPets = new("PetOwners",
from person in people
join pet in pets on person equals pet.Owner into gj
select new XElement("Person",
new XAttribute("FirstName", person.FirstName),
new XAttribute("LastName", person.LastName),
from subpet in gj
select new XElement("Pet", subpet.Name)
)
);
Console.WriteLine(ownersAndPets);
/* Output:
<PetOwners>
<Person FirstName="Magnus" LastName="Hedlund">
<Pet>Daisy</Pet>
</Person>
<Person FirstName="Terry" LastName="Adams">
<Pet>Barley</Pet>
<Pet>Boots</Pet>
<Pet>Blue Moon</Pet>
</Person>
<Person FirstName="Charlotte" LastName="Weiss">
<Pet>Whiskers</Pet>
</Person>
<Person FirstName="Arlene" LastName="Huff" />
</PetOwners>
*/