Bagikan melalui


Join Operasi di LINQ

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

Penting

Sampel ini menggunakan System.Collections.Generic.IEnumerable<T> sumber data. Sumber data berdasarkan System.Linq.IQueryProvider penggunaan System.Linq.IQueryable<T> sumber data dan pohon ekspresi. Pohon ekspresi memiliki batasan pada sintaks C# yang diizinkan. Selain itu, setiap IQueryProvider sumber data, seperti EF Core dapat memberlakukan lebih banyak batasan. Periksa dokumentasi untuk sumber data Anda.

Bergabung 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 join operasi untuk menemukannya.

Metode join 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 join operator selain equals, misalnya less than operator.) Dalam istilah database relasional, Join mengimplementasikan bagian dalam join, jenis join di mana hanya objek yang memiliki kecocokan di himpunan data lain yang dikembalikan. Metode GroupJoin tidak setara secara langsung dalam istilah database relasional, tetapi mengimplementasikan superset gabungan dalam dan gabungan kiri luar. Luar join kiri adalah join yang mengembalikan setiap elemen sumber data pertama (kiri), bahkan jika tidak memiliki elemen berkorelasi di sumber data lainnya.

Ilustrasi berikut menunjukkan tampilan konseptual dua set dan elemen dalam set yang disertakan dalam bagian dalam join atau kiri luar join.

Dua lingkaran yang tumpang tindih memperlihatkan bagian dalam/ Luar.

Metode

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

Queryable.Join
GroupJoin Menggabungkan dua urutan berdasarkan fungsi pemilih utama 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 … ke join 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 join 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 relasional, bagian dalam join menghasilkan tataan hasil di mana setiap elemen koleksi pertama muncul satu kali untuk setiap elemen yang cocok di koleksi kedua. Jika elemen dalam koleksi pertama tidak memiliki elemen yang cocok, elemen tersebut tidak muncul dalam tataan hasil. Metode Join , yang disebut dengan join klausul dalam C#, mengimplementasikan bagian dalam join. Contoh berikut menunjukkan kepada Anda cara melakukan empat variasi dalam join:

  • Bagian join dalam sederhana yang menghubungkan elemen dari dua sumber data berdasarkan kunci sederhana.
  • Bagian dalam join 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 join di mana operasi berturut-turut join ditambahkan satu sama lain.
  • Bagian dalam join yang diimplementasikan dengan menggunakan grup join.

Kunci tunggal join

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.

Kunci komposit join

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 join kunci dari setiap elemen daftar mengembalikan jenis anonim yang terdiri dari properti. Operasi membandingkan join 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 join

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

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 berkorelasi dengan jenis anonim yang dikembalikan oleh yang pertama join 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 bagian dalam join, 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}".""");
}

join Dalam dengan menggunakan dikelompokkanjoin

Contoh berikut menunjukkan kepada Anda cara mengimplementasikan bagian dalam join dengan menggunakan grup join. Daftar Department objek digabungkan dengan grup ke daftar Student objek berdasarkan properti yang Department.ID Student.DepartmentID cocok. Grup join ini membuat kumpulan grup perantara, di mana setiap grup terdiri dari Department objek dan urutan objek yang Student 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 bagian dalam join. 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

Grup join ini berguna untuk menghasilkan struktur data hierarkis. Ini memasangkan setiap elemen dari kumpulan pertama dengan sekumpulan elemen berkorelasi dari kumpulan kedua.

Catatan

Setiap elemen koleksi pertama muncul dalam kumpulan hasil grup join terlepas dari apakah elemen berkorelasi ditemukan di koleksi 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 non-grup join, yang tidak dapat mengakses elemen dari koleksi pertama yang tidak memiliki kecocokan di koleksi 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 hal yang dikelompokkan join. Untuk informasi selengkapnya, lihat Entity Framework Core, GroupJoin.

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

Kelompok join

Contoh berikut melakukan sekelompok join objek jenis Department dan Student berdasarkan pencocokan Department.ID Student.DepartmentID properti. Tidak seperti non-grup join, yang menghasilkan sepasang elemen untuk setiap kecocokan, grup join 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}");
    }
}

Grup join 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

Luar join kiri adalah join tempat setiap elemen koleksi pertama dikembalikan, terlepas dari apakah ia memiliki elemen yang berkorelasi dalam koleksi kedua. Anda dapat menggunakan LINQ untuk melakukan luar join kiri dengan memanggil DefaultIfEmpty metode pada hasil grup join.

Contoh berikut menunjukkan cara menggunakan DefaultIfEmpty metode pada hasil grup join untuk melakukan luar joinkiri .

Langkah pertama dalam menghasilkan bagian luar join kiri dari dua koleksi adalah melakukan bagian dalam join dengan menggunakan grup 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. Ini dicapai dengan memanggil DefaultIfEmpty pada setiap urutan elemen pencocokan 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, departmentList) => new { student, subgroup = departmentList.AsQueryable() })
    .SelectMany(joinedSet => joinedSet.subgroup.DefaultIfEmpty(), (student, department) => new
    {
        student.student.FirstName,
        student.student.LastName,
        Department = department.Name
    });

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

Lihat juga