join-Klausel (C#-Referenz)
Die join
-Klausel ist sehr nützlich beim verknüpfen von Elementen aus unterschiedlichen Quellsequenzen, die keine direkte Beziehung im Objektmodell haben. Die Elemente müssen lediglich in jeder Quelle einige Werte freigeben, die auf Gleichheit verglichen werden können. Ein Lebensmittelgroßhändler hat z.B. eine Liste seiner Lieferanten und seiner Käufer für ein bestimmtes Produkt. Eine join
-Klausel kann beispielsweise verwendet werden, um eine Liste von Lieferanten und Käufern dieses Produkts zu erstellen, die sich alle in einer angegebenen Region befinden.
Eine join
-Klausel akzeptiert zwei Quellsequenzen als Eingabe. Die Elemente jeder Sequenz müssen entweder eine Eigenschaft sein oder eine Eigenschaft enthalten, die mit einer entsprechenden Eigenschaft einer anderen Sequenz verglichen werden kann. Die join
-Klausel vergleicht die angegebenen Schlüssel auf Gleichheit, indem sie das besonderen Schlüsselwort equals
verwendet. Alle Verknüpfungen, die von der join
-Klausel vorgenommen werden, sind Gleichheitsverknüpfungen. Die Form der Ausgabe einer join
-Klausel hängt vom Typ der durchgeführten Verknüpfung ab. Hier sind die am häufigsten vorkommenden Verknüpfungstypen:
Innere Verknüpfung
Gruppenverknüpfung
Left Outer Join
Innere Verknüpfung
Im folgenden Beispiel wird eine einfache Gleichheitsverknüpfung dargestellt. Diese Abfrage produziert eine flache Sequenz aus Paaren der Form „Produktname/Kategorie“. Die gleiche Kategoriezeichenfolge taucht in mehreren Elementen auf. Wenn ein Element aus categories
keine entsprechenden products
hat, wird diese Kategorie nicht in den Ergebnissen angezeigt.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { ProductName = prod.Name, Category = category.Name }; //produces flat sequence
Weitere Informationen finden Sie unter Ausführen von inneren Verknüpfungen.
Gruppenverknüpfung
Eine join
-Klausel mit einem into
-Ausdruck wird Gruppenverknüpfung genannt.
var innerGroupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new { CategoryName = category.Name, Products = prodGroup };
Eine Gruppenverknüpfung erstellt eine hierarchische Ergebnissequenz, die Elemente in der linken Quellsequenz mit mindestens einem entsprechenden Element der rechten Quellsequenz verknüpft. Eine Gruppenverknüpfung hat keine entsprechenden Beziehungsbedingungen; eigentlich ist sie eine Sequenz von Objektarrays.
Wenn keine Elemente der rechten Quellsequenz gefunden werden, die mit Elementen der linken Sequenz übereinstimmen, erstellt die join
-Klausel ein leeres Array für dieses Element. Deshalb ist eine Gruppenverknüpfung immer noch grundsätzlich eine innere Gleichheitsverknüpfung, nur dass die Ergebnissequenz in Gruppen aufgeteilt ist.
Wenn Sie nur das Ergebnis einer Gruppenverknüpfung auswählen, können Sie auf Elemente zugreifen, aber Sie können nicht den Schlüssel identifizieren, der ihnen gleich ist. Deshalb ist es im Allgemeinen sinnvoller, das Ergebnis der Gruppenverknüpfung in einem neuen Typen auszuwählen, der auch einen Schlüsselnamen aufweist – so wie im vorherigen Ergebnis erläutert.
Selbstverständlich können Sie auch das Ergebnis einer Gruppenverknüpfung als Generator einer anderen Unterabfrage verwenden:
var innerGroupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from prod2 in prodGroup
where prod2.UnitPrice > 2.50M
select prod2;
Weitere Informationen finden Sie unter Ausführen von Gruppenverknüpfungen.
Left Outer Join
In einem Left Outer Join werden alle Elemente der linken Quellsequenz zurückgegeben, auch wenn sich keine entsprechenden Elemente in der rechten Sequenz befinden. Um einen Left Outer Join in LINQ durchzuführen, verwenden Sie die Methode DefaultIfEmpty
zusammen mit einer Gruppenverknüpfung, um ein Standardelement für den rechten Bereich festzulegen, das erstellt wird, wenn es kein entsprechendes Element im linken Bereich gibt. Sie können null
als Standardwert für jeden beliebigen Verweistyp verwenden, oder Sie können einen benutzerdefinierten Standardtyp festlegen. Im folgendem Beispiel wird ein benutzerdefinierter Standardtyp dargestellt:
var leftOuterJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty(new Product { Name = String.Empty, CategoryID = 0 })
select new { CatName = category.Name, ProdName = item.Name };
Weitere Informationen finden Sie unter Ausführen von Left Outer Joins.
Der equals-Operator
Eine join
-Klausel führt eine Gleichheitsverknüpfung durch. D.h., dass Übereinstimmungen nur auf der Gleichheit zweier Schlüssel basieren können. Andere Vergleichstypen wie etwa „größer als“ oder „ungleich“ werden nicht unterstützt. Um sicherzustellen, dass die Verknüpfungen Gleichheitsverknüpfungen sind, verwendet die join
-Klausel das Schlüsselwort equals
statt des Operators ==
. Das Schlüsselwort equals
kann nur in einer join
-Klausel verwendet werden, und es unterscheidet sich vom Operator ==
in einigen wesentlichen Punkten. Beim Vergleichen von Zeichenfolgen verfügt equals
über eine Überladung zum Vergleichen nach Werten, und der Operator ==
verwendet Verweisgleichheit. Wenn beide Seiten des Vergleichs über identische Zeichenfolgenvariablen verfügen, weisen equals
und ==
das gleiche Ergebnis auf (true). Das liegt daran, dass der Compiler, wenn ein Programm zwei oder mehr äquivalente Zeichenfolgenvariablen deklariert, alle an demselben Speicherort speichert. Dies wird als Internalisierung bezeichnet. Ein weiterer wichtiger Unterschied ist der NULL-Vergleich: null equals null
wird mit dem equals
-Operator als „false“ ausgewertet, mit dem ==
-Operator aber als „true“. Schließlich unterscheidet sich das Bereichsverhalten: Mit equals
nutzt der linke Schlüssel die äußere Quellsequenz und der rechte Schlüssel die innere Quelle. Die äußere Quelle befindet sich nur links von equals
im Geltungsbereich, und die innere Quellsequenz befindet sich nur auf der rechten Seite im Geltungsbereich.
Nicht-Gleichheitsverknüpfungen
Sie können Nicht-Gleichheitsverknüpfungen, Kreuzverknüpfungen und andere benutzerdefinierte Verknüpfungen durchführen, indem Sie mehrere from
-Klauseln verwenden, um unabhängig neue Sequenzen in eine Abfrage einzuführen. Weitere Informationen finden Sie unter Ausführen von benutzerdefinierten Verknüpfungsoperationen.
Verknüpfungen für Objektauflistungen vs. relationale Tabellen
Verknüpfungsvorgänge in einem LINQ-Abfrageausdruck werden in Objektsammlungen durchgeführt. Objektauflistungen können nicht wie relationale Tabellen „verknüpft“ werden. In LINQ sind explizite join
-Klauseln nur erforderlich, wenn zwei Quellsequenzen nicht durch eine Beziehung verbunden sind. Wenn Sie mit LINQ to SQL arbeiten, werden Tabellen mit Fremdschlüsseln in einem Objektmodell als Eigenschaften der primären Tabelle dargestellt. In der Northwind-Datenbank weist die Tabelle „Customers“ (Kunden) beispielsweise eine Fremdschlüsselbeziehung zu der Tabelle „Orders“ (Aufträge) auf. Wenn Sie die Tabellen dem Objektmodell zuordnen, hat die Klasse „Customers“ eine Eigenschaft „Orders“, die die Auflistung der Aufträge enthält, die zu diesem Kunden gehören. Tatsächlich wurde die Verknüpfung bereits für Sie vorgenommen.
Weitere Informationen zu Abfragen zusammengehöriger Tabellen im Kontext von LINQ to SQL finden Sie unter Vorgehensweise: Zuordnen von Datenbankbeziehungen.
Zusammengesetzte Schlüssel
Mithilfe eines zusammengesetzten Schlüssels können Sie auf die Gleichheit mehrerer Werte prüfen. Weitere Informationen finden Sie unter Verknüpfen mithilfe eines zusammengesetzten Schlüssels. Zusammengesetzte Schlüssel können auch in einer group
-Klausel verwendet werden.
Beispiel
In folgendem Beispiel werden die Ergebnisse einer inneren Verknüpfung, einer Gruppenverknüpfung und einer linken äußeren Verknüpfung in der gleichen Datenquelle anhand derselben übereinstimmenden Schlüssel miteinander verglichen. Diesen Beispielen wurde zusätzlicher Code hinzugefügt, um die Ergebnisse in der Konsolenanzeige zu verdeutlichen.
class JoinDemonstration
{
#region Data
class Product
{
public required string Name { get; init; }
public required int CategoryID { get; init; }
}
class Category
{
public required string Name { get; init; }
public required int ID { get; init; }
}
// Specify the first data source.
List<Category> categories =
[
new Category {Name="Beverages", ID=001},
new Category {Name="Condiments", ID=002},
new Category {Name="Vegetables", ID=003},
new Category {Name="Grains", ID=004},
new Category {Name="Fruit", ID=005}
];
// Specify the second data source.
List<Product> products =
[
new Product {Name="Cola", CategoryID=001},
new Product {Name="Tea", CategoryID=001},
new Product {Name="Mustard", CategoryID=002},
new Product {Name="Pickles", CategoryID=002},
new Product {Name="Carrots", CategoryID=003},
new Product {Name="Bok Choy", CategoryID=003},
new Product {Name="Peaches", CategoryID=005},
new Product {Name="Melons", CategoryID=005},
];
#endregion
static void Main(string[] args)
{
JoinDemonstration app = new JoinDemonstration();
app.InnerJoin();
app.GroupJoin();
app.GroupInnerJoin();
app.GroupJoin3();
app.LeftOuterJoin();
app.LeftOuterJoin2();
}
void InnerJoin()
{
// Create the query that selects
// a property from each element.
var innerJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID
select new { Category = category.ID, Product = prod.Name };
Console.WriteLine("InnerJoin:");
// Execute the query. Access results
// with a simple foreach statement.
foreach (var item in innerJoinQuery)
{
Console.WriteLine("{0,-10}{1}", item.Product, item.Category);
}
Console.WriteLine("InnerJoin: {0} items in 1 group.", innerJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin()
{
// This is a demonstration query to show the output
// of a "raw" group join. A more typical group join
// is shown in the GroupInnerJoin method.
var groupJoinQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup;
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Simple GroupJoin:");
// A nested foreach statement is required to access group items.
foreach (var prodGrouping in groupJoinQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("Unshaped GroupJoin: {0} items in {1} unnamed groups", totalItems, groupJoinQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupInnerJoin()
{
var groupJoinQuery2 =
from category in categories
orderby category.ID
join prod in products on category.ID equals prod.CategoryID into prodGroup
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupInnerJoin:");
foreach (var productGroup in groupJoinQuery2)
{
Console.WriteLine(productGroup.Category);
foreach (var prodItem in productGroup.Products)
{
totalItems++;
Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID);
}
}
Console.WriteLine("GroupInnerJoin: {0} items in {1} named groups", totalItems, groupJoinQuery2.Count());
Console.WriteLine(System.Environment.NewLine);
}
void GroupJoin3()
{
var groupJoinQuery3 =
from category in categories
join product in products on category.ID equals product.CategoryID into prodGroup
from prod in prodGroup
orderby prod.CategoryID
select new { Category = prod.CategoryID, ProductName = prod.Name };
//Console.WriteLine("GroupInnerJoin:");
int totalItems = 0;
Console.WriteLine("GroupJoin3:");
foreach (var item in groupJoinQuery3)
{
totalItems++;
Console.WriteLine(" {0}:{1}", item.ProductName, item.Category);
}
Console.WriteLine("GroupJoin3: {0} items in 1 group", totalItems);
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin()
{
// Create the query.
var leftOuterQuery =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
select prodGroup.DefaultIfEmpty(new Product() { Name = "Nothing!", CategoryID = category.ID });
// Store the count of total items (for demonstration only).
int totalItems = 0;
Console.WriteLine("Left Outer Join:");
// A nested foreach statement is required to access group items
foreach (var prodGrouping in leftOuterQuery)
{
Console.WriteLine("Group:");
foreach (var item in prodGrouping)
{
totalItems++;
Console.WriteLine(" {0,-10}{1}", item.Name, item.CategoryID);
}
}
Console.WriteLine("LeftOuterJoin: {0} items in {1} groups", totalItems, leftOuterQuery.Count());
Console.WriteLine(System.Environment.NewLine);
}
void LeftOuterJoin2()
{
// Create the query.
var leftOuterQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
from item in prodGroup.DefaultIfEmpty()
select new { Name = item == null ? "Nothing!" : item.Name, CategoryID = category.ID };
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", leftOuterQuery2.Count());
// Store the count of total items
int totalItems = 0;
Console.WriteLine("Left Outer Join 2:");
// Groups have been flattened.
foreach (var item in leftOuterQuery2)
{
totalItems++;
Console.WriteLine("{0,-10}{1}", item.Name, item.CategoryID);
}
Console.WriteLine("LeftOuterJoin2: {0} items in 1 group", totalItems);
}
}
/*Output:
InnerJoin:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Peaches 5
Melons 5
InnerJoin: 8 items in 1 group.
Unshaped GroupJoin:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Group:
Peaches 5
Melons 5
Unshaped GroupJoin: 8 items in 5 unnamed groups
GroupInnerJoin:
Beverages
Cola 1
Tea 1
Condiments
Mustard 2
Pickles 2
Vegetables
Bok Choy 3
Carrots 3
Grains
Fruit
Melons 5
Peaches 5
GroupInnerJoin: 8 items in 5 named groups
GroupJoin3:
Cola:1
Tea:1
Mustard:2
Pickles:2
Carrots:3
Bok Choy:3
Peaches:5
Melons:5
GroupJoin3: 8 items in 1 group
Left Outer Join:
Group:
Cola 1
Tea 1
Group:
Mustard 2
Pickles 2
Group:
Carrots 3
Bok Choy 3
Group:
Nothing! 4
Group:
Peaches 5
Melons 5
LeftOuterJoin: 9 items in 5 groups
LeftOuterJoin2: 9 items in 1 group
Left Outer Join 2:
Cola 1
Tea 1
Mustard 2
Pickles 2
Carrots 3
Bok Choy 3
Nothing! 4
Peaches 5
Melons 5
LeftOuterJoin2: 9 items in 1 group
Press any key to exit.
*/
Hinweise
Eine join
-Klausel, auf die kein into
folgt, wird in einen Join-Methodenaufruf übersetzt. Eine join
-Klausel, auf die into
folgt, wird in einen GroupJoin-Methodenaufruf übersetzt.
Weitere Informationen
- Abfrageschlüsselwörter (LINQ)
- Language-Integrated Query (LINQ)
- Verknüpfungsvorgänge
- group-Klausel
- Ausführen linker äußerer Verknüpfungen
- Ausführen innerer Verknüpfungen
- Ausführen von Gruppenverknüpfungen
- Sortieren der Ergebnisse einer Join-Klausel
- Verknüpfen mithilfe eines zusammengesetzten Schlüssels
- Kompatible Datenbanksysteme für Visual Studio