Join Bewerkingen in LINQ
Een join van twee gegevensbronnen is de koppeling van objecten in de ene gegevensbron met objecten die een gemeenschappelijk kenmerk in een andere gegevensbron delen.
Belangrijk
In deze voorbeelden wordt een System.Collections.Generic.IEnumerable<T> gegevensbron gebruikt. Gegevensbronnen op System.Linq.IQueryProvider basis van het gebruik van System.Linq.IQueryable<T> gegevensbronnen en expressiestructuren. Expressiestructuren hebben beperkingen voor de toegestane C#-syntaxis. Bovendien kan elke IQueryProvider
gegevensbron, zoals EF Core , meer beperkingen opleggen. Raadpleeg de documentatie voor uw gegevensbron.
Samenvoegen is een belangrijke bewerking in query's die zijn gericht op gegevensbronnen waarvan de relaties met elkaar niet rechtstreeks kunnen worden gevolgd. Bij objectgeoriënteerde programmering kan samenvoegen een correlatie betekenen tussen objecten die niet zijn gemodelleerd, zoals de achteruitrichting van een eenrichtingsrelatie. Een voorbeeld van een eenrichtingsrelatie is een Student
klasse met een eigenschap van het type Department
dat de primaire waarde vertegenwoordigt, maar de Department
klasse heeft geen eigenschap die een verzameling Student
objecten is. Als u een lijst Department
met objecten hebt en u alle studenten in elke afdeling wilt vinden, kunt u een join bewerking gebruiken om ze te vinden.
De join methoden die in het LINQ-framework worden geboden, zijn Join en GroupJoin. Met deze methoden worden equijoins uitgevoerd of samengevoegd die overeenkomen met twee gegevensbronnen op basis van gelijkheid van hun sleutels. (Ter vergelijking ondersteunt join Transact-SQL andere operators dan equals
bijvoorbeeld de less than
operator.) In relationele databasetermen Join implementeert u een innerlijk join, een type waarin join alleen de objecten met een overeenkomst in de andere gegevensset worden geretourneerd. De GroupJoin methode heeft geen direct equivalent in relationele databasetermen, maar implementeert een superset van inner joins en left outer joins. Een left outer join is een join die elk element van de eerste (linker) gegevensbron retourneert, zelfs als er geen gecorreleerde elementen in de andere gegevensbron zijn.
In de volgende afbeelding ziet u een conceptuele weergave van twee sets en de elementen in die sets die zijn opgenomen in een binnenste join of linkerste.join
Methoden
Methodenaam | Beschrijving | Syntaxis van C#-queryexpressie | Meer informatie |
---|---|---|---|
Join | Hiermee worden twee reeksen samengevoegd op basis van sleutelkiezerfuncties en worden paren waarden geëxtraheerd. | join … in … on … equals … |
Enumerable.Join Queryable.Join |
GroupJoin | Voegt twee reeksen samen op basis van sleutelkiezerfuncties en groepeert de resulterende overeenkomsten voor elk element. | join … in … on … equals … into … |
Enumerable.GroupJoin Queryable.GroupJoin |
Notitie
In de volgende voorbeelden in dit artikel worden de algemene gegevensbronnen voor dit gebied gebruikt.
Elk Student
heeft een cijferniveau, een primaire afdeling en een reeks scores. Een Teacher
heeft ook een City
eigenschap die de campus identificeert waar de docent klassen heeft. A Department
heeft een naam en een verwijzing naar een Teacher
persoon die als afdelingshoofd fungeert.
U vindt de voorbeeldgegevensset in de bronopslagplaats.
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; }
}
In het volgende voorbeeld wordt de join … in … on … equals …
component gebruikt voor join twee reeksen op basis van een specifieke waarde:
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}");
}
De voorgaande query kan worden uitgedrukt met behulp van methodesyntaxis, zoals wordt weergegeven in de volgende code:
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}");
}
In het volgende voorbeeld wordt de join … in … on … equals … into …
component gebruikt voor join twee reeksen op basis van specifieke waarde en worden de resulterende overeenkomsten voor elk element gegroepeerd:
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}");
}
}
De voorgaande query kan worden uitgedrukt met behulp van methodesyntaxis, zoals wordt weergegeven in het volgende voorbeeld:
// 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}");
}
}
Inner joins uitvoeren
In relationele databasetermen produceert een inner join een resultatenset waarin elk element van de eerste verzameling één keer wordt weergegeven voor elk overeenkomend element in de tweede verzameling. Als een element in de eerste verzameling geen overeenkomende elementen bevat, wordt dit niet weergegeven in de resultatenset. De Join methode, die wordt aangeroepen door de join
component in C#, implementeert een inner join. In de volgende voorbeelden ziet u hoe u vier variaties van een binnenste joinkunt uitvoeren:
- Een eenvoudige inner join die elementen uit twee gegevensbronnen correleert op basis van een eenvoudige sleutel.
- Een inner join die elementen uit twee gegevensbronnen correleert op basis van een samengestelde sleutel. Met een samengestelde sleutel, een sleutel die uit meer dan één waarde bestaat, kunt u elementen correleren op basis van meer dan één eigenschap.
- Een veelvoud join waarin opeenvolgende join bewerkingen aan elkaar worden toegevoegd.
- Een binnenste join die wordt geïmplementeerd met behulp van een groep join.
Eén sleutel join
Het volgende voorbeeld komt overeen Teacher
met objecten met Department
objecten waarvan TeacherId
deze Teacher
overeenkomt. De select
component in C# definieert hoe de resulterende objecten eruitzien. In het volgende voorbeeld zijn de resulterende objecten anonieme typen die bestaan uit de afdelingsnaam en de naam van de docent die de afdeling leidt.
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}");
}
U bereikt dezelfde resultaten met behulp van de syntaxis van de Join methode:
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}");
}
De docenten die geen afdelingshoofden zijn, worden niet weergegeven in de eindresultaten.
Samengestelde sleutel join
In plaats van elementen te correleren op basis van slechts één eigenschap, kunt u een samengestelde sleutel gebruiken om elementen te vergelijken op basis van meerdere eigenschappen. Geef de sleutelkiezerfunctie op voor elke verzameling om een anoniem type te retourneren dat bestaat uit de eigenschappen die u wilt vergelijken. Als u de eigenschappen labelt, moeten ze hetzelfde label hebben in het anonieme type van elke sleutel. De eigenschappen moeten ook in dezelfde volgorde worden weergegeven.
In het volgende voorbeeld wordt een lijst Teacher
met objecten en een lijst Student
met objecten gebruikt om te bepalen welke docenten ook leerlingen/studenten zijn. Beide typen hebben eigenschappen die de voor- en familienaam van elke persoon vertegenwoordigen. De functies waarmee de sleutels worden gemaakt op basis van de join elementen van elke lijst, retourneren een anoniem type dat uit de eigenschappen bestaat. De join bewerking vergelijkt deze samengestelde sleutels voor gelijkheid en retourneert paren van objecten uit elke lijst waarbij zowel de voornaam als de familienaam overeenkomen.
// 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);
U kunt de Join methode gebruiken, zoals wordt weergegeven in het volgende voorbeeld:
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);
}
Veelvoud join
Een willekeurig aantal join bewerkingen kan aan elkaar worden toegevoegd om een veelvoud joinuit te voeren. Elke join
component in C# correleert een opgegeven gegevensbron met de resultaten van de vorige join.
De eerste join
component komt overeen met studenten en afdelingen op basis van de overeenkomsten Department
van DepartmentID
ID
een Student
object. Het retourneert een reeks anonieme typen die het Student
object en Department
object bevatten.
De tweede join
component correleert de anonieme typen die door de eerste join worden geretourneerd met Teacher
objecten op basis van de id van die docent die overeenkomen met de afdelingshoofd-id. Het retourneert een reeks anonieme typen die de naam van de student, de naam van de afdeling en de naam van de afdelingsleider bevatten. Omdat deze bewerking een interne joinbewerking is, worden alleen die objecten uit de eerste gegevensbron geretourneerd die een overeenkomst hebben in de tweede gegevensbron.
// 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}".""");
}
Het equivalent met meerdere Join methoden gebruikt dezelfde benadering met het anonieme type:
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}".""");
}
Binnenkant join met behulp van gegroepeerd join
In het volgende voorbeeld ziet u hoe u een inner join implementeert met behulp van een groep join. De lijst Department
met objecten wordt gegroepeerd aan de lijst met objecten op basis van Student
de Department.ID
overeenkomende Student.DepartmentID
eigenschap. De groep join maakt een verzameling tussenliggende groepen, waarbij elke groep bestaat uit een Department
object en een reeks overeenkomende Student
objecten. De tweede from
component combineert (of vlakt) deze reeks reeksen in één langere reeks. De select
component specificeert het type elementen in de uiteindelijke volgorde. Dit type is een anoniem type dat bestaat uit de naam van de student en de overeenkomende afdelingsnaam.
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}");
}
Dezelfde resultaten kunnen als volgt worden bereikt met behulp van GroupJoin de methode:
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}");
}
Het resultaat is gelijk aan de resultatenset die is verkregen met behulp van de join
component zonder de into
component om een inner uit jointe voeren. De volgende code demonstreert deze equivalente query:
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}");
}
Om ketens te voorkomen, kan de ene Join methode worden gebruikt zoals hier wordt weergegeven:
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}");
}
Gegroepeerde joins uitvoeren
De groep join is handig voor het produceren van hiërarchische gegevensstructuren. Het paren van elk element uit de eerste verzameling met een set gecorreleerde elementen uit de tweede verzameling.
Notitie
Elk element van de eerste verzameling wordt weergegeven in de resultatenset van een groep join , ongeacht of gecorreleerde elementen worden gevonden in de tweede verzameling. In het geval dat er geen gecorreleerde elementen worden gevonden, is de volgorde van gecorreleerde elementen voor dat element leeg. De resultaatkiezer heeft daarom toegang tot elk element van de eerste verzameling. Dit verschilt van de resultaatkiezer in een niet-groep join, die geen toegang heeft tot elementen uit de eerste verzameling die geen overeenkomst hebben in de tweede verzameling.
Waarschuwing
Enumerable.GroupJoin heeft geen direct equivalent in traditionele relationele databasetermen. Deze methode implementeert echter wel een superset van inner joins en left outer joins. Beide bewerkingen kunnen worden geschreven in termen van een gegroepeerde joinbewerking. Zie Entity Framework Core, GroupJoinvoor meer informatie.
In het eerste voorbeeld in dit artikel ziet u hoe u een groep joinuitvoert. In het tweede voorbeeld ziet u hoe u een groep join gebruikt om XML-elementen te maken.
Groep join
In het volgende voorbeeld wordt een groep join objecten van het type Department
uitgevoerd en Student
op basis van de Department.ID
overeenkomende Student.DepartmentID
eigenschap. In tegenstelling tot een niet-groep join, die voor elke overeenkomst een paar elementen produceert, produceert de groep join slechts één resulterend object voor elk element van de eerste verzameling, dat in dit voorbeeld een Department
object is. De bijbehorende elementen uit de tweede verzameling, die in dit voorbeeld objecten zijn Student
, worden gegroepeerd in een verzameling. Ten slotte maakt de functie resultaatkiezer een anoniem type voor elke overeenkomst die bestaat uit Department.Name
en een verzameling Student
objecten.
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}");
}
}
In het bovenstaande voorbeeld query
bevat de variabele de query waarmee een lijst wordt gemaakt waarin elk element een anoniem type is dat de naam van de afdeling bevat en een verzameling studenten die op die afdeling studeren.
De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:
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}");
}
}
Groep join voor het maken van XML
Groepsdeelnames zijn ideaal voor het maken van XML met behulp van LINQ naar XML. Het volgende voorbeeld is vergelijkbaar met het vorige voorbeeld, behalve dat in plaats van anonieme typen te maken, de functie resultaatkiezer XML-elementen maakt die de gekoppelde objecten vertegenwoordigen.
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);
De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:
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);
Left outer joins uitvoeren
Een left outer join is een join element waarin elk element van de eerste verzameling wordt geretourneerd, ongeacht of het gecorreleerde elementen in de tweede verzameling bevat. U kunt LINQ gebruiken om een left outer uit join te voeren door de DefaultIfEmpty methode aan te roepen voor de resultaten van een groep join.
In het volgende voorbeeld ziet u hoe u de DefaultIfEmpty methode gebruikt voor de resultaten van een groep join om een left outer joinuit te voeren.
De eerste stap bij het produceren van een left outer join van twee verzamelingen is het uitvoeren van een inner join met behulp van een groep join. (Zie Voer inner joins uit voor een uitleg van dit proces.) In dit voorbeeld wordt de lijst Department
met objecten binnenste gekoppeld aan de lijst met objecten op basis van de id van Student
een Department
object die overeenkomt met de student DepartmentID
.
De tweede stap bestaat uit het opnemen van elk element van de eerste (linker) verzameling in de resultatenset, zelfs als dat element geen overeenkomsten bevat in de juiste verzameling. Dit wordt bereikt door elke reeks overeenkomende elementen uit de groep joinaan te roepenDefaultIfEmpty. In dit voorbeeld DefaultIfEmpty wordt elke reeks overeenkomende Student
objecten aangeroepen. De methode retourneert een verzameling die één standaardwaarde bevat als de reeks overeenkomende Student
objecten leeg is voor een Department
object, zodat elk Department
object wordt weergegeven in de resultatenverzameling.
Notitie
De standaardwaarde voor een verwijzingstype is null
; daarom controleert het voorbeeld op een null-verwijzing voordat elk element van elke Student
verzameling wordt geopend.
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}");
}
De equivalente query met behulp van de methodesyntaxis wordt weergegeven in de volgende code:
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}");
}