klausul gabung (Referensi C#)
Klausul join
ini berguna untuk mengaitkan elemen dari urutan sumber yang berbeda yang tidak memiliki hubungan langsung dalam model objek. Satu-satunya persyaratan adalah elemen di setiap sumber berbagi beberapa nilai yang dapat dibandingkan untuk kesetaraan. Misalnya, distributor makanan mungkin memiliki daftar pemasok produk tertentu, dan daftar pembeli. Klausul join
dapat digunakan, misalnya untuk membuat daftar pemasok dan pembeli produk tersebut yang semuanya berada di wilayah tertentu yang sama.
Klausul join
mengambil dua urutan sumber sebagai masukan. Elemen-elemen dalam setiap barisan harus berupa atau mengandung suatu sifat yang dapat dibandingkan dengan suatu sifat yang bersesuaian dalam urutan lainnya. Klausul join
membandingkan kunci yang ditentukan untuk kesetaraan dengan menggunakan kata kunci equals
khusus. Semua gabungan yang dilakukan oleh klausul join
adalah equijoins. Bentuk output klausul join
tergantung pada jenis gabungan tertentu yang Anda lakukan. Berikut ini adalah tiga jenis gabungan yang paling umum:
Gabungan Dalam
Gabungan grup
Gabungan luar kiri
Gabungan Dalam
Contoh berikut menunjukkan equijoin dalam sederhana. Kueri ini menghasilkan urutan datar pasangan "nama/kategori produk". String kategori yang sama akan muncul di beberapa elemen. Jika elemen dari categories
tidak memiliki pencocokan products
, kategori tersebut tidak akan muncul dalam hasil.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
Untuk informasi selengkapnya, lihat Melakukan gabungan dalam.
Gabungan grup
Klausul join
dengan ekspresi into
disebut gabungan grup.
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
Gabungan grup menghasilkan urutan hasil hierarkis, yang mengaitkan elemen di urutan sumber kiri dengan satu atau lebih elemen yang cocok di urutan sumber sisi kanan. Gabung grup tidak memiliki padanan dalam hal relasional; itu pada dasarnya adalah urutan array objek.
Jika tidak ada elemen dari urutan sumber kanan yang ditemukan cocok dengan elemen di sumber kiri, klausul join
akan menghasilkan array kosong untuk item tersebut. Oleh karena itu, gabungan grup pada dasarnya masih merupakan equijoin gabungan kecuali bahwa urutan hasil diatur ke dalam grup.
Jika Anda hanya memilih hasil gabungan grup, Anda dapat mengakses item, tetapi Anda tidak dapat mengidentifikasi kunci yang cocok dengannya. Oleh karena itu, umumnya lebih berguna untuk memilih hasil gabungan grup menjadi jenis baru yang juga memiliki nama kunci, seperti yang ditunjukkan pada contoh sebelumnya.
Anda juga tentu saja dapat menggunakan hasil gabungan grup sebagai penghasil subkueri lain:
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
Untuk informasi selengkapnya, lihat Melakukan gabungan grup.
Gabungan luar kiri
Dalam gabungan kiri luar, semua elemen dalam urutan sumber kiri dikembalikan, bahkan jika tidak ada elemen yang cocok dalam urutan yang tepat. Untuk melakukan gabungan kiri luar di LINQ, gunakan metode DefaultIfEmpty
dalam kombinasi dengan gabungan grup untuk menentukan elemen sisi kanan default yang akan dihasilkan jika elemen sisi kiri tidak cocok. Anda dapat menggunakan null
sebagai nilai default untuk semua jenis referensi, atau Anda dapat menentukan jenis default yang ditentukan pengguna. Dalam contoh berikut, jenis default yang ditentukan pengguna akan ditampilkan:
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
select new { CatName = category.Name, ProdName = item.Name };
Untuk informasi selengkapnya, lihat Melakukan gabungan kiri luar.
Operator yang sama
Klausul join
melakukan equijoin. Dengan kata lain, Anda hanya dapat mendasarkan kecocokan pada kesetaraan dua kunci. Jenis perbandingan lain seperti "lebih besar dari" atau "tidak sama" tidak akan didukung. Untuk memperjelas bahwa semua gabungan setara, klausul join
menggunakan kata kunci equals
alih-alih operator ==
. Kata kunci equals
hanya dapat digunakan dalam klausul join
, dan berbeda dari operator ==
dengan beberapa cara penting. Saat membandingkan string, equals
mengalami kelebihan beban untuk dibandingkan dengan nilai dan operator ==
menggunakan kesetaraan referensi. Ketika kedua sisi perbandingan memiliki variabel string yang identik, equals
dan ==
mencapai hasil yang sama: true. Itu karena, ketika program mendeklarasikan dua atau lebih variabel string yang setara, pengkompilasi menyimpan semuanya di lokasi yang sama. Ini dikenal sebagai interning. Perbedaan penting lainnya adalah perbandingan null: null equals null
dievaluasi sebagai false dengan operator equals
, bukan operator ==
yang mengevaluasinya sebagai true. Terakhir, perilaku cakupan berbeda: dengan equals
, kunci kiri menggunakan urutan sumber luar, dan kunci yang tepat menggunakan sumber dalam. Sumber luar hanya dalam cakupan di sisi kiri equals
dan urutan sumber dalam hanya dalam cakupan di sisi kanan.
Non-equijoins
Anda dapat melakukan non-equijoins, cross join, dan operasi gabungan kustom lainnya dengan menggunakan beberapa klausul from
untuk memperkenalkan urutan baru secara independen ke dalam kueri. Untuk informasi selengkapnya, lihat Melakukan operasi gabungan kustom.
Gabungan pada koleksi objek vs. tabel relasional
Dalam ekspresi kueri LINQ, operasi gabungan dilakukan pada kumpulan objek. Kumpulan objek tidak dapat "digabungkan" dengan cara yang sama persis seperti dua tabel relasional. Dalam LINQ, klausul join
eksplisit hanya diperlukan ketika dua urutan sumber tidak terikat oleh hubungan apa pun. Saat bekerja dengan LINQ hingga SQL, tabel kunci asing direpresentasikan dalam model objek sebagai properti tabel utama. Misalnya, dalam database Northwind, tabel Pelanggan memiliki hubungan kunci asing dengan tabel Pesanan. Saat Anda memetakan tabel ke model objek, kelas Pelanggan memiliki properti Pesanan berisi kumpulan Pesanan terkait dengan Pelanggan tersebut. Akibatnya, gabungan sudah dilakukan untuk Anda.
Untuk informasi selengkapnya tentang mengkueri di seluruh tabel terkait dalam konteks LINQ untuk SQL, lihat Cara: Memetakan Hubungan Database.
Kunci komposit
Anda dapat menguji kesetaraan beberapa nilai dengan menggunakan kunci komposit. Untuk informasi selengkapnya, lihat Gabungan dengan menggunakan kunci komposit. Kunci komposit juga dapat digunakan dalam klausul group
.
Contoh
Contoh berikut membandingkan hasil gabungan dalam, gabungan grup, dan gabungan kiri luar pada sumber data yang sama dengan menggunakan kunci pencocokan yang sama. Beberapa kode tambahan ditambahkan ke contoh ini untuk memperjelas hasil di tampilan konsol.
class JoinDemonstration
{
#region Data
class Product
{
public required string Name { get; init; }
public required int CategoryID { get; init; }
}
class Category
{
public required string Name { get; init; }
public required int ID { get; init; }
}
// Specify the first data source.
List<Category> categories =
[
new Category {Name="Beverages", ID=001},
new Category {Name="Condiments", ID=002},
new Category {Name="Vegetables", ID=003},
new Category {Name="Grains", ID=004},
new Category {Name="Fruit", ID=005}
];
// Specify the second data source.
List<Product> products =
[
new Product {Name="Cola", CategoryID=001},
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},
];
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine($" {item.ProductName}:{item.Category}");
}
Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine($"LeftOuterJoin2: {totalItems} items in 1 group");
}
}
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group
Left Outer Join:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups
LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/
Keterangan
Klausul join
yang tidak diikuti oleh into
diterjemahkan ke dalam panggilan metode Join. Klausul join
yang diikuti oleh into
diterjemahkan ke panggilan metode GroupJoin.