Delen via


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 equalsbijvoorbeeld 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

Twee overlappende cirkels met binnenste/ buiten.

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

Zie ook