Join Operasi di LINQ

Gabungan dari dua sumber data adalah asosiasi objek dalam satu sumber data dengan objek yang berbagi atribut umum di sumber data lain.

Joining adalah operasi penting dalam kueri yang menargetkan sumber data yang hubungannya satu sama lain tidak dapat diikuti secara langsung. Dalam pemrograman berorientasi objek, bergabung dapat berarti korelasi antara objek yang tidak dimodelkan, seperti arah mundur dari hubungan satu arah. Contoh hubungan satu arah adalah Student kelas yang memiliki properti jenis Department yang mewakili utama, tetapi Department kelas tidak memiliki properti yang merupakan kumpulan Student objek. Jika Anda memiliki daftar Department objek dan ingin menemukan semua siswa di setiap departemen, Anda dapat menggunakan operasi gabungan untuk menemukannya.

Metode gabungan yang disediakan dalam kerangka kerja LINQ adalah Join dan GroupJoin. Metode ini melakukan equijoins, atau gabungan yang cocok dengan dua sumber data berdasarkan kesetaraan kunci. (Sebagai perbandingan, Transact-SQL mendukung operator gabungan selain equals, misalnya less than operator.) Dalam istilah database relasional, Join mengimplementasikan gabungan dalam, jenis gabungan di mana hanya objek yang memiliki kecocokan dalam himpunan data lain yang dikembalikan. Metode GroupJoin tidak setara secara langsung dalam istilah database relasional, tetapi mengimplementasikan superset gabungan dalam dan gabungan kiri luar. Gabungan kiri luar adalah gabungan yang memunculkan setiap elemen dari sumber data pertama (kiri), bahkan jika tidak memiliki elemen yang berkorelasi di sumber data lainnya.

Ilustrasi berikut menunjukkan pandangan konseptual dari dua set dan elemen dalam set yang termasuk dalam gabungan dalam atau gabungan kiri luar.

Dua lingkaran yang tumpang tindih memperlihatkan bagian dalam/ Luar.

Metode

Nama Metode Deskripsi Sintaksis Ekspresi Kueri C# Informasi Selengkapnya
Join Joins dua urutan berdasarkan fungsi pemilih kunci dan mengekstrak pasangan nilai. join … in … on … equals … Enumerable.Join

Queryable.Join
GroupJoin Joins dua urutan berdasarkan fungsi pemilih kunci dan mengelompokkan kecocokan yang dihasilkan untuk setiap elemen. join … in … on … equals … into … Enumerable.GroupJoin

Queryable.GroupJoin

Contoh berikut dalam artikel ini menggunakan sumber data umum untuk area ini:

public enum GradeLevel
{
    FirstYear = 1,
    SecondYear,
    ThirdYear,
    FourthYear
};

public class Student
{
    public required string FirstName { get; init; }
    public required string LastName { get; init; }
    public required int ID { get; init; }

    public required GradeLevel Year { get; init; }
    public required List<int> Scores { get; init; }

    public required int DepartmentID { get; init; }
}

public class Teacher
{
    public required string First { get; init; }
    public required string Last { get; init; }
    public required int ID { get; init; }
    public required string City { get; init; }
}
public class Department
{
    public required string Name { get; init; }
    public int ID { get; init; }

    public required int TeacherID { get; init; }
}

Masing-masing Student memiliki tingkat kelas, departemen utama, dan serangkaian skor. A Teacher juga memiliki City properti yang mengidentifikasi kampus tempat guru mengadakan kelas. A Department memiliki nama, dan referensi untuk siapa Teacher yang menjabat sebagai kepala departemen.

Contoh berikut menggunakan klausul join … in … on … equals … untuk menggabungkan dua urutan berdasarkan nilai tertentu:

var query = from student in students
            join department in departments on student.DepartmentID equals department.ID
            select new { Name = $"{student.FirstName} {student.LastName}", DepartmentName = department.Name };

foreach (var item in query)
{
    Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}

Kueri sebelumnya dapat diekspresikan menggunakan sintaks metode seperti yang diperlihatkan dalam kode berikut:

var query = students.Join(departments,
    student => student.DepartmentID, department => department.ID,
    (student, department) => new { Name = $"{student.FirstName} {student.LastName}", DepartmentName = department.Name });

foreach (var item in query)
{
    Console.WriteLine($"{item.Name} - {item.DepartmentName}");
}

Contoh berikut menggunakan klausul join … in … on … equals … into … untuk menggabungkan dua urutan berdasarkan nilai tertentu dan mengelompokkan kecocokan yang dihasilkan untuk setiap elemen:

IEnumerable<IEnumerable<Student>> studentGroups = from department in departments
                    join student in students on department.ID equals student.DepartmentID into studentGroup
                    select studentGroup;

foreach (IEnumerable<Student> studentGroup in studentGroups)
{
    Console.WriteLine("Group");
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"  - {student.FirstName}, {student.LastName}");
    }
}

Kueri sebelumnya dapat diekspresikan menggunakan sintaks metode seperti yang diperlihatkan dalam contoh berikut:

// Join department and student based on DepartmentId and grouping result
IEnumerable<IEnumerable<Student>> studentGroups = departments.GroupJoin(students,
    department => department.ID, student => student.DepartmentID,
    (department, studentGroup) => studentGroup);

foreach (IEnumerable<Student> studentGroup in studentGroups)
{
    Console.WriteLine("Group");
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"  - {student.FirstName}, {student.LastName}");
    }
}

Lakukan gabungan dalam

Dalam istilah database hubungan, gabungan dalam menghasilkan kumpulan hasil di mana setiap elemen koleksi pertama muncul satu kali untuk setiap elemen yang cocok dalam koleksi kedua. Jika elemen dalam koleksi pertama tidak memiliki elemen yang cocok, elemen tersebut tidak muncul dalam tataan hasil. Metode Join, yang dipanggil oleh klausul join di C#, mengimplementasikan gabungan dalam. Contoh berikut menunjukkan kepada Anda cara melakukan empat variasi gabungan dalam:

  • Gabungan dalam sederhana yang menghubungkan elemen dari dua sumber data berdasarkan kunci sederhana.
  • Gabungan dalam yang menghubungkan elemen dari dua sumber data berdasarkan kunci komposit. Kunci komposit, yang merupakan kunci yang terdiri dari lebih dari satu nilai, memungkinkan Anda untuk menghubungkan elemen berdasarkan lebih dari satu properti.
  • Beberapa gabungan di mana operasi gabungan berturut-turut ditambahkan satu sama lain.
  • Gabungan dalam yang diimplementasikan dengan menggunakan gabungan grup.

Gabungan kunci tunggal

Contoh berikut cocok Teacher dengan objek dengan objek yang TeacherId cocok dengan Deparment .Teacher Klausa select dalam C# menentukan tampilan objek yang dihasilkan. Dalam contoh berikut, objek yang dihasilkan adalah jenis anonim yang terdiri dari nama departemen dan nama guru yang memimpin departemen.

var query = from department in departments
            join teacher in teachers on department.TeacherID equals teacher.ID
            select new
            {
                DepartmentName = department.Name,
                TeacherName = $"{teacher.First} {teacher.Last}"
            };

foreach (var departmentAndTeacher in query)
{
    Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by {departmentAndTeacher.TeacherName}");
}

Anda mencapai hasil yang sama menggunakan Join sintaks metode:

var query = teachers
    .Join(departments, teacher => teacher.ID, department => department.TeacherID,
        (teacher, department) =>
        new { DepartmentName = department.Name, TeacherName = $"{teacher.First} {teacher.Last}" });

foreach (var departmentAndTeacher in query)
{
    Console.WriteLine($"{departmentAndTeacher.DepartmentName} is managed by {departmentAndTeacher.TeacherName}");
}

Guru yang bukan kepala departemen tidak muncul di hasil akhir.

Gabungan kunci komposit

Alih-alih menghubungkan elemen hanya berdasarkan satu properti, Anda dapat menggunakan kunci komposit untuk membandingkan elemen berdasarkan beberapa properti. Tentukan fungsi pemilih kunci untuk setiap koleksi untuk mengembalikan jenis anonim yang terdiri dari properti yang ingin Anda bandingkan. Jika Anda memberi label properti, properti tersebut harus memiliki label yang sama di setiap jenis anonim kunci. Properti tersebut juga harus muncul dalam urutan yang sama.

Contoh berikut menggunakan daftar Teacher objek dan daftar Student objek untuk menentukan guru mana yang juga siswa. Kedua jenis ini memiliki properti yang mewakili nama depan dan keluarga setiap orang. Fungsi yang membuat kunci gabungan dari setiap elemen daftar mengembalikan jenis anonim yang terdiri dari properti. Operasi gabungan membandingkan kunci komposit ini untuk kesetaraan dan mengembalikan pasangan objek dari setiap daftar di mana nama depan dan nama keluarga cocok.

// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
IEnumerable<string> query =
    from teacher in teachers
    join student in students on new
    {
        FirstName = teacher.First,
        LastName = teacher.Last
    } equals new
    {
        student.FirstName,
        student.LastName
    }
    select teacher.First + " " + teacher.Last;

string result = "The following people are both teachers and students:\r\n";
foreach (string name in query)
{
    result += $"{name}\r\n";
}
Console.Write(result);

Anda dapat menggunakan metode , seperti yang Join ditunjukkan dalam contoh berikut:

IEnumerable<string> query = teachers
    .Join(students,
        teacher => new { FirstName = teacher.First, LastName = teacher.Last },
        student => new { student.FirstName, student.LastName },
        (teacher, student) => $"{teacher.First} {teacher.Last}"
 );

Console.WriteLine("The following people are both teachers and students:");
foreach (string name in query)
{
    Console.WriteLine(name);
}

Beberapa gabungan

Sejumlah operasi gabungan dapat ditambahkan ke satu sama lain untuk melakukan beberapa gabungan. Setiap klausul join dalam C# menghubungkan sumber data tertentu dengan hasil gabungan sebelumnya.

Klausa pertama join cocok dengan siswa dan departemen berdasarkan objek DepartmentID yang Student cocok dengan Department objek ID. Ini mengembalikan urutan jenis anonim yang berisi Student objek dan Department objek.

Klausa kedua join menghubungkan jenis anonim yang dikembalikan oleh gabungan pertama dengan objek berdasarkan ID guru yang cocok dengan Teacher ID kepala departemen. Ini mengembalikan urutan jenis anonim yang berisi nama siswa, nama departemen, dan nama pemimpin departemen. Karena operasi ini adalah gabungan dalam, hanya objek dari sumber data pertama yang memiliki kecocokan di sumber data kedua yang dikembalikan.

// The first join matches Department.ID and Student.DepartmentID from the list of students and
// departments, based on a common ID. The second join matches teachers who lead departments
// with the students studying in that department.
var query = from student in students
    join department in departments on student.DepartmentID equals department.ID
    join teacher in teachers on department.TeacherID equals teacher.ID
    select new {
        StudentName = $"{student.FirstName} {student.LastName}",
        DepartmentName = department.Name,
        TeacherName = $"{teacher.First} {teacher.Last}"
    };

foreach (var obj in query)
{
    Console.WriteLine($"""The student "{obj.StudentName}" studies in the department run by "{obj.TeacherName}".""");
}

Yang setara menggunakan beberapa Join metode menggunakan pendekatan yang sama dengan jenis anonim:

var query = students
    .Join(departments, student => student.DepartmentID, department => department.ID,
        (student, department) => new { student, department })
    .Join(teachers, commonDepartment => commonDepartment.department.TeacherID, teacher => teacher.ID,
        (commonDepartment, teacher) => new
        {
            StudentName = $"{commonDepartment.student.FirstName} {commonDepartment.student.LastName}",
            DepartmentName = commonDepartment.department.Name,
            TeacherName = $"{teacher.First} {teacher.Last}"
        });

foreach (var obj in query)
{
    Console.WriteLine($"""The student "{obj.StudentName}" studies in the department run by "{obj.TeacherName}".""");
}

Gabungan dalam dengan menggunakan gabungan yang dikelompokkan

Contoh berikut menunjukkan cara mengimplementasikan gabungan dalam dengan menggunakan gabungan grup. Daftar Department objek digabungkan dengan grup ke daftar Student objek berdasarkan properti yang Department.IDStudent.DepartmentID cocok. Gabungan grup membuat kumpulan grup perantara, di mana setiap grup terdiri dari Department objek dan urutan objek Student yang cocok. Klausa kedua from menggabungkan (atau meratakan) urutan urutan ini menjadi satu urutan yang lebih lama. Klausa select menentukan jenis elemen dalam urutan akhir. Jenis itu adalah jenis anonim yang terdiri dari nama siswa dan nama departemen yang cocok.

var query1 =
    from department in departments
    join student in students on department.ID equals student.DepartmentID into gj
    from subStudent in gj
    select new
    {
        DepartmentName = department.Name,
        StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
    };
Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in query1)
{
    Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Hasil yang sama dapat dicapai menggunakan GroupJoin metode, sebagai berikut:

var queryMethod1 = departments
    .GroupJoin(students, department => department.ID, student => student.DepartmentID,
        (department, gj) => new { department, gj })
    .SelectMany(departmentAndStudent => departmentAndStudent.gj,
        (departmentAndStudent, subStudent) => new
        {
            DepartmentName = departmentAndStudent.department.Name,
            StudentName = $"{subStudent.FirstName} {subStudent.LastName}"
        });

Console.WriteLine("Inner join using GroupJoin():");
foreach (var v in queryMethod1)
{
    Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Hasilnya setara dengan tataan hasil yang diperoleh dengan menggunakan join klausa tanpa into klausul untuk melakukan gabungan dalam. Kode berikut menunjukkan kueri yang setara ini:

var query2 = from department in departments
    join student in students on department.ID equals student.DepartmentID
    select new
    {
        DepartmentName = department.Name,
        StudentName = $"{student.FirstName} {student.LastName}"
    };

Console.WriteLine("The equivalent operation using Join():");
foreach (var v in query2)
{
    Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Untuk menghindari penautan, metode tunggal Join dapat digunakan seperti yang disajikan di sini:

var queryMethod2 = departments.Join(students, departments => departments.ID, student => student.DepartmentID,
    (department, student) => new
    {
        DepartmentName = department.Name,
        StudentName = $"{student.FirstName} {student.LastName}"
    });

Console.WriteLine("The equivalent operation using Join():");
foreach (var v in queryMethod2)
{
    Console.WriteLine($"{v.DepartmentName} - {v.StudentName}");
}

Melakukan gabungan yang dikelompokkan

Gabungan grup berguna untuk menghasilkan struktur data hierarkis. Ini memasangkan setiap elemen dari kumpulan pertama dengan sekumpulan elemen berkorelasi dari kumpulan kedua.

Catatan

Setiap elemen dari kumpulan pertama muncul di kumpulan hasil dari gabungan grup terlepas dari apakah elemen berkorelasi ditemukan di kumpulan kedua. Dalam kasus di mana tidak ada elemen berkorelasi yang ditemukan, urutan elemen berkorelasi untuk elemen tersebut kosong. Oleh karena itu, pemilih hasil memiliki akses ke setiap elemen dari kumpulan pertama. Ini berbeda dari pemilih hasil dalam gabungan non-grup, yang tidak dapat mengakses elemen dari kumpulan pertama yang tidak cocok dengan kumpulan kedua.

Peringatan

Enumerable.GroupJoin tidak memiliki padanan langsung dalam istilah database hubungan tradisional. Namun, metode ini menerapkan superset gabungan dalam dan gabungan kiri luar. Kedua operasi ini dapat ditulis dalam bentuk gabungan yang dikelompokkan. Untuk informasi selengkapnya, lihat Entity Framework Core, GroupJoin.

Contoh pertama dalam artikel ini menunjukkan kepada Anda bagaimana melakukan gabungan grup. Contoh kedua menunjukkan cara menggunakan gabungan grup untuk membuat elemen XML.

Gabungan grup

Contoh berikut melakukan gabungan grup objek berjenis Department dan Student berdasarkan Department.ID yang cocok dengan properti Student.DepartmentID. Tidak seperti gabungan non-grup, yang menghasilkan sepasang elemen untuk setiap kecocokan, gabungan grup hanya menghasilkan satu objek yang dihasilkan untuk setiap elemen koleksi pertama, yang dalam contoh ini adalah Department objek. Elemen terkait dari kumpulan kedua, yang dalam contoh ini adalah objek Student, dikelompokkan ke dalam kumpulan. Terakhir, fungsi pemilih hasil membuat jenis anonim untuk setiap kecocokan yang terdiri dari Department.Name dan kumpulan objek Student.

var query = from department in departments
    join student in students on department.ID equals student.DepartmentID into studentGroup
    select new
    {
        DepartmentName = department.Name,
        Students = studentGroup
    };

foreach (var v in query)
{
    // Output the department's name.
    Console.WriteLine($"{v.DepartmentName}:");

    // Output each of the students in that department.
    foreach (Student? student in v.Students)
    {
        Console.WriteLine($"  {student.FirstName} {student.LastName}");
    }
}

Dalam contoh di atas, query variabel berisi kueri yang membuat daftar di mana setiap elemen adalah jenis anonim yang berisi nama departemen dan kumpulan siswa yang belajar di departemen tersebut.

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

var query = departments.GroupJoin(students, department => department.ID, student => student.DepartmentID,
    (department, Students) => new { DepartmentName = department.Name, Students });

foreach (var v in query)
{
    // Output the department's name.
    Console.WriteLine($"{v.DepartmentName}:");

    // Output each of the students in that department.
    foreach (Student? student in v.Students)
    {
        Console.WriteLine($"  {student.FirstName} {student.LastName}");
    }
}

Gabungan grup untuk membuat XML

Gabungan grup ideal untuk membuat XML menggunakan LINQ ke XML. Contoh berikut ini mirip dengan contoh sebelumnya kecuali bahwa sebagai ganti dari membuat jenis anonim, fungsi pemilih hasil membuat elemen XML yang mewakili objek yang digabungkan.

XElement departmentsAndStudents = new("DepartmentEnrollment",
    from department in departments
    join student in students on department.ID equals student.DepartmentID into studentGroup
    select new XElement("Department",
        new XAttribute("Name", department.Name),
        from student in studentGroup
        select new XElement("Student",
            new XAttribute("FirstName", student.FirstName),
            new XAttribute("LastName", student.LastName)
        )
    )
);

Console.WriteLine(departmentsAndStudents);

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

XElement departmentsAndStudents = new("DepartmentEnrollment",
    departments.GroupJoin(students, department => department.ID, student => student.DepartmentID,
        (department, Students) => new XElement("Department",
            new XAttribute("Name", department.Name),
            from student in Students
            select new XElement("Student",
                new XAttribute("FirstName", student.FirstName),
                new XAttribute("LastName", student.LastName)
            )
        )
    )
);

Console.WriteLine(departmentsAndStudents);

Melakukan gabungan kiri luar

Gabung luar kiri adalah gabungan di mana setiap elemen dari koleksi pertama dikembalikan, terlepas dari apakah elemen tersebut memiliki elemen berkorelasi dalam koleksi kedua. Anda dapat menggunakan LINQ untuk melakukan left outer join dengan memanggil metode DefaultIfEmpty pada hasil group join.

Contoh berikut menunjukkan cara menggunakan metode DefaultIfEmpty pada hasil gabungan grup untuk melakukan gabungan luar kiri.

Langkah pertama dalam menghasilkan left outer join dari dua collection adalah melakukan inner join dengan menggunakan group join. (Lihat Lakukan gabungan dalam untuk penjelasan tentang proses ini.) Dalam contoh ini, daftar Department objek digabungkan dalam ke daftar Student objek berdasarkan Department ID objek yang cocok dengan siswa DepartmentID.

Langkah kedua adalah memasukkan setiap elemen dari koleksi pertama (kiri) dalam kumpulan hasil meskipun elemen tersebut tidak memiliki kecocokan dalam koleksi kanan. Hal ini dapat dilakukan dengan memanggil DefaultIfEmpty pada setiap rangkaian elemen yang cocok dari grup join. Dalam contoh ini, DefaultIfEmpty dipanggil pada setiap serangkai objek Student yang cocok. Metode mengembalikan koleksi yang berisi nilai default tunggal jika urutan objek yang Student cocok kosong untuk objek apa pun Department , memastikan bahwa setiap Department objek diwakili dalam koleksi hasil.

Catatan

Nilai default untuk jenis referensi adalah null; oleh karena itu, contoh memeriksa referensi null sebelum mengakses setiap elemen dari setiap koleksi Student.

var query =
    from student in students
    join department in departments on student.DepartmentID equals department.ID into gj
    from subgroup in gj.DefaultIfEmpty()
    select new
    {
        student.FirstName,
        student.LastName,
        Department = subgroup?.Name ?? string.Empty
    };

foreach (var v in query)
{
    Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}

Kueri yang setara menggunakan sintaks metode diperlihatkan dalam kode berikut:

var query = students.GroupJoin(departments, student => student.DepartmentID, department => department.ID,
    (student, department) => new { student, subgroup = department.DefaultIfEmpty() })
    .Select(gj => new
    {
        gj.student.FirstName,
        gj.student.LastName,
        Department = gj.subgroup?.FirstOrDefault()?.Name ?? string.Empty
    });

foreach (var v in query)
{
    Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}

Lihat juga