Join Operace v LINQ
A join ze dvou zdrojů dat je přidružení objektů v jednom zdroji dat k objektům, které sdílejí společný atribut v jiném zdroji dat.
Důležité
Tyto ukázky používají System.Collections.Generic.IEnumerable<T> zdroj dat. Zdroje dat založené na System.Linq.IQueryProvider použití System.Linq.IQueryable<T> zdrojů dat a stromů výrazů Stromy výrazů mají omezení povolené syntaxe jazyka C#. Každý zdroj dat, například EF Core, IQueryProvider
může navíc uplatňovat další omezení. Projděte si dokumentaci ke zdroji dat.
Spojení je důležitou operací v dotazech, které cílí na zdroje dat, jejichž vztahy mezi sebou nelze sledovat přímo. V objektově orientovaném programování by spojení mohlo znamenat korelaci mezi objekty, které nejsou modelovány, například zpětné směr jednosměrné relace. Příkladem jednosměrné relace je Student
třída, která má vlastnost typu Department
, která představuje hlavní objekt, ale Department
třída nemá vlastnost, která je kolekcí Student
objektů. Pokud máte seznam Department
objektů a chcete najít všechny studenty v každém oddělení, můžete k jejich vyhledání použít join operaci.
Metody join poskytované v rozhraní LINQ jsou Join a GroupJoin. Tyto metody provádějí koňovitosti nebo spojení, která odpovídají dvěma zdrojům dat na základě rovnosti jejich klíčů. (Pro porovnání transact-SQL podporuje join jiné operátory než equals
, například less than
operátor.) V relačních databázových termínech implementuje vnitřní jointypjoin, Join ve kterém jsou vráceny pouze objekty, které mají shodu v jiné datové sadě. Metoda GroupJoin nemá žádné přímé ekvivalenty v relačních databázových termínech, ale implementuje nadmnožinu vnitřních spojení a levých vnějších spojení. Levý vnější je join prvekjoin, který vrací každý prvek prvního (levého) zdroje dat, i když neobsahuje žádné korelované prvky v jiném zdroji dat.
Následující obrázek znázorňuje koncepční zobrazení dvou sad a prvků v těchto sadách, které jsou zahrnuty buď ve vnitřní join , nebo levé vnější join.
Metody
Název metody | Popis | Syntaxe výrazu dotazu jazyka C# | Další informace |
---|---|---|---|
Join | Spojí dvě sekvence na základě funkcí selektoru klíčů a extrahuje dvojice hodnot. | join … in … on … equals … |
Enumerable.Join Queryable.Join |
GroupJoin | Spojí dvě sekvence na základě funkcí selektoru klíčů a seskupí výsledné shody pro každý prvek. | join … in … on … equals … into … |
Enumerable.GroupJoin Queryable.GroupJoin |
Poznámka:
Následující příklady v tomto článku používají společné zdroje dat pro tuto oblast.
Každý z nich Student
má úroveň známek, primární oddělení a řadu výsledků. A Teacher
má City
také vlastnost, která identifikuje areál, kde učitel má předměty. A Department
má jméno a odkaz na Teacher
toho, kdo slouží jako vedoucí oddělení.
Ukázkové datové sady najdete ve zdrojovém úložišti.
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; }
}
Následující příklad používá klauzuli join … in … on … equals …
na join dvě sekvence založené na konkrétní hodnotě:
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}");
}
Předchozí dotaz lze vyjádřit pomocí syntaxe metody, jak je znázorněno v následujícím kódu:
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}");
}
Následující příklad používá join … in … on … equals … into …
klauzuli na join dvě sekvence založené na konkrétní hodnotě a seskupuje výsledné shody pro každý prvek:
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}");
}
}
Předchozí dotaz lze vyjádřit pomocí syntaxe metody, jak je znázorněno v následujícím příkladu:
// 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}");
}
}
Provádění vnitřních spojení
V relačních databázových termínech vytvoří vnitřní join sada výsledků, ve které se každý prvek první kolekce zobrazí jednou pro každý odpovídající prvek v druhé kolekci. Pokud prvek v první kolekci neobsahuje žádné odpovídající prvky, nezobrazí se v sadě výsledků. Metoda Join , která je volána join
klauzulí v jazyce C#, implementuje vnitřní join. Následující příklady ukazují, jak provést čtyři varianty vnitřního join:
- Jednoduchý vnitřní prvek join , který koreluje prvky ze dvou zdrojů dat na základě jednoduchého klíče.
- Vnitřní join , který koreluje prvky ze dvou zdrojů dat na základě složeného klíče. Složený klíč, což je klíč, který se skládá z více než jedné hodnoty, umožňuje korelovat prvky na základě více než jedné vlastnosti.
- Násobekjoin, ve kterém jsou následné join operace připojeny k sobě navzájem.
- Vnitřní join , který je implementován pomocí skupiny join.
Jeden klíč join
Následující příklad odpovídá Teacher
objektům s Deparment
objekty, jejichž TeacherId
odpovídá tomu Teacher
. Klauzule select
v jazyce C# definuje, jak výsledné objekty vypadají. V následujícím příkladu jsou výsledné objekty anonymní typy, které se skládají z názvu oddělení a jména učitele, který vede oddělení.
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}");
}
Stejné výsledky dosáhnete pomocí Join syntaxe metody:
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}");
}
Učitelé, kteří nejsou vedoucími oddělení, se v konečných výsledcích nezobrazí.
Složený klíč join
Místo korelace prvků na základě pouze jedné vlastnosti můžete použít složený klíč k porovnání prvků na základě více vlastností. Zadejte funkci selektoru klíčů pro každou kolekci, která vrátí anonymní typ, který se skládá z vlastností, které chcete porovnat. Pokud vlastnosti označíte, musí mít stejný popisek v anonymním typu každého klíče. Vlastnosti musí být také zobrazeny ve stejném pořadí.
Následující příklad používá seznam Teacher
objektů a seznam Student
objektů k určení, kteří učitelé jsou také studenti. Oba tyto typy mají vlastnosti, které představují jméno a jméno rodiny každé osoby. Funkce, které vytvářejí join klíče z prvků každého seznamu, vrací anonymní typ, který se skládá z vlastností. Operace join porovnává tyto složené klíče pro rovnost a vrací dvojice objektů z každého seznamu, kde se jméno i jméno rodiny shodují.
// 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);
Tuto metodu Join můžete použít, jak je znázorněno v následujícím příkladu:
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);
}
Mnohonásobný join
K sobě lze připojit libovolný počet join operací, aby bylo možné provést více joinoperací . Každá join
klauzule v jazyce C# koreluje zadaný zdroj dat s výsledky předchozího join.
První join
klauzule odpovídá studentům a oddělením na Student
základě objektu DepartmentID
odpovídajícího objektu Department
ID
. Vrátí posloupnost anonymních typů, které obsahují Student
objekt a Department
objekt.
Druhá join
klauzule koreluje anonymní typy vrácené první join s Teacher
objekty na základě ID daného učitele odpovídající ID vedoucího oddělení. Vrátí posloupnost anonymních typů, které obsahují jméno studenta, název oddělení a název vedoucího oddělení. Vzhledem k tomu, že tato operace je vnitřní join, vrátí se pouze ty objekty z prvního zdroje dat, které mají shodu ve druhém zdroji dat.
// 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}".""");
}
Ekvivalentní použití více Join metod používá stejný přístup s anonymním typem:
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}".""");
}
Vnitřní join pomocí seskupených join
Následující příklad ukazuje, jak implementovat vnitřní join pomocí skupiny join. Seznam Department
objektů je seskupeně připojen k seznamu Student
objektů na Department.ID
základě odpovídající Student.DepartmentID
vlastnosti. Skupina join vytvoří kolekci zprostředkujících skupin, kde se každá skupina skládá z objektu Department
a posloupnosti odpovídajících Student
objektů. Druhá from
klauzule kombinuje (nebo zploštěná) tuto sekvenci do jedné delší sekvence. Klauzule select
určuje typ prvků v konečné sekvenci. Tento typ je anonymní typ, který se skládá z jména studenta a odpovídajícího názvu oddělení.
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}");
}
Stejné výsledky lze dosáhnout pomocí GroupJoin metody následujícím způsobem:
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}");
}
Výsledek je ekvivalentem sady výsledků získané pomocí join
klauzule bez into
klauzule k provedení vnitřní join. Následující kód ukazuje tento ekvivalentní dotaz:
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}");
}
Pokud se chcete vyhnout řetězení, můžete použít jednu Join metodu, jak je znázorněno zde:
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}");
}
Provádění seskupených spojení
join Skupina je užitečná pro vytváření hierarchických datových struktur. Každý prvek z první kolekce spáruje se sadou korelovaných prvků z druhé kolekce.
Poznámka:
Každý prvek první kolekce se zobrazí v sadě výsledků skupiny join bez ohledu na to, zda jsou v druhé kolekci nalezeny korelované prvky. V případě, že nejsou nalezeny žádné korelované prvky, je posloupnost korelovaných prvků pro daný prvek prázdná. Výběr výsledku má proto přístup ke všem prvkům první kolekce. To se liší od selektoru výsledků v jiné skupině join, která nemůže získat přístup k prvkům z první kolekce, které nemají v druhé kolekci žádnou shodu.
Upozorňující
Enumerable.GroupJoin nemá žádný přímý ekvivalent v tradičních termínech relační databáze. Tato metoda však implementuje nadmnožinu vnitřních spojení a levé vnější spojení. Oba tyto operace mohou být napsány z hlediska seskupené join. Další informace naleznete v tématu Entity Framework Core, GroupJoin.
První příklad v tomto článku ukazuje, jak provést skupinu join. Druhý příklad ukazuje, jak pomocí skupiny join vytvořit elementy XML.
Skupina join
Následující příklad provádí skupinu join objektů typu Department
a Student
na Department.ID
základě odpovídající Student.DepartmentID
vlastnosti. Na rozdíl od skupiny, joinkterá vytváří dvojici prvků pro každou shodu, vytvoří skupina join pouze jeden výsledný objekt pro každý prvek první kolekce, který v tomto příkladu Department
je objekt. Odpovídající prvky z druhé kolekce, které v tomto příkladu jsou Student
objekty, jsou seskupeny do kolekce. Nakonec funkce selektoru výsledků vytvoří anonymní typ pro každou shodu, která se skládá z Department.Name
a kolekce Student
objektů.
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}");
}
}
V předchozím příkladu obsahuje proměnná dotaz, query
který vytvoří seznam, kde každý prvek je anonymní typ, který obsahuje název oddělení a kolekci studentů, kteří studují v daném oddělení.
Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:
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}");
}
}
Seskupit join pro vytvoření XML
Spojení skupin jsou ideální pro vytváření XML pomocí LINQ to XML. Následující příklad je podobný předchozímu příkladu s tím rozdílem, že místo vytváření anonymních typů vytvoří funkce selektoru výsledků elementy XML, které představují spojené objekty.
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);
Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:
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);
Provedení levých vnějších spojení
Levý vnější je join prvekjoin, ve kterém je vrácen každý prvek první kolekce, bez ohledu na to, zda má jakékoli korelované prvky v druhé kolekci. LinQ můžete použít k provedení levého vnějšího join volání DefaultIfEmpty metody na výsledcích skupiny join.
Následující příklad ukazuje, jak použít metodu DefaultIfEmpty na výsledcích skupiny join k provedení levé vnější join.
Prvním krokem při vytváření levého vnějšího ze join dvou kolekcí je provést vnitřní join pomocí skupiny join. (Viz Pro vysvětlení tohoto procesu proveďte vnitřní spojení .) V tomto příkladu je seznam Department
objektů ve vnitřním spojení se seznamem Student
objektů na Department
základě ID objektu, které odpovídá studentovi DepartmentID
.
Druhým krokem je zahrnutí každého prvku první (levé) kolekce do sady výsledků, i když tento prvek nemá v pravé kolekci žádné shody. Toho se dosahuje voláním DefaultIfEmpty každé posloupnosti odpovídajících prvků ze skupiny join. V tomto příkladu se DefaultIfEmpty volá pro každou sekvenci odpovídajících Student
objektů. Metoda vrátí kolekci, která obsahuje jednu výchozí hodnotu, pokud sekvence odpovídajících Student
objektů je prázdná pro libovolný Department
objekt, čímž zajistí, že každý Department
objekt je reprezentován ve výsledné kolekci.
Poznámka:
Výchozí hodnota pro typ odkazu je null
; proto příklad kontroluje nulový odkaz před přístupem ke každému prvku každé Student
kolekce.
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}");
}
Ekvivalentní dotaz pomocí syntaxe metody se zobrazí v následujícím kódu:
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}");
}