Megosztás a következőn keresztül:


Join Műveletek a LINQ-ban

Két adatforrás összekapcsolása az egyik adatforrás objektumainak társítása olyan objektumokkal, amelyek közös attribútumot használnak egy másik adatforrásban.

Fontos

Ezek a minták adatforrást System.Collections.Generic.IEnumerable<T> használnak. Adatforrások és kifejezésfák használata alapján.System.Linq.IQueryProviderSystem.Linq.IQueryable<T> A kifejezésfák korlátozottan használhatják az engedélyezett C# szintaxist. Emellett minden adatforrás IQueryProvider , például az EF Core további korlátozásokat is alkalmazhat. Ellenőrizze az adatforrás dokumentációját.

A csatlakozás olyan lekérdezések fontos művelete, amelyek olyan adatforrásokat céloznak meg, amelyek egymáshoz való viszonya közvetlenül nem követhető. Az objektumorientált programozásban az összekapcsolás olyan korrelációt jelenthet a nem modellezett objektumok között, mint például egy egyirányú kapcsolat visszafelé irányuló iránya. Az egyirányú kapcsolatokra példa egy Student osztály, amelynek típusa Department a főt jelöli, de az Department osztálynak nincs objektumgyűjteményt tartalmazó tulajdonsága Student . Ha rendelkezik Department objektumok listájával, és az egyes részlegek összes tanulóját meg szeretné találni, egy illesztési művelettel megkeresheti őket.

A LINQ-keretrendszerben megadott illesztési módszerek Join és GroupJoin. Ezek a metódusok a kulcsok egyenlősége alapján két adatforrásnak megfelelő egyenlő illesztéseket vagy illesztéseket hajtanak végre. (Összehasonlításképpen Transact-SQL támogatja a equalskívüli illesztő operátorokat, például a less than operátort.) A relációs adatbázis szempontjából a Join belső illesztés implementál, egy olyan illesztéstípust, amelyben csak azokat az objektumokat adja vissza a rendszer, amelyeknek egyezése van a többi adatkészletben. A GroupJoin metódusnak nincs közvetlen megfelelője a relációs adatbázis szempontjából, de belső illesztések és bal oldali külső illesztések szuperhalmazát valósítja meg. A bal oldali külső illesztés olyan illesztés, amely az első (bal oldali) adatforrás minden elemét visszaadja, még akkor is, ha nincsenek korrelált elemei a másik adatforrásban.

Az alábbi ábrán két halmaz és a belső illesztésben vagy bal oldali külső illesztésben szereplő elemek fogalmi nézete látható.

Két egymást átfedő kör belső/ külső.

Metódusok

Metódus neve Leírás C# lekérdezési kifejezés szintaxisa További információ
Join Két sorozatot illeszt össze a kulcsválasztó függvények alapján, és kinyeri az értékpárokat. join … in … on … equals … Enumerable.Join

Queryable.Join
GroupJoin Két sorozatot illeszt össze a kulcsválasztó függvények alapján, és csoportosítja az eredményként kapott egyezéseket az egyes elemekhez. join … in … on … equals … into … Enumerable.GroupJoin

Queryable.GroupJoin

Feljegyzés

A cikkben szereplő alábbi példák a terület közös adatforrásait használják.
Mindegyiknek Student van egy osztályszintje, egy elsődleges osztálya és egy sor pontszáma. Az A-nek Teacher is van egy City tulajdonsága, amely azonosítja azt a campust, ahol a tanár órákat tart. Az A-nek Department van egy neve, és egy olyan személyre Teacher való hivatkozás, aki az osztályvezető.
A mintaadatkészlet a forrásadattárban található.

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

Feljegyzés

A terület gyakori adatforrásai a Standard lekérdezési operátorok áttekintése című cikkben találhatóak.

Az alábbi példa a join … in … on … equals … záradékot használja két sorozat adott értéken alapuló összekapcsolásához:

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

Az előző lekérdezés metódusszintaxissal fejezhető ki az alábbi kódban látható módon:

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

Az alábbi példa az join … in … on … equals … into … záradékot használja két szekvencia összekapcsolására egy adott érték alapján, és csoportosítja az eredményül kapott egyezéseket az egyes elemek esetében.

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

Az előző lekérdezés metódusszintaxissal fejezhető ki az alábbi példában látható módon:

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

Belső illesztések végrehajtása

A relációs adatbázis szempontjából egy belső illesztés olyan eredményhalmazt hoz létre, amelyben az első gyűjtemény minden eleme egyszer megjelenik a második gyűjtemény minden egyező eleméhez. Ha az első gyűjtemény egyik eleme nem rendelkezik egyező elemekkel, az nem jelenik meg az eredményhalmazban. A Join metódus, amelyet a C# join záradéka hív meg, egy belső illesztést implementál. Az alábbi példák bemutatják, hogyan lehet véghez vinni négyféle belső illesztést.

  • Egyszerű belső illesztés, amely két adatforrás elemeit egy egyszerű kulcs alapján korrelálja.
  • Belső illesztés, amely két adatforrás elemeit korrelálja egy összetett kulcs alapján. Az összetett kulcs, amely egynél több értékből álló kulcs, lehetővé teszi az elemek egynél több tulajdonságon alapuló korrelációját.
  • Egy többszörös illesztés, amelyben egymást követő illesztési műveletek egymáshoz kapcsolódnak.
  • Egy belső illesztés, amelyet csoportillesztés használatával valósítunk meg.

Egykulcsos összekapcsolás

Az alábbi példa olyan objektumokkal egyezik Teacher meg, amelyek Department megegyeznek a TeacherIdkövetkezővelTeacher. A select C# záradéka határozza meg az eredményként kapott objektumok megjelenését. Az alábbi példában az eredményül kapott objektumok névtelen típusok, amelyek a részleg nevét és a részleget vezető tanár nevét tartalmazzák.

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

Ugyanezeket az eredményeket a metódus szintaxisával Join érheti el:

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

Azok a tanárok, akik nem osztályvezetők, nem jelennek meg a végeredményben.

Összetett kulcs illesztése

Az elemek egyetlen tulajdonságon alapuló korrelációja helyett összetett kulccsal összehasonlíthatja az elemeket több tulajdonság alapján. Adja meg az egyes gyűjtemények kulcsválasztó függvényét egy névtelen típus visszaadásához, amely az összehasonlítandó tulajdonságokból áll. Ha megjelöli a tulajdonságokat, mindegyik kulcs névtelen típusában ugyanazzal a címkével kell rendelkeznie. A tulajdonságoknak ugyanabban a sorrendben is meg kell jelenniük.

Az alábbi példa egy objektumlistát Teacher és egy objektumlistát Student használ annak meghatározására, hogy mely tanárok is diákok. Mindkét típus rendelkezik olyan tulajdonságokkal, amelyek az egyes személyek vezeték- és családnevét jelölik. Az illesztőkulcsokat az egyes listaelemekből létrehozó függvények névtelen típust adnak vissza, amely a tulajdonságokból áll. Az illesztési művelet összehasonlítja ezeket az összetett kulcsokat az egyenlőség szempontjából, és minden listából visszaadja az objektumpárokat, ahol az utónév és a családnév is egyezik.

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

A metódust az Join alábbi példában látható módon használhatja:

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

Több illesztés

Tetszőleges számú illesztési művelet hozzáfűzhető egymáshoz több illesztés végrehajtásához. A C# minden join záradéka egy adott adatforrást korrelál az előző illesztés eredményeivel.

Az első join záradék egy objektum objektumának megfelelő Student objektum DepartmentID alapján Department egyezik a tanulókkal és a részlegekévalID. Az objektumot és Student objektumot Department tartalmazó névtelen típusok sorozatát adja vissza.

A második join záradék korrelálja az első illesztés által visszaadott névtelen típusokat Teacher objektumokkal az osztályvezető azonosítójával egyező tanári azonosító alapján. Névtelen típusokat ad vissza, amelyek tartalmazzák a tanuló nevét, a részleg nevét és a részlegvezető nevét. Mivel ez a művelet egy belső illesztés, a rendszer csak azokat az objektumokat adja vissza, amelyek az első adatforrásból származnak, és megegyeznek a második adatforrásban.

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

A több Join metódust használó egyenértékű módszer ugyanazt a megközelítést használja a névtelen típussal:

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

Belső illesztés csoportosított csatlakozással

Az alábbi példa bemutatja, hogyan valósíthat meg belső illesztéseket csoportillesztés használatával. Az objektumok listája Department a tulajdonságnak megfelelő Student objektumok listájához Department.IDStudent.DepartmentID van csoportosítva. A csoportillesztés köztes csoportok gyűjteményét hozza létre, ahol minden csoport egy Department objektumból és egy egyező Student objektumok sorozatából áll. A második from záradék ezt a sorozatot egy hosszabb sorozatba egyesíti (vagy simítja). A select záradék a végső sorrend elemeinek típusát határozza meg. Ez a típus egy névtelen típus, amely a tanuló nevéből és az egyező részleg nevéből áll.

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

Ugyanezek az eredmények a következő módszerrel érhetők el GroupJoin :

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

Az eredmény megegyezik azzal az eredményhalmazsal, amelyet az join záradék használatával kapott a belső illesztés végrehajtásához szükséges into záradék nélkül. Az alábbi kód ezt az egyenértékű lekérdezést mutatja be:

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

A láncolás elkerülése érdekében az itt bemutatott egyetlen Join módszer használható:

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

Csoportosított illesztések végrehajtása

A csoportillesztés hierarchikus adatstruktúrák létrehozásához hasznos. Párosítja az első gyűjtemény egyes elemeit a második gyűjtemény korrelált elemeivel.

Feljegyzés

Az első gyűjtemény minden eleme megjelenik egy csoportillesztés eredményhalmazában, függetlenül attól, hogy a korrelált elemek megtalálhatók-e a második gyűjteményben. Abban az esetben, ha nem találhatók korrelált elemek, az adott elemhez tartozó korrelált elemek sorrendje üres. Az eredményválasztó ezért hozzáfér az első gyűjtemény minden eleméhez. Ez eltér a nem csoporthoz tartozó illesztés eredményválasztójától, amely nem fér hozzá az első gyűjtemény azon elemeihez, amelyek nem egyeznek a második gyűjteményben.

Figyelmeztetés

Enumerable.GroupJoin nincs közvetlen egyenértékű a hagyományos relációs adatbázis szempontjából. Ez a módszer azonban a belső illesztések és a bal oldali külső illesztések szuperhalmazát implementálja. Mindkét művelet csoportosított illesztésként írható. További információ: Entity Framework Core, GroupJoin.

A cikk első példája bemutatja, hogyan végezhet csoportillesztéseket. A második példa bemutatja, hogyan hozhat létre XML-elemeket csoportillesztés használatával.

Csoporthoz való csatlakozás

Az alábbi példa a Department-nak a Student tulajdonsággal való egyezése alapján végzi el a Department.ID és Student.DepartmentID típusú objektumok csoportos összekapcsolását. A nem csoportos illesztéssel ellentétben, amely minden egyes egyezéshez létrehoz egy pár elemet, a csoportillesztés csak egy eredményt adó objektumot hoz létre az első gyűjtemény minden eleméhez, amely ebben a példában egy Department objektum. A második gyűjtemény megfelelő elemei, amelyek ebben a példában Student objektumok, gyűjteménybe vannak csoportosítva. Végül az eredményválasztó függvény létrehoz egy névtelen típust az egyes egyezésekhez, amelyekből és objektumok gyűjteményéből Department.NameStudent áll.

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

A fenti példában a változó azt a lekérdezést tartalmazza, amely létrehoz egy listát, query amelyben minden elem egy névtelen típus, amely tartalmazza a részleg nevét és a részlegen tanuló diákok gyűjteményét.

A metódusszintaxissal egyenértékű lekérdezés a következő kódban jelenik meg:

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

Csoportillesztés XML létrehozásához

A csoportillesztések ideálisak az XML létrehozásához a LINQ és az XML használatával. Az alábbi példa az előző példához hasonló, azzal a kivételrel, hogy névtelen típusok létrehozása helyett az eredményválasztó függvény xml-elemeket hoz létre, amelyek az összekapcsolt objektumokat jelölik.

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

A metódusszintaxissal egyenértékű lekérdezés a következő kódban jelenik meg:

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

Bal oldali külső illesztések végrehajtása

A bal oldali külső illesztés olyan illesztés, amelyben az első gyűjtemény minden eleme vissza lesz adva, függetlenül attól, hogy a második gyűjteményben vannak-e korrelált elemei. A LINQ használatával bal oldali külső illesztést hajthat végre úgy, hogy meghívja a DefaultIfEmpty metódust a csoportillesztés eredményein.

Az alábbi példa bemutatja, hogyan használhatja a DefaultIfEmpty metódust egy csoportillesztés eredményein a bal oldali külső illesztés végrehajtásához.

Két gyűjtemény bal oldali külső illesztésének első lépése egy belső illesztés csoportillesztés használatával történő végrehajtása. (Lásd:Belső illesztések végrehajtása a folyamat magyarázatához.) Ebben a példában az objektumok listája Department egy olyan objektumazonosító alapján Student csatlakozik az objektumok listájáhozDepartment, amely megfelel a tanulóénakDepartmentID.

A második lépés az, hogy az első (bal oldali) gyűjtemény minden elemét belefoglalja az eredményhalmazba, még akkor is, ha az elemnek nincs egyezése a megfelelő gyűjteményben. Ez úgy érhető el, hogy meghívja DefaultIfEmpty a csoportillesztés egyes egyező elemeinek mindegyik sorozatára. Ebben a példában DefaultIfEmpty a program meghívja az egyező Student objektumok mindegyik sorozatát. A metódus egy olyan gyűjteményt ad vissza, amely egyetlen alapértelmezett értéket tartalmaz, ha az egyező Student objektumok sorozata üres bármely Department objektum esetében, biztosítva, hogy minden Department objektum szerepeljen az eredménygyűjteményben.

Feljegyzés

A referenciatípus nullalapértelmezett értéke tehát a null értékű hivatkozás, mielőtt hozzáfér az egyes Student gyűjtemények egyes elemeihez.

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

A metódusszintaxissal egyenértékű lekérdezés a következő kódban jelenik meg:

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 ?? string.Empty
        });

foreach (var v in query)
{
    Console.WriteLine($"{v.FirstName:-15} {v.LastName:-15}: {v.Department}");
}

Lásd még