Łą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
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.
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.
C#
publicenum GradeLevel
{
FirstYear = 1,
SecondYear,
ThirdYear,
FourthYear
};
publicclassStudent
{
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; }
}
publicclassTeacher
{
public required string First { get; init; }
public required string Last { get; init; }
public required int ID { get; init; }
public required string City { get; init; }
}
publicclassDepartment
{
public required string Name { get; init; }
publicint 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:
C#
var query = from student in students
join department in departments on student.DepartmentID equals department.ID
selectnew { 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:
C#
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:
C#
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:
C#
// 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ł.
C#
var query = from department in departments
join teacher in teachers on department.TeacherID equals teacher.ID
selectnew
{
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:
C#
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.
C#
// 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 onnew
{
FirstName = teacher.First,
LastName = teacher.Last
} equalsnew
{
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:
C#
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 DepartmentIDID. 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.
C#
// 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
selectnew {
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:
C#
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.
C#
var query1 =
from department in departments
join student in students on department.ID equals student.DepartmentID into gj
from subStudent in gj
selectnew
{
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:
C#
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:
C#
var query2 = from department in departments
join student in students on department.ID equals student.DepartmentID
selectnew
{
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:
C#
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.
C#
var query = from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
selectnew
{
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:
C#
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.
C#
XElement departmentsAndStudents = new("DepartmentEnrollment",
from department in departments
join student in students on department.ID equals student.DepartmentID into studentGroup
selectnewXElement("Department",
new XAttribute("Name", department.Name),
from student in studentGroup
selectnewXElement("Student",
new XAttribute("FirstName", student.FirstName),
newXAttribute("LastName", student.LastName)
)
)
);
Console.WriteLine(departmentsAndStudents);
Równoważne zapytanie używające składni metody jest wyświetlane w następującym kodzie:
C#
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
selectnewXElement("Student",
new XAttribute("FirstName", student.FirstName),
newXAttribute("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.
C#
var query =
from student in students
join department in departments on student.DepartmentID equals department.ID into gj
from subgroup in gj.DefaultIfEmpty()
selectnew
{
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:
C#
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}");
}
Źródło tej zawartości można znaleźć w witrynie GitHub, gdzie można również tworzyć i przeglądać problemy i żądania ściągnięcia. Więcej informacji znajdziesz w naszym przewodniku dla współtwórców.
Opinia o produkcie .NET
.NET to projekt typu open source. Wybierz link, aby przekazać opinię:
Dołącz do serii meetup, aby tworzyć skalowalne rozwiązania sztucznej inteligencji oparte na rzeczywistych przypadkach użycia z innymi deweloperami i ekspertami.