方法 : カスタム結合操作を実行する (C# プログラミング ガイド)
join 句では実行できない結合操作を実行する方法を次の例に示します。クエリ式の join 句は、最も一般的な種類の結合操作である等結合に制限され、また、この目的に合わせて最適化されています。一般に、等結合を実行する場合は、join 句を使用すると、最適なパフォーマンスが得られます。
ただし、次のような場合は join 句を使用できません。
非等値の式の述部に結合が使用される場合 (非等結合)
複数の等値または非等値の式の述部に結合が使用される場合
結合操作の前に右側 (内部) シーケンス用の一時的な範囲変数を導入する必要がある場合
等結合ではない結合を実行するには、複数の from 句を使用して各データ ソースを個別に導入します。次に、where 句の述部式を各ソースの範囲変数に適用します。この式は、メソッド呼び出しの形式にすることもできます。
[!メモ]
このようなカスタム結合操作を、複数の from 句を使用して内部コレクションにアクセスすることと混同しないでください。詳細については、「join 句 (C# リファレンス)」を参照してください。
使用例
次の例の最初のメソッドは、単純なクロス結合を示しています。クロス結合では、非常に大きな結果セットが生成されることがあるため、使用には注意が必要です。ただし、ソース シーケンスを作成し、このソース シーケンスに対して追加のクエリを実行する場合には、クロス結合が便利です。
2 番目のメソッドでは、左側のカテゴリの一覧にカテゴリ 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.
*/
次の例のクエリでは、一致するキーに基づいて 2 つのシーケンスを結合する必要がありますが、内部 (右側) のシーケンスの場合、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 キーを押して、プログラムをコンパイルおよび実行します。
任意のキーを押して、コンソール ウィンドウを終了します。
参照
処理手順
方法 : join 句の結果の順序を指定する (C# プログラミング ガイド)