Join Opérations dans LINQ
Une join de deux sources de données est l’association des objets d’une source de données aux objets qui partagent un attribut commun dans une autre source de données.
Important
Ces exemples utilisent une source de données System.Collections.Generic.IEnumerable<T>. Les sources de données basées sur System.Linq.IQueryProvider utilisent des sources de données System.Linq.IQueryable<T> et des arborescences d’expressions. Les arborescences d’expressions présentent des limitations sur la syntaxe C# autorisée. De plus, chaque source de données IQueryProvider
, telle que EF Core peut imposer des restrictions supplémentaires. Consultez la documentation de votre source de données.
La jointure est une opération importante dans les requêtes qui ciblent les sources de données dont les relations ne peuvent pas être suivies directement. En programmation orientée objet, une jointure peut signifier une corrélation entre objets qui n’est pas modélisée, par exemple le sens inverse d’une relation unidirectionnelle. Voici un exemple de relation unidirectionnelle : une classe Student
a une propriété de type Department
qui représente l’élément majeur, mais la classe Department
n’a pas de propriété correspondant à une collection d’objets Student
. Si vous avez une liste d'objets Department
et si vous souhaitez rechercher tous les étudiants de chaque département, vous pouvez recourir à une opération de join.
Les méthodes de join fournies dans le framework LINQ sont Join et GroupJoin. Ces méthodes effectuent des équijointures, qui sont des jointures associant deux sources de données en fonction de l’égalité de leurs clés. (À titre de comparaison, Transact-SQL prend en charge des opérateurs join autres que equals
, par exemple, l’opérateur less than
.) En termes de base de données relationnelle, Join implémente un join interne, un type de join dans lequel seuls les objets qui ont une correspondance dans l’autre jeu de données sont renvoyés. La méthode GroupJoin n’a aucun équivalent direct dans le contexte des bases de données relationnelles, mais elle implémente un sur-ensemble de jointures internes et de jointures externes gauches. Une join externe gauche est une join qui retourne chaque élément de la source de données (gauche), même si elle n’a pas d’éléments corrélés dans l’autre source de données.
L'illustration suivante présente une vue conceptuelle de deux ensembles, ainsi que leurs éléments inclus dans une join interne ou une join externe gauche.
Méthodes
Nom de la méthode | Description | Syntaxe d'expression de requête C# | Informations supplémentaires |
---|---|---|---|
Join | Joint deux séquences selon les fonctions de sélection de clé et extrait des paires de valeurs. | join … in … on … equals … |
Enumerable.Join Queryable.Join |
GroupJoin | Joint deux séquences selon les fonctions de sélection de clé et regroupe les résultats correspondants pour chaque élément. | join … in … on … equals … into … |
Enumerable.GroupJoin Queryable.GroupJoin |
Remarque
Les exemples suivants de cet article utilisent les sources de données courantes pour cette zone.
Chaque Student
a un niveau scolaire, un département principal et une série de notes. Un Teacher
a également une propriété City
qui identifie le campus où l’enseignant donne des cours. Un Department
a un nom, et une référence à un Teacher
qui est responsable du département.
Vous trouverez l’exemple de jeu de données dans le référentiel source.
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; }
}
L’exemple suivant utilise la clause join … in … on … equals …
pour join deux séquences basées sur une valeur spécifique :
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}");
}
La requête précédente peut être exprimée en utilisant la syntaxe de méthode, comme illustré dans le code suivant :
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}");
}
L’exemple suivant utilise la clause join … in … on … equals … into …
pour join deux séquences basées sur une valeur spécifique et regroupe les correspondances obtenues pour chaque élément :
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}");
}
}
La requête précédente peut être exprimée en utilisant la syntaxe de méthode, comme illustré dans le code suivant :
// 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}");
}
}
Effectuer des jointures internes
Dans le domaine des bases de données relationnelles, une join interne produit un jeu de résultats dans lequel chaque élément de la première collection apparaît une fois pour chaque élément correspondant dans la deuxième collection. Si un élément de la première collection n’a pas d’éléments correspondants, il n’apparaît pas dans le jeu de résultats. La méthode Join, qui est appelée par la clause join
en C#, implémente une join interne. Les exemples suivants vous montrent comment effectuer quatre variations d’une join interne :
- Une join interne simple qui met en corrélation des éléments de deux sources de données sur la base d’une clé simple.
- Une join interne qui met en corrélation des éléments de deux sources de données sur la base d’une clé composite. Une clé composite, qui est une clé composée de plusieurs valeurs, permet de mettre en corrélation des éléments sur la base de plusieurs propriétés.
- Une join multiple dans laquelle les opérations de join consécutives sont ajoutées les unes aux autres.
- Une jointure join qui est implémentée à l’aide d’une join groupée.
join à clé unique
L’exemple suivant trouve les objets Teacher
avec des objets Department
dont TeacherId
correspond à Teacher
. La clause select
dans C# définit l’apparence des objets résultants. Dans l’exemple suivant, les objets résultants sont des types anonymes qui se composent du nom du département et du nom de l’enseignant qui dirige le département.
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}");
}
Vous obtenez les mêmes résultats à l’aide de la syntaxe de méthode Join :
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}");
}
Les enseignants qui ne sont pas chefs de département n’apparaissent pas dans les résultats finaux.
join à clé composite
Au lieu de mettre en corrélation des éléments sur la base d’une seule propriété, vous pouvez utiliser une clé composite pour comparer des éléments en fonction de plusieurs propriétés. Spécifiez la fonction de sélecteur de clé pour chaque collection pour retourner un type anonyme qui se compose des propriétés que vous voulez comparer. Si vous étiquetez les propriétés, elles doivent avoir la même étiquette dans le type anonyme de chaque clé. Les propriétés doivent également apparaître dans le même ordre.
L’exemple suivant utilise une liste d’objets Teacher
et une liste d’objets Student
pour déterminer quels enseignants sont également étudiants. Ces deux types ont des propriétés qui représentent le prénom et le nom de famille de chaque personne. Les fonctions qui créent les clés de join à partir des éléments de chaque liste retournent un type anonyme qui se compose des propriétés. L’opération de join effectue une comparaison d’égalité de ces clés composites et retourne les paires d’objets de chaque liste où il y a correspondance entre le prénom et le nom.
// 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);
Vous pouvez utiliser la méthode Join, comme illustré dans l’exemple suivant :
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);
}
join multiple
Vous pouvez effectuer une join multiple en ajoutant n’importe quel nombre d’opérations de join les unes aux autres. Chaque clause join
dans C# met en corrélation une source de données spécifiée avec les résultats de la join précédente.
La première clause join
trouve les étudiants et les départements en fonction de la correspondance du DepartmentID
d’un objet Student
avec l’ID
d’un objet Department
. Elle retourne une séquence de types anonymes qui contiennent l’objet Student
et l’objet Department
.
La deuxième clause join
met en corrélation les types anonymes renvoyés par la première join avec des objets Teacher
basés sur l’ID de cet enseignant correspondant à l’ID du chef de département. Elle retourne une séquence de types anonymes qui contiennent le nom de l’étudiant, le nom du département et le nom du chef du département. Comme il s’agit d’une join interne, seuls les objets de la première source de données qui ont une correspondance dans la deuxième source de données sont retournés.
// 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}".""");
}
L’équivalent utilisant plusieurs méthodes Join utilise la même approche avec le type anonyme :
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 interne à l’aide d’une join regroupée
L’exemple suivant montre comment implémenter une join interne en utilisant une join groupée. La liste d’objets Department
est jointe par groupe à la liste d’objets Student
sur la base du Department.ID
correspondant à la propriété Student.DepartmentID
. La join groupée crée une collection de groupes intermédiaires où chaque groupe se compose d’un objet Department
et d’une séquence d’objets Student
correspondants. La deuxième clause from
combine (ou aplatit) cette séquence de séquences en une séquence plus longue. La clause select
spécifie le type d’éléments dans la séquence finale. Ce type est un type anonyme qui se compose du nom de l’étudiant et du nom du département correspondant.
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}");
}
Les mêmes résultats peuvent être obtenus à l’aide de GroupJoin la méthode suivante :
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}");
}
Le résultat est équivalent au jeu de résultats qui aurait été obtenu en utilisant la clause join
sans la clause into
pour effectuer une join interne. Le code suivant montre cette requête équivalente :
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}");
}
Pour éviter le chaînage, la méthode unique Join peut être utilisée comme indiqué ici :
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}");
}
Effectuer des jointures groupées
La join groupée est utile pour produire des structures de données hiérarchiques. Elle associe chaque élément de la première collection à un jeu d’éléments corrélés de la deuxième collection.
Remarque
Chaque élément de la première collection apparaît dans le jeu de résultats d’une join groupée, même si des éléments corrélés sont trouvés dans la deuxième collection. Si aucun élément corrélé n’est trouvé, la séquence d’éléments corrélés pour cet élément est vide. Le sélecteur de résultats a donc accès à chaque élément de la première collection. Cela n’est pas le cas du sélecteur de résultats dans une join non groupée, qui ne peut pas accéder à des éléments de la première collection qui n’ont aucune correspondance dans la deuxième collection.
Avertissement
Enumerable.GroupJoin n’a pas d’équivalent direct dans les termes de base de données relationnelle traditionnels. Toutefois, cette méthode implémente un sur-ensemble de jointures internes et de jointures externes gauches. Ces deux opérations peuvent être écrites en termes de join groupée. Pour plus d’informations, consultez Entity Framework Core, GroupJoin.
Le premier exemple de cet article montre comment effectuer une join groupée. Le deuxième exemple montre comment utiliser une join groupée pour créer des éléments XML.
join groupée
L’exemple suivant effectue une join groupée d’objets de types Department
et Student
où Department.ID
correspond à la propriété Student.DepartmentID
. Contrairement à une join non groupée qui produit une paire d’éléments pour chaque correspondance, la join groupée produit un seul objet résultant pour chaque élément de la première collection, qui est un objet Department
dans cet exemple. Les éléments correspondants de la deuxième collection, qui sont des objets Student
dans cet exemple, sont regroupés dans une collection. Enfin, le sélecteur de résultats crée un type anonyme pour chaque correspondance qui se compose de Department.Name
et d’une collection d’objets 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}");
}
}
Dans l’exemple ci-dessus, la variable query
contient la requête qui crée une liste où chaque élément est un type anonyme qui contient le nom du département et une collection d’étudiants qui y étudient.
La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :
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}");
}
}
join groupée pour créer des éléments XML
Les jointures groupées sont appropriées pour créer des éléments XML à l’aide de LINQ to XML. L’exemple suivant est similaire à l’exemple précédent, sauf qu’au lieu de créer des types anonymes, le sélecteur de résultats crée des éléments XML qui représentent les objets joints.
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);
La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :
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);
Effectuer des jointures externes gauches
Une join externe gauche est une join dans laquelle chaque élément de la première collection est retourné, qu’elle ait ou non des éléments corrélés dans la deuxième collection. Vous pouvez utiliser LINQ pour effectuer une join externe gauche en appelant la méthode DefaultIfEmpty sur les résultats d’une join groupée.
L’exemple suivant montre comment utiliser la méthode DefaultIfEmpty sur les résultats d’une join groupée pour effectuer une join externe gauche.
Pour créer une join externe gauche entre deux collections, la première étape consiste à effectuer une join interne à l’aide d’une join groupée. (Pour savoir comment faire, consultez Effectuer des jointures internes.) Dans cet exemple, la liste d’objets Department
se voit appliquer une jointure interne avec la liste d’objets Student
sur la base de la correspondance entre l’ID d’un objet Department
et le DepartmentID
d’un étudiant.
La deuxième étape consiste à inclure tous les éléments de la première collection (celle de gauche) dans le jeu de résultats, y compris les éléments sans correspondance dans la collection de droite. Pour cela, appelez DefaultIfEmpty sur chaque séquence d’éléments correspondants de la join groupée. Dans cet exemple, la méthode DefaultIfEmpty est appelée sur chaque séquence d’objets Student
correspondants. La méthode retourne une collection qui contient une seule valeur par défaut si la séquence des objets Student
correspondant à un objet Department
est vide, ce qui garantit que chaque objet Department
est représenté dans la collection de résultats.
Remarque
La valeur par défaut pour un type référence est null
. L’exemple recherche donc une référence null avant d’accéder à chaque élément de chaque collection 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}");
}
La requête équivalente utilisant la syntaxe de méthode est illustrée dans le code suivant :
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}");
}