执行内部联接
在关系数据库术语中,内部联接会生成一个结果集,在该结果集中,第一个集合的每个元素对于第二个集合中的每个匹配元素都会出现一次。 如果第一个集合中的元素没有匹配元素,则它不会出现在结果集中。 由 C# 中的 join
子句调用的 Join 方法可实现内部联接。
本文演示如何执行内联的四种变体:
基于简单键使两个数据源中的元素相关联的简单内部联接。
基于复合键使两个数据源中的元素相关联的内部联接。 复合键是由多个值组成的键,使你可以基于多个属性使元素相关联。
在其中将连续联接操作相互追加的多联接。
使用分组联接实现的内部联接。
备注
本主题中的示例使用下面的数据类:
record Person(string FirstName, string LastName);
record Pet(string Name, Person Owner);
record Employee(string FirstName, string LastName, int EmployeeID);
record Cat(string Name, Person Owner) : Pet(Name, Owner);
record Dog(string Name, Person Owner) : Pet(Name, Owner);
以及查询对象集合中的 Student
类。
示例 - 简单键联接
下面的示例创建两个集合,其中包含两种用户定义类型 Person
和 Pet
的对象。 查询使用 C# 中的 join
子句将 Person
对象与 Owner
是该 Person
的 Pet
对象匹配。 C# 中的 select
子句定义结果对象的外观。 在此示例中,结果对象是由所有者名字和宠物姓名组成的匿名类型。
Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
Person rui = new("Rui", "Raposo");
List<Person> people = new() { magnus, terry, charlotte, arlene, rui };
List<Pet> pets = new()
{
new(Name: "Barley", Owner: terry),
new("Boots", terry),
new("Whiskers", charlotte),
new("Blue Moon", rui),
new("Daisy", magnus),
};
// Create a collection of person-pet pairs. Each element in the collection
// is an anonymous type containing both the person's name and their pet's name.
var query =
from person in people
join pet in pets on person equals pet.Owner
select new
{
OwnerName = person.FirstName,
PetName = pet.Name
};
foreach (var ownerAndPet in query)
{
Console.WriteLine($"\"{ownerAndPet.PetName}\" is owned by {ownerAndPet.OwnerName}");
}
/* Output:
"Daisy" is owned by Magnus
"Barley" is owned by Terry
"Boots" is owned by Terry
"Whiskers" is owned by Charlotte
"Blue Moon" is owned by Rui
*/
请注意,LastName
是“Huff”的 Person
对象未出现在结果集中,因为没有 Pet
对象的 Pet.Owner
等于该 Person
。
示例 - 组合键联接
可以使用复合键基于多个属性来比较元素,而不是只基于一个属性使元素相关联。 为此,请为每个集合指定键选择器函数,以返回由要比较的属性组成的匿名类型。 如果对属性进行标记,则它们必须在每个键的匿名类型中具有相同标签。 属性还必须按相同顺序出现。
下面的示例使用 Employee
对象的列表和 Student
对象的列表来确定哪些雇员同时还是学生。 这两种类型都具有 String 类型的 FirstName
和 LastName
属性。 通过每个列表的元素创建联接键的函数会返回由每个元素的 FirstName
和 LastName
属性组成的匿名类型。 联接运算会比较这些复合键是否相等,并从每个列表返回名字和姓氏都匹配的对象对。
List<Employee> employees = new()
{
new(FirstName: "Terry", LastName: "Adams", EmployeeID: 522459),
new("Charlotte", "Weiss", 204467),
new("Magnus", "Hedland", 866200),
new("Vernette", "Price", 437139)
};
List<Student> students = new()
{
new(FirstName: "Vernette", LastName: "Price", StudentID: 9562),
new("Terry", "Earls", 9870),
new("Terry", "Adams", 9913)
};
// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
var query =
from employee in employees
join student in students on new
{
employee.FirstName,
employee.LastName
} equals new
{
student.FirstName,
student.LastName
}
select employee.FirstName + " " + employee.LastName;
Console.WriteLine("The following people are both employees and students:");
foreach (string name in query)
{
Console.WriteLine(name);
}
/* Output:
The following people are both employees and students:
Terry Adams
Vernette Price
*/
示例 - 多联接
可以将任意数量的联接操作相互追加,以执行多联接。 C# 中的每个 join
子句会将指定数据源与上一个联接的结果相关联。
下面的示例创建三个集合:Person
对象的列表、Cat
对象的列表和 Dog
对象的列表。
C# 中的第一个 join
子句基于与 Cat.Owner
匹配的 Person
对象来匹配人和猫。 它返回包含 Person
对象和 Cat.Name
的匿名类型的序列。
C# 中的第二个 join
子句基于由 Owner
类型的 Person
属性和动物姓名的第一个字母组成的复合键,将第一个联接返回的匿名类型与提供的狗列表中的 Dog
对象相关联。 它返回包含来自每个匹配对的 Cat.Name
和 Dog.Name
属性的匿名类型的序列。 由于这是内部联接,因此只返回第一个数据源中在第二个数据源中具有匹配项的对象。
Person magnus = new(FirstName: "Magnus", LastName: "Hedlund");
Person terry = new("Terry", "Adams");
Person charlotte = new("Charlotte", "Weiss");
Person arlene = new("Arlene", "Huff");
Person rui = new("Rui", "Raposo");
Person phyllis = new("Phyllis", "Harris");
List<Person> people = new() { magnus, terry, charlotte, arlene, rui, phyllis };
List<Cat> cats = new()
{
new(Name: "Barley", Owner: terry),
new("Boots", terry),
new("Whiskers", charlotte),
new("Blue Moon", rui),
new("Daisy", magnus),
};
List<Dog> dogs = new()
{
new(Name: "Four Wheel Drive", Owner: phyllis),
new("Duke", magnus),
new("Denim", terry),
new("Wiley", charlotte),
new("Snoopy", rui),
new("Snickers", arlene),
};
// The first join matches Person and Cat.Owner from the list of people and
// cats, based on a common Person. The second join matches dogs whose names start
// with the same letter as the cats that have the same owner.
var query =
from person in people
join cat in cats on person equals cat.Owner
join dog in dogs on new
{
Owner = person,
Letter = cat.Name.Substring(0, 1)
} equals new
{
dog.Owner,
Letter = dog.Name.Substring(0, 1)
}
select new
{
CatName = cat.Name,
DogName = dog.Name
};
foreach (var obj in query)
{
Console.WriteLine(
$"The cat \"{obj.CatName}\" shares a house, and the first letter of their name, with \"{obj.DogName}\"."
);
}
/* Output:
The cat "Daisy" shares a house, and the first letter of their name, with "Duke".
The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".
*/
示例 - 使用分组联接的内联
下面的示例演示如何使用分组联接实现内部联接。
在 query1
中,Person
对象的列表会基于与 Pet.Owner
属性匹配的 Person
,分组联接到 Pet
对象队列中。 分组联接会创建中间组的集合,其中每个组都包含 Person
对象和匹配 Pet
对象的序列。
通过向查询添加另一个 from
子句,此序列的序列会合并(或平展)为一个较长的序列。 最后一个序列的元素的类型由 select
子句指定。 在此示例中,该类型是由每个匹配对的 Person.FirstName
和 Pet.Name
属性组成的匿名类型。
query1
的结果等效于通过使用 join
子句(不使用 into
子句)执行内部联接来获取的结果集。 query2
变量演示了此等效查询。
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),
};
var query1 =
from person in people
join pet in pets on person equals pet.Owner into gj
from subpet in gj
select new
{
OwnerName = person.FirstName,
PetName = subpet.Name
};
Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in query1)
{
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}
var query2 =
from person in people
join pet in pets on person equals pet.Owner
select new
{
OwnerName = person.FirstName,
PetName = pet.Name
};
Console.WriteLine();
Console.WriteLine("The equivalent operation using Join():");
foreach (var v in query2)
{
Console.WriteLine($"{v.OwnerName} - {v.PetName}");
}
/* Output:
Inner join using GroupJoin():
Magnus - Daisy
Terry - Barley
Terry - Boots
Terry - Blue Moon
Charlotte - Whiskers
The equivalent operation using Join():
Magnus - Daisy
Terry - Barley
Terry - Boots
Terry - Blue Moon
Charlotte - Whiskers
*/