执行自定义联接操作
此示例演示如何执行无法使用 join
子句实现的联接操作。 在查询表达式中,join
子句只限于同等联接(并针对其进行优化),这类联接到目前为止是最常见的联接操作类型。 执行同等联接时,可能会始终使用 join
子句获得最佳性能。
但是,在以下情况下不能使用 join
子句:
当联接依据不等式表达式时(非同等联接)。
当联接依据多个等式或不等式表达式时。
当必须为联接操作前的右侧(内部)序列引入临时范围变量。
若要执行不是同等联接的联接,可以使用多个 from
子句独立引入每个数据源。 随后可在 where
子句中将谓词表达式应用于每个源的范围变量。 表达式还可以采用方法调用的形式。
注意
不要将这种类型的自定义联接操作与使用多个 from
子句访问内部集合相混淆。 有关详细信息,请参阅 join 子句。
交叉联接
备注
此示例和下一个示例使用对联接子句中的结果排序中的 Product
和Category
定义。
此查询显示一个简单的交叉联接。 必须谨慎使用交叉联接,因为它们可能会生成非常大的结果集。 但是,在创建针对其运行其他查询的源序列的某些情况下,它们可能会十分有用。
List<Category> categories = new()
{
new(Name: "Beverages", ID: 001),
new("Condiments", 002),
new("Vegetables", 003)
};
List<Product> products = new()
{
new(Name: "Tea", CategoryID: 001),
new("Mustard", 002),
new("Pickles", 002),
new("Carrots", 003),
new("Bok Choy", 003),
new("Peaches", 005),
new("Melons", 005),
new("Ice Cream", 007),
new("Mackerel", 012)
};
var crossJoinQuery =
from c in categories
from p in products
select new
{
c.ID,
p.Name
};
Console.WriteLine("Cross Join Query:");
foreach (var v in crossJoinQuery)
{
Console.WriteLine($"{v.ID,-5}{v.Name}");
}
/* Output:
Cross Join Query:
1 Tea
1 Mustard
1 Pickles
1 Carrots
1 Bok Choy
1 Peaches
1 Melons
1 Ice Cream
1 Mackerel
2 Tea
2 Mustard
2 Pickles
2 Carrots
2 Bok Choy
2 Peaches
2 Melons
2 Ice Cream
2 Mackerel
3 Tea
3 Mustard
3 Pickles
3 Carrots
3 Bok Choy
3 Peaches
3 Melons
3 Ice Cream
3 Mackerel
*/
非同等联接
此查询会生成其类别 ID 在左侧类别列表中列出的所有产品的序列。 请注意,其中使用 let
子句和 Contains
方法创建临时数组。 还可以在查询前创建数组并去掉第一个 from
子句。
var nonEquijoinQuery =
from p in products
let catIds =
from c in categories
select c.ID
where catIds.Contains(p.CategoryID) == true
select new
{
Product = p.Name,
p.CategoryID
};
Console.WriteLine("Non-equijoin query:");
foreach (var v in nonEquijoinQuery)
{
Console.WriteLine($"{v.CategoryID,-5}{v.Product}");
}
/* Output:
Non-equijoin query:
1 Tea
2 Mustard
2 Pickles
3 Carrots
3 Bok Choy
*/
合并 CSV 文件
在下面的示例中,查询必须基于匹配键(对于内部(右侧)序列,无法在 join 子句本身之前获取这些键)联接两个序列。 如果使用 join
子句执行了此联接,则必须为每个元素调用 Split
方法。 使用多个 from
子句可使查询避免重复进行方法调用的开销。 但是,因为 join
经过了优化,所有在此特定情况下,它可能仍然比使用多个 from
子句更快。 结果的变化主要取决于方法调用的成本高低。
string[] names = File.ReadAllLines(@"csv/names.csv");
string[] scores = File.ReadAllLines(@"csv/scores.csv");
// Merge the data sources using a named type.
// You could use var instead of an explicit type for the query.
IEnumerable<Student> queryNamesScores =
// Split each line in the data files into an array of strings.
from name in names
let x = name.Split(',')
from score in scores
let s = score.Split(',')
// Look for matching IDs from the two data files.
where x[2] == s[0]
// If the IDs match, build a Student object.
select new Student(
FirstName: x[0],
LastName: x[1],
StudentID: int.Parse(x[2]),
ExamScores: (
from scoreAsText in s.Skip(1)
select int.Parse(scoreAsText)
).ToList()
);
// Optional. Store the newly created student objects in memory
// for faster access in future queries
List<Student> students = queryNamesScores.ToList();
foreach (var student in students)
{
Console.WriteLine($"The average score of {student.FirstName} {student.LastName} is {student.ExamScores.Average()}.");
}
/* Output:
The average score of Omelchenko Svetlana is 82.5.
The average score of O'Donnell Claire is 72.25.
The average score of Mortensen Sven is 84.5.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Hugo is 85.75.
The average score of Tucker Lance is 81.75.
The average score of Adams Terry is 85.25.
The average score of Zabokritski Eugene is 83.
The average score of Tucker Michael is 92.
*/