Join Åtgärder i LINQ
En join av två datakällor är associationen mellan objekt i en datakälla och objekt som delar ett gemensamt attribut i en annan datakälla.
Viktigt!
Dessa exempel använder en System.Collections.Generic.IEnumerable<T> datakälla. Datakällor baserade på System.Linq.IQueryProvider användning av System.Linq.IQueryable<T> datakällor och uttrycksträd. Uttrycksträd har begränsningar för den tillåtna C#-syntaxen. Dessutom kan varje IQueryProvider
datakälla, till exempel EF Core , införa fler begränsningar. Kontrollera dokumentationen för din datakälla.
Anslutning är en viktig åtgärd i frågor som riktar sig till datakällor vars relationer till varandra inte kan följas direkt. I objektorienterad programmering kan koppling innebära en korrelation mellan objekt som inte är modellerade, till exempel bakåtriktningen för en enkelriktad relation. Ett exempel på en enkelriktad relation är en Student
klass som har en egenskap av typen Department
som representerar huvudnamnet, men Department
klassen har ingen egenskap som är en samling Student
objekt. Om du har en lista över Department
objekt och vill hitta alla studenter på varje avdelning kan du använda en join åtgärd för att hitta dem.
Metoderna join som tillhandahålls i LINQ-ramverket är Join och GroupJoin. Dessa metoder utför likvärdighet eller kopplingar som matchar två datakällor baserat på likheten mellan deras nycklar. (Som jämförelse stöder join Transact-SQL andra operatorer än equals
, till exempel operatorn less than
.) I relationsdatabastermer Join implementerar en inre join, en typ av join där endast de objekt som har en matchning i den andra datauppsättningen returneras. Metoden GroupJoin har ingen direkt motsvarighet i relationsdatabastermer, men den implementerar en supermängd av inre kopplingar och vänster yttre kopplingar. En vänster yttre join är en join som returnerar varje element i den första (vänstra) datakällan, även om den inte har några korrelerade element i den andra datakällan.
Följande bild visar en konceptuell vy över två uppsättningar och elementen i de uppsättningar som ingår i antingen en inre join eller en vänster yttre join.
Metoder
Metodnamn | beskrivning | Syntax för C#-frågeuttryck | Mer information |
---|---|---|---|
Join | Kopplar ihop två sekvenser baserat på nyckelväljarens funktioner och extraherar par med värden. | join … in … on … equals … |
Enumerable.Join Queryable.Join |
GroupJoin | Kopplar två sekvenser baserat på nyckelväljarens funktioner och grupperar de resulterande matchningarna för varje element. | join … in … on … equals … into … |
Enumerable.GroupJoin Queryable.GroupJoin |
Kommentar
I följande exempel i den här artikeln används vanliga datakällor för det här området.
Var Student
och en har en betygsnivå, en primär avdelning och en serie poäng. En Teacher
har också en City
egenskap som identifierar det campus där läraren har klasser. A Department
har ett namn och en referens till en Teacher
som fungerar som avdelningschef.
Du hittar exempeldatauppsättningen i källdatabasen.
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; }
}
I följande exempel används join … in … on … equals …
-satsen till join två sekvenser baserat på ett specifikt värde:
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}");
}
Föregående fråga kan uttryckas med hjälp av metodsyntax enligt följande kod:
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}");
}
I följande exempel används join … in … on … equals … into …
-satsen till join två sekvenser baserat på ett specifikt värde och grupperar de resulterande matchningarna för varje element:
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}");
}
}
Föregående fråga kan uttryckas med hjälp av metodsyntax enligt följande exempel:
// 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}");
}
}
Utföra inre kopplingar
I relationsdatabastermer skapar ett inre join en resultatuppsättning där varje element i den första samlingen visas en gång för varje matchande element i den andra samlingen. Om ett element i den första samlingen inte har några matchande element visas det inte i resultatuppsättningen. Metoden Join , som anropas av join
-satsen i C#, implementerar en inre join. I följande exempel visas hur du utför fyra varianter av en inre join:
- En enkel inre join som korrelerar element från två datakällor baserat på en enkel nyckel.
- Ett inre join som korrelerar element från två datakällor baserat på en sammansatt nyckel. Med en sammansatt nyckel, som är en nyckel som består av mer än ett värde, kan du korrelera element baserat på mer än en egenskap.
- En multipel join där efterföljande join åtgärder läggs till varandra.
- Ett inre join som implementeras med hjälp av en grupp join.
Enkel nyckel join
Följande exempel matchar Teacher
objekt med Deparment
objekt vars TeacherId
matchar .Teacher
Satsen select
i C# definierar hur de resulterande objekten ser ut. I följande exempel är de resulterande objekten anonyma typer som består av avdelningsnamnet och namnet på den lärare som leder avdelningen.
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}");
}
Du får samma resultat med hjälp av metodsyntaxen 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}");
}
De lärare som inte är avdelningschefer visas inte i slutresultatet.
Sammansatt nyckel join
I stället för att korrelera element baserat på bara en egenskap kan du använda en sammansatt nyckel för att jämföra element baserat på flera egenskaper. Ange nyckelväljarens funktion för varje samling för att returnera en anonym typ som består av de egenskaper som du vill jämföra. Om du märker egenskaperna måste de ha samma etikett i varje nyckels anonyma typ. Egenskaperna måste också visas i samma ordning.
I följande exempel används en lista med Teacher
objekt och en lista över Student
objekt för att avgöra vilka lärare som också är elever. Båda dessa typer har egenskaper som representerar för- och efternamn för varje person. De funktioner som skapar join nycklarna från varje listas element returnerar en anonym typ som består av egenskaperna. Åtgärden join jämför dessa sammansatta nycklar för likhet och returnerar par med objekt från varje lista där både förnamnet och familjenamnet matchar.
// 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);
Du kan använda Join metoden, som du ser i följande exempel:
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);
}
Multipel join
Valfritt antal join åtgärder kan läggas till varandra för att utföra flera join. Varje join
sats i C# korrelerar en angiven datakälla med resultatet från föregående join.
Den första join
satsen matchar studenter och avdelningar baserat på ett Student
objekts DepartmentID
matchning av ett Department
objekts ID
. Den returnerar en sekvens med anonyma typer som innehåller Student
objektet och Department
objektet.
Den andra join
satsen korrelerar de anonyma typer som returneras av den första join med Teacher
objekt baserat på lärarens ID som matchar avdelningschefens ID. Den returnerar en sekvens av anonyma typer som innehåller elevens namn, avdelningsnamnet och avdelningsledarens namn. Eftersom den här åtgärden är en inre joinreturneras endast de objekt från den första datakällan som har en matchning i den andra datakällan.
// 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}".""");
}
Motsvarigheten med flera Join metoder använder samma metod med den anonyma typen:
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}".""");
}
Inre join genom att använda grupperade join
I följande exempel visas hur du implementerar ett inre join med hjälp av en grupp join. Listan över Department
objekt är gruppansluten till listan över Student
objekt baserat på den Department.ID
matchande Student.DepartmentID
egenskapen. Gruppen join skapar en samling mellanliggande grupper, där varje grupp består av ett Department
objekt och en sekvens med matchande Student
objekt. Den andra from
satsen kombinerar (eller jämnar ut) sekvensen i en längre sekvens. Satsen select
anger typ av element i den sista sekvensen. Den typen är en anonym typ som består av elevens namn och det matchande avdelningsnamnet.
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}");
}
Samma resultat kan uppnås med hjälp av GroupJoin metoden enligt följande:
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}");
}
Resultatet motsvarar den resultatuppsättning som erhålls med hjälp join
av -satsen utan into
-satsen för att utföra en inre join. Följande kod visar den här motsvarande frågan:
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}");
}
För att undvika sammanlänkning kan den enda Join metoden användas enligt beskrivningen här:
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}");
}
Utföra grupperade kopplingar
Gruppen join är användbar för att skapa hierarkiska datastrukturer. Den parkopplar varje element från den första samlingen med en uppsättning korrelerade element från den andra samlingen.
Kommentar
Varje element i den första samlingen visas i resultatuppsättningen för en grupp join oavsett om korrelerade element finns i den andra samlingen. Om inga korrelerade element hittas är sekvensen med korrelerade element för det elementet tom. Resultatväljaren har därför åtkomst till varje element i den första samlingen. Detta skiljer sig från resultatväljaren i en icke-grupp join, som inte kan komma åt element från den första samlingen som inte har någon matchning i den andra samlingen.
Varning
Enumerable.GroupJoin har ingen direkt motsvarighet i traditionella relationsdatabastermer. Den här metoden implementerar dock en superuppsättning inre kopplingar och vänster yttre kopplingar. Båda dessa åtgärder kan skrivas i termer av en grupperad join. Mer information finns i Entity Framework Core, GroupJoin.
Det första exemplet i den här artikeln visar hur du utför en grupp join. Det andra exemplet visar hur du använder en grupp join för att skapa XML-element.
Grupp join
I följande exempel utförs en grupp join med objekt av typen Department
och Student
baseras på den Department.ID
matchande Student.DepartmentID
egenskapen. Till skillnad från en icke-grupp join, som producerar ett par element för varje matchning, genererar gruppen join bara ett resulterande objekt för varje element i den första samlingen, vilket i det här exemplet är ett Department
objekt. Motsvarande element från den andra samlingen, som i det här exemplet är Student
objekt, grupperas i en samling. Slutligen skapar resultatväljaren en anonym typ för varje matchning som består av Department.Name
och en samling 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}");
}
}
I exemplet ovan query
innehåller variabeln frågan som skapar en lista där varje element är en anonym typ som innehåller institutionens namn och en samling studenter som studerar på den avdelningen.
Motsvarande fråga med metodsyntax visas i följande kod:
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}");
}
}
Gruppera join för att skapa XML
Gruppkopplingar är idealiska för att skapa XML med hjälp av LINQ till XML. Följande exempel liknar föregående exempel, förutom att i stället för att skapa anonyma typer skapar resultatväljarens funktion XML-element som representerar de kopplade objekten.
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);
Motsvarande fråga med metodsyntax visas i följande kod:
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);
Utföra vänster yttre kopplingar
En vänster yttre join är en join där varje element i den första samlingen returneras, oavsett om det har några korrelerade element i den andra samlingen. Du kan använda LINQ för att utföra en vänster yttre join genom att anropa DefaultIfEmpty metoden på resultatet av en grupp join.
I följande exempel visas hur du använder DefaultIfEmpty metoden på resultatet av en grupp join för att utföra en vänster yttre join.
Det första steget i att skapa en vänster yttre join av två samlingar är att utföra ett inre join med hjälp av en grupp join. (Se Utför inre kopplingar för en förklaring av den här processen.) I det här exemplet är listan med Department
objekt inre kopplad till listan över Student
objekt baserat på ett Department
objekts ID som matchar elevens DepartmentID
.
Det andra steget är att inkludera varje element i den första (vänstra) samlingen i resultatuppsättningen även om elementet inte har några matchningar i den högra samlingen. Detta uppnås genom att anropa DefaultIfEmpty varje sekvens med matchande element från gruppen join. I det här exemplet DefaultIfEmpty anropas på varje sekvens med matchande Student
objekt. Metoden returnerar en samling som innehåller ett enda standardvärde om sekvensen med matchande Student
objekt är tom för alla Department
objekt, vilket säkerställer att varje Department
objekt representeras i resultatsamlingen.
Kommentar
Standardvärdet för en referenstyp är null
. Därför söker exemplet efter en null-referens innan varje element i varje Student
samling används.
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}");
}
Motsvarande fråga med metodsyntax visas i följande kod:
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}");
}