Freigeben über


join-Klausel (C#-Referenz)

Verwenden Sie die join Klausel, um Elemente aus verschiedenen Quellsequenzen zuzuordnen, die keine direkte Beziehung im Objektmodell aufweisen. Die einzige Anforderung besteht darin, dass die Elemente in jeder Quelle einen Wert teilen, den Sie mit Gleichheit vergleichen können. Beispielsweise kann ein Lebensmittelhändler eine Liste der Lieferanten eines bestimmten Produkts und eine Liste der Käufer haben. Sie können eine join Klausel verwenden, um eine Liste der Lieferanten und Käufer dieses Produkts zu erstellen, die sich alle in derselben angegebenen Region befinden.

Die C#-Sprachreferenz dokumentiert die zuletzt veröffentlichte Version der C#-Sprache. Außerdem enthält sie eine erste Dokumentation zu Funktionen in der öffentlichen Vorschau für die kommende Sprachversion.

In der Dokumentation werden alle Features identifiziert, die in den letzten drei Versionen der Sprache oder in der aktuellen öffentlichen Vorschau eingeführt wurden.

Tipp

Informationen dazu, wann ein Feature erstmals in C# eingeführt wurde, finden Sie im Artikel zum Versionsverlauf der C#-Sprache.

Eine join Klausel akzeptiert zwei Quellsequenzen als Eingabe. Die Elemente in jeder Sequenz müssen entweder eine Eigenschaft sein oder enthalten, die Sie mit einer entsprechenden Eigenschaft in der anderen Sequenz vergleichen können. Die join Klausel verwendet das spezielle equals Schlüsselwort, um die angegebenen Schlüssel für die Gleichheit zu vergleichen. Alle Verknüpfungen, die die join Klausel ausführt, sind Equijoins. Die Form der Ausgabe einer join Klausel hängt vom jeweiligen Typ der Verknüpfung ab, die Sie ausführen. In der folgenden Liste sind die drei am häufigsten verwendeten Verknüpfungstypen aufgeführt:

  • Innerer Join
  • Gruppenverknüpfung
  • Linker äußerer Join

Innerer Join

Im folgenden Beispiel wird eine einfache Gleichheitsverknüpfung dargestellt. Diese Abfrage erzeugt eine flache Sequenz von "Produktname/Kategorie"-Paaren. Die gleiche Kategoriezeichenfolge wird in mehreren Elementen angezeigt. Wenn ein Element nicht categories übereinstimmend productsist, 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 innerer 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 Gruppenbeitritt erzeugt eine hierarchische Ergebnissequenz, die Elemente in der linken Quellsequenz einem oder mehreren übereinstimmenden Elementen in der rechten Quellsequenz zuordnet. Ein Gruppenbeitritt hat keine Entsprechung in relationalen Ausdrücken; Es handelt sich im Wesentlichen um eine Abfolge von Objektarrays.

Wenn keine Elemente aus der rechten Quellsequenz mit einem Element in der linken Quelle übereinstimmen, erzeugt 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 die Ergebnisse eines Gruppenbeitritts auswählen, können Sie auf die Elemente zugreifen, aber Sie können den Schlüssel, für den sie übereinstimmen, nicht identifizieren. Daher ist es in der Regel nützlicher, die Ergebnisse der Gruppenbeitritt in einen neuen Typ auszuwählen, der auch den Schlüsselnamen aufweist, wie im vorherigen Beispiel gezeigt.

Sie können natürlich auch das Ergebnis einer Gruppenbeitritts 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 "Gruppierte Verknüpfungen ausführen".

Linker äußerer Join

In einer linken äußeren Verknüpfung gibt die Abfrage alle Elemente in der linken Quellsequenz zurück, auch wenn sich keine übereinstimmenden Elemente in der rechten Sequenz befinden. Verwenden Sie die DefaultIfEmpty Methode in LINQ in Kombination mit einer Gruppenverknüpfung, um eine linke äußere Verknüpfung auszuführen, bei der ein standardmäßiges rechtsseitiges Element angegeben wird, das erzeugt werden soll, wenn ein linksseitiges Element keine Übereinstimmungen aufweist. Sie können null als Standardwert für einen beliebigen Verweistyp verwenden oder einen benutzerdefinierten Standardtyp angeben. Im folgenden Beispiel wird ein benutzerdefinierter Standardtyp angezeigt:

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 "größer als" oder "nicht gleich" werden nicht unterstützt. Um klarzustellen, dass alle Verknüpfungen equijoins sind, verwendet die join Klausel das equals Schlüsselwort anstelle des == Operators. Das equals Schlüsselwort kann nur in einer join Klausel verwendet werden und unterscheidet sich von dem == Operator auf einige wichtige Weise. Beim Vergleichen von Zeichenfolgen equals weist eine Überladung zum Vergleichen nach Wert auf, und der Operator == verwendet die Referenzgleichheit. Wenn beide Vergleichsseiten identische Zeichenfolgenvariablen equals aufweisen und == dasselbe Ergebnis erreichen: true. Das liegt daran, dass der Compiler, wenn ein Programm zwei oder mehr gleichwertige Zeichenfolgenvariablen deklariert, alle an demselben Speicherort speichert. Dies wird als Internierung 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 Geltungsbereichsverhalten: Mit equals wird die äußere Quellsequenz durch den linken Schlüssel genutzt und die innere Quelle durch den rechten Schlüssel. Die äußere Quelle befindet sich nur im Geltungsbereich auf der linken Seite von equals und die innere Sequenz befindet sich nur im Geltungsbereich auf der rechten Seite.

Nicht-Gleichheitsverknüpfungen

Sie können Nicht-Equijoins, Kreuzverknüpfungen und andere benutzerdefinierte Verknüpfungsvorgänge ausführen, indem Sie mehrere from Klauseln verwenden, um neue Sequenzen unabhängig in eine Abfrage einzuführen. Weitere Informationen finden Sie unter Ausführen von benutzerdefinierten Verknüpfungsvorgängen.

Verknüpfungen in Objektsammlungen im Vergleich zu relationalen Tabellen

In einem LINQ-Abfrageausdruck führen Sie Verknüpfungsvorgänge für Objektsammlungen aus. Objektauflistungen können nicht auf die gleiche Weise wie zwei relationale Tabellen verknüpft werden. In LINQ benötigen Sie nur explizite join Klauseln, wenn zwei Quellsequenzen keine Beziehung haben. Wenn Sie mit LINQ to SQL arbeiten, stellt das Objektmodell Fremdschlüsseltabellen als Eigenschaften der Primärtabelle dar. Beispielsweise weist die Tabelle "Kunde" in der Northwind-Datenbank eine Fremdschlüsselbeziehung mit der Tabelle "Bestellungen" auf. Wenn Sie die Tabellen dem Objektmodell zuordnen, verfügt die Customer-Klasse über eine Orders Eigenschaft, die die Auflistung enthält Orders , die diesem Kunden zugeordnet ist. Tatsächlich ist die Verknüpfung bereits für Sie erledigt.

Weitere Informationen zum Abfragen verwandter Tabellen im Kontext von LINQ to SQL finden Sie unter How to: Map Database Relationships.

Zusammengesetzte Tasten

Sie können die Gleichheit mehrerer Werte mithilfe eines zusammengesetzten Schlüssels testen. Weitere Informationen finden Sie unter Verknüpfung mithilfe zusammengesetzter Schlüssel. Sie können auch zusammengesetzte Schlüssel in einer group Klausel verwenden.

Beispiel

Im folgenden Beispiel werden die Ergebnisse einer inneren Verknüpfung, einer Gruppenverknüpfung und einer linken äußeren Verknüpfung an denselben Datenquellen mit denselben Schlüsseln verglichen. Diesen Beispielen werden zusätzlichen 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: {innerJoinQuery.Count()} items in 1 group.");
        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: {totalItems} items in {groupJoinQuery.Count()} unnamed groups");
        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: {totalItems} items in {groupJoinQuery2.Count()} named groups");
        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($"   {item.ProductName}:{item.Category}");
        }

        Console.WriteLine($"GroupJoin3: {totalItems} items in 1 group");
        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: {totalItems} items in {leftOuterQuery.Count()} groups");
        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: {leftOuterQuery2.Count()} items in 1 group");
        // 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: {totalItems} items in 1 group");
    }
}
/*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.
*/

Bemerkungen

Eine join Klausel, auf die nicht gefolgt into wird, wird in einen Join Methodenaufruf übersetzt. Eine join Klausel, auf die gefolgt wird, into wird in einen GroupJoin Methodenaufruf übersetzt.

Siehe auch