如何:執行自訂聯結作業 (C# 程式設計手冊)
下列範例顯示如何執行無法使用 join 子句完成的聯結作業。 在查詢運算式中,join 子句受限於等聯結 (Equijoin),並且也針對等聯結進行最佳化,而等聯結是聯結作業最常見的類型。 執行等聯結時,您可以藉由使用 join 子句而一律取得最佳效能。
但是,在下列情況下無法使用 join 子句:
在不等比較 (非等聯結) 運算式上預測有聯結時。
在多個等號比較或不等比較運算式上預測有聯結時。
當您必須在聯結作業前引入右側 (內部) 序列的暫時範圍變數時。
若要執行非等聯結的聯結,您可以使用多個 from 子句個別引入每個資料來源。 然後將 where 子句中的述詞 (Predicate) 運算式套用至每個來源的範圍變數。 運算式也可以採用方法呼叫的形式。
注意事項 |
---|
請勿將這類型的自訂聯結作業與使用多個 from 子句以存取內部集合混淆。如需詳細資訊,請參閱 join 子句 (C# 參考)。 |
範例
下列範例中的第一個方法顯示簡單的交叉聯結。 交叉聯結必須小心使用,因為它們會產生相當大的結果集。 但是,它們在某些建立來源序列 (會針對這些序列執行額外的查詢) 的情況中相當有用。
第二個方法會產生所有產品的序列,產品的分類 ID 列在左側的分類清單中。 使用 let 子句和 Contains 方法建立暫時陣列時請特別注意。 另外也可以在查詢和排除第一個 from 子句之前建立陣列。
class CustomJoins
{
#region Data
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
// Specify the first data source.
List<Category> categories = new List<Category>()
{
new Category(){Name="Beverages", ID=001},
new Category(){ Name="Condiments", ID=002},
new Category(){ Name="Vegetables", ID=003},
};
// Specify the second data source.
List<Product> products = new List<Product>()
{
new Product{Name="Tea", CategoryID=001},
new Product{Name="Mustard", CategoryID=002},
new Product{Name="Pickles", CategoryID=002},
new Product{Name="Carrots", CategoryID=003},
new Product{Name="Bok Choy", CategoryID=003},
new Product{Name="Peaches", CategoryID=005},
new Product{Name="Melons", CategoryID=005},
new Product{Name="Ice Cream", CategoryID=007},
new Product{Name="Mackerel", CategoryID=012},
};
#endregion
static void Main()
{
CustomJoins app = new CustomJoins();
app.CrossJoin();
app.NonEquijoin();
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
void CrossJoin()
{
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("{0,-5}{1}", v.ID, v.Name);
}
}
void NonEquijoin()
{
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, CategoryID = p.CategoryID };
Console.WriteLine("Non-equijoin query:");
foreach (var v in nonEquijoinQuery)
{
Console.WriteLine("{0,-5}{1}", v.CategoryID, v.Product);
}
}
}
/* 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
Non-equijoin query:
1 Tea
2 Mustard
2 Pickles
3 Carrots
3 Bok Choy
Press any key to exit.
*/
在下列範例中,在 join 子句之前無法取得內部 (右側) 序列的情況下,查詢必須根據相符的索引鍵聯結兩個序列。 如果此聯結是使用 join 子句執行,則必須針對每個項目呼叫 Split 方法。 使用多個 from 子句可以讓查詢避免重複方法呼叫的負荷。 但是,由於 join 已最佳化,因此在這個特殊案例中,它的速度可能仍然比使用多個 from 子句快。 結果主要會依據方法呼叫耗費的資源而有所不同。
class MergeTwoCSVFiles
{
static void Main()
{
// See section Compiling the Code for information about the data files.
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
string[] scores = System.IO.File.ReadAllLines(@"../../../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],
ID = Convert.ToInt32(x[2]),
ExamScores = (from scoreAsText in s.Skip(1)
select Convert.ToInt32(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 {0} {1} is {2}.",
student.FirstName, student.LastName, student.ExamScores.Average());
}
//Keep console window open in debug mode
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}
/* 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.
*/
編譯程式碼
建立以 .NET Framework 3.5 (含) 以後版本為目標的 Visual Studio 主控台應用程式專案。 根據預設,專案有 System.Core.dll 的參考,以及 System.Linq 命名空間的 using 指示詞。
將 Program 類別取代為前述範例中的程式碼。
依照 如何:從不同的檔案聯結內容 (LINQ) 中的指示,設定資料檔、scores.csv 和 names.csv。
按 F5 編譯和執行程式。
按任何鍵離開主控台視窗。