Udostępnij za pośrednictwem


Join Operacje w LINQ

Jednym ze źródeł danych jest join skojarzenie obiektów w jednym źródle danych z obiektami, które współużytkuje wspólny atrybut w innym źródle danych.

Ważne

Te przykłady używają System.Collections.Generic.IEnumerable<T> źródła danych. Źródła danych oparte na System.Linq.IQueryProvider System.Linq.IQueryable<T> źródłach danych i drzewach wyrażeń. Drzewa wyrażeń mają ograniczenia dotyczące dozwolonej składni języka C#. Ponadto każde IQueryProvider źródło danych, takie jak EF Core , może nakładać więcej ograniczeń. Zapoznaj się z dokumentacją źródła danych.

Łączenie jest ważną operacją w zapytaniach przeznaczonych dla źródeł danych, których relacje ze sobą nie mogą być wykonywane bezpośrednio. W programowaniu obiektowym łączenie może oznaczać korelację między obiektami, które nie są modelowane, takie jak kierunek wsteczny relacji jednokierunkowej. Przykładem relacji jednokierunkowej jest Student klasa, która ma właściwość typu Department reprezentującą główną, ale Department klasa nie ma właściwości, która jest kolekcją Student obiektów. Jeśli masz listę Department obiektów i chcesz znaleźć wszystkich uczniów w każdym dziale, możesz użyć join operacji , aby je znaleźć.

Metody join podane w strukturze LINQ to Join i GroupJoin. Te metody wykonują równoczesne lub sprzężenia pasujące do dwóch źródeł danych na podstawie równości kluczy. (Dla porównania język Transact-SQL obsługuje join operatory inne niż equals, na przykład less than operator). W terminach relacyjnej bazy danych implementuje wewnętrzny jointypjoin, Join w którym zwracane są tylko te obiekty, które mają dopasowanie w innym zestawie danych. Metoda GroupJoin nie ma bezpośredniego odpowiednika w terminach relacyjnej bazy danych, ale implementuje nadzbiór sprzężeń wewnętrznych i lewe sprzężenia zewnętrzne. Lewa zewnętrzna join jest elementem join , który zwraca każdy element pierwszego (po lewej) źródle danych, nawet jeśli nie ma skorelowanych elementów w innym źródle danych.

Na poniższej ilustracji przedstawiono koncepcyjny widok dwóch zestawów i elementy w tych zestawach, które znajdują się w wewnętrznym join lub lewym zewnętrznym .join

Dwa nakładające się okręgi pokazujące wewnętrzne / Zewnętrzne.

Metody

Nazwa metody opis Składnia wyrażeń zapytań języka C# Więcej informacji
Join Łączy dwie sekwencje na podstawie funkcji selektora kluczy i wyodrębnia pary wartości. join … in … on … equals … Enumerable.Join

Queryable.Join
GroupJoin Łączy dwie sekwencje na podstawie funkcji selektora kluczy i grupuje wynikowe dopasowania dla każdego elementu. join … in … on … equals … into … Enumerable.GroupJoin

Queryable.GroupJoin

Uwaga

W poniższych przykładach w tym artykule użyto typowych źródeł danych dla tego obszaru.
Każdy z nich Student ma poziom klasy, dział podstawowy i serię wyników. Obiekt Teacher ma również właściwość identyfikującą City kampus, w którym nauczyciel posiada zajęcia. Element Department ma nazwę i odwołanie do osoby Teacher , która służy jako szef działu.
Przykładowy zestaw danych można znaleźć w repozytorium źródłowym.

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; }
}

W poniższym przykładzie użyto klauzuli join … in … on … equals … do join dwóch sekwencji na podstawie określonej wartości:

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}");
}

Powyższe zapytanie można wyrazić przy użyciu składni metody, jak pokazano w poniższym kodzie:

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}");
}

W poniższym przykładzie użyto klauzuli join … in … on … equals … into … do join dwóch sekwencji na podstawie określonej wartości i grupuje wynikowe dopasowania dla każdego elementu:

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}");
    }
}

Powyższe zapytanie można wyrazić przy użyciu składni metody, jak pokazano w poniższym przykładzie:

// 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}");
    }
}

Wykonywanie sprzężeń wewnętrznych

W kategoriach relacyjnej bazy danych wewnętrzny join generuje zestaw wyników, w którym każdy element pierwszej kolekcji pojawia się jeden raz dla każdego pasującego elementu w drugiej kolekcji. Jeśli element w pierwszej kolekcji nie zawiera pasujących elementów, nie jest wyświetlany w zestawie wyników. Metoda Join , która jest wywoływana przez klauzulę join w języku C#, implementuje wewnętrzny joinelement . W poniższych przykładach pokazano, jak wykonać cztery odmiany wewnętrznego joinelementu :

  • Prosty wewnętrzny join , który koreluje elementy z dwóch źródeł danych na podstawie prostego klucza.
  • Wewnętrznyjoin, który koreluje elementy z dwóch źródeł danych na podstawie klucza złożonego. Klucz złożony, który jest kluczem składającym się z więcej niż jednej wartości, umożliwia korelowanie elementów na podstawie więcej niż jednej właściwości.
  • Wiele join operacji, w których kolejne join operacje są dołączane do siebie nawzajem.
  • Wewnętrzny join , który jest implementowany przy użyciu grupy join.

Pojedynczy klucz join

Poniższy przykład pasuje do Teacher obiektów z obiektami Department , których TeacherId pasuje do tego Teacherobiektu . Klauzula select w języku C# definiuje wygląd wynikowych obiektów. W poniższym przykładzie wynikowe obiekty są typami anonimowymi, które składają się z nazwy działu i nazwy nauczyciela prowadzącego dział.

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}");
}

Te same wyniki są osiągane przy użyciu Join składni metody:

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}");
}

Nauczyciele, którzy nie są szefami działów, nie pojawiają się w końcowych wynikach.

Klucz złożony join

Zamiast korelować elementy na podstawie tylko jednej właściwości, można użyć klucza złożonego do porównywania elementów na podstawie wielu właściwości. Określ funkcję selektora kluczy dla każdej kolekcji, aby zwrócić typ anonimowy składający się z właściwości, które chcesz porównać. Jeśli etykietujesz właściwości, muszą mieć tę samą etykietę w typie anonimowym każdego klucza. Właściwości muszą być również wyświetlane w tej samej kolejności.

W poniższym przykładzie użyto listy Teacher obiektów i listy Student obiektów, aby określić, którzy nauczyciele są również uczniami. Oba te typy mają właściwości reprezentujące imię i nazwisko każdej osoby. Funkcje, które tworzą join klucze z elementów każdej listy, zwracają typ anonimowy, który składa się z właściwości. Operacja join porównuje te klucze złożone pod kątem równości i zwraca pary obiektów z każdej listy, gdzie zarówno imię, jak i nazwa rodziny są zgodne.

// 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);

Możesz użyć Join metody , jak pokazano w poniższym przykładzie:

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);
}

Wielokrotny join

Dowolną liczbę join operacji można dołączać do siebie, aby wykonać wiele joinoperacji. Każda klauzula join w języku C# koreluje określone źródło danych z wynikami poprzedniego joinelementu .

join Pierwsza klauzula pasuje do uczniów i działów w oparciu o Student obiekt pasujący Department do obiektu DepartmentID ID. Zwraca sekwencję typów anonimowych, które zawierają Student obiekt i Department obiekt.

Druga join klauzula koreluje typy anonimowe zwracane przez pierwszy join z Teacher obiektami na podstawie identyfikatora tego nauczyciela pasującego do identyfikatora głównego działu. Zwraca sekwencję typów anonimowych, które zawierają nazwę ucznia, nazwę działu i nazwę kierownika działu. Ponieważ ta operacja jest wewnętrzną joinoperacją, zwracane są tylko te obiekty z pierwszego źródła danych, które mają dopasowanie w drugim źródle danych.

// 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}".""");
}

Odpowiednik przy użyciu wielu Join metod używa tego samego podejścia z typem anonimowym:

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}".""");
}

Wewnętrzny join przy użyciu grupowanych join

W poniższym przykładzie pokazano, jak zaimplementować wewnętrzny join element przy użyciu grupy join. Lista Department obiektów jest przyłączona do listy Student obiektów na Department.ID podstawie pasującej Student.DepartmentID właściwości. join Grupa tworzy kolekcję grup pośrednich, gdzie każda grupa składa się z Department obiektu i sekwencji pasujących Student obiektów. Druga from klauzula łączy (lub spłaszcza) tę sekwencję sekwencji w jedną dłuższą sekwencję. Klauzula select określa typ elementów w sekwencji końcowej. Ten typ jest typem anonimowym, który składa się z nazwy ucznia i pasującej nazwy działu.

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}");
}

Te same wyniki można osiągnąć przy użyciu GroupJoin metody w następujący sposób:

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}");
}

Wynik jest odpowiednikiem zestawu wyników uzyskanego przy użyciu join klauzuli bez into klauzuli w celu wykonania wewnętrznego join. Poniższy kod demonstruje to równoważne zapytanie:

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}");
}

Aby uniknąć tworzenia łańcuchów, można użyć pojedynczej Join metody, jak pokazano poniżej:

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}");
}

Wykonywanie sprzężeń grupowanych

Grupa join jest przydatna do tworzenia hierarchicznych struktur danych. Łączy każdy element z pierwszej kolekcji z zestawem skorelowanych elementów z drugiej kolekcji.

Uwaga

Każdy element pierwszej kolekcji jest wyświetlany w zestawie wyników grupy join niezależnie od tego, czy skorelowane elementy znajdują się w drugiej kolekcji. W przypadku, gdy nie znaleziono skorelowanych elementów, sekwencja skorelowanych elementów dla tego elementu jest pusta. Selektor wyników ma zatem dostęp do każdego elementu pierwszej kolekcji. Różni się to od selektora wyników w grupie innej niż grupa join, która nie może uzyskać dostępu do elementów z pierwszej kolekcji, które nie mają dopasowania w drugiej kolekcji.

Ostrzeżenie

Enumerable.GroupJoin nie ma bezpośredniego odpowiednika w tradycyjnych terminach relacyjnej bazy danych. Jednak ta metoda implementuje nadzbiór sprzężeń wewnętrznych i lewe sprzężenia zewnętrzne. Oba te operacje można napisać pod względem zgrupowanego joinobiektu . Aby uzyskać więcej informacji, zobacz Entity Framework Core, GroupJoin.

W pierwszym przykładzie w tym artykule pokazano, jak wykonać grupę join. W drugim przykładzie pokazano, jak utworzyć elementy XML za pomocą grupy join .

Grupa join

Poniższy przykład wykonuje grupę join obiektów typu Department i Student na Department.ID podstawie pasującej Student.DepartmentID właściwości. W przeciwieństwie do grupy innej niż grupa join, która tworzy parę elementów dla każdego dopasowania, grupa join tworzy tylko jeden wynikowy obiekt dla każdego elementu pierwszej kolekcji, który w tym przykładzie jest obiektem Department . Odpowiednie elementy z drugiej kolekcji, które w tym przykładzie są Student obiektami, są pogrupowane w kolekcję. Na koniec funkcja selektora wyników tworzy typ anonimowy dla każdego dopasowania składającego Department.Name się z i kolekcji Student obiektów.

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}");
    }
}

W powyższym przykładzie zmienna zawiera zapytanie, które tworzy listę, query gdzie każdy element jest typem anonimowym zawierającym nazwę działu i kolekcję studentów, którzy badają się w tym dziale.

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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}");
    }
}

Grupowanie join w celu utworzenia kodu XML

Sprzężenia grup są idealne do tworzenia kodu XML przy użyciu linQ to XML. Poniższy przykład jest podobny do poprzedniego przykładu, z tą różnicą, że zamiast tworzenia typów anonimowych funkcja selektora wyników tworzy elementy XML reprezentujące sprzężone obiekty.

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);

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

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);

Wykonywanie lewych sprzężeń zewnętrznych

Lewa zewnętrzna join jest elementem join , w którym zwracany jest każdy element pierwszej kolekcji, niezależnie od tego, czy ma jakiekolwiek skorelowane elementy w drugiej kolekcji. LinQ można użyć do wykonania lewej zewnętrznej, join wywołując DefaultIfEmpty metodę w wynikach grupy join.

W poniższym przykładzie pokazano, jak użyć DefaultIfEmpty metody w wynikach grupy join do wykonania lewej zewnętrznej join.

Pierwszym krokiem tworzenia lewej zewnętrznej join dwóch kolekcji jest wykonanie wewnętrznego join przy użyciu grupy join. (Zobacz Wykonaj sprzężenia wewnętrzne, aby uzyskać wyjaśnienie tego procesu). W tym przykładzie lista Department obiektów jest przyłączona do listy Student obiektów na Department podstawie identyfikatora obiektu zgodnego z identyfikatorem ucznia DepartmentID.

Drugim krokiem jest dołączenie każdego elementu pierwszej kolekcji (po lewej) do zestawu wyników, nawet jeśli ten element nie ma dopasowań w prawej kolekcji. Jest to realizowane przez wywołanie DefaultIfEmpty każdej sekwencji pasujących elementów z grupy join. W tym przykładzie DefaultIfEmpty wywoływana jest każda sekwencja pasujących Student obiektów. Metoda zwraca kolekcję zawierającą pojedynczą, domyślną wartość, jeśli sekwencja pasujących Student obiektów jest pusta dla dowolnego Department obiektu, zapewniając, że każdy Department obiekt jest reprezentowany w kolekcji wyników.

Uwaga

Wartość domyślna typu odwołania to null; w związku z tym przykład sprawdza odwołanie o wartości null przed uzyskaniem dostępu do każdego elementu każdej Student kolekcji.

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}");
}

Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:

var query = students
    .GroupJoin(
        departments,
        student => student.DepartmentID,
        department => department.ID,
        (student, departmentList) => new { student, subgroup = departmentList })
    .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}");
}

Zobacz też