join-Klausel (C#-Referenz)

Aktualisiert: November 2007

Die join-Klausel ist nützlich, um Elemente aus unterschiedlichen Quellsequenzen, die keine direkte Beziehung im Objektmodell haben, zuzuweisen. Die einzige Anforderung ist, dass die Elemente in jeder Quelle einige Werte gemeinsam nutzen, die auf Gleichheit verglichen werden können. Ein Lebensmittelanbieter hat beispielsweise eine Liste mit Anbietern bestimmter Produkte und eine Liste mit Käufern. Eine join-Klausel kann z. B. verwendet werden, um eine Liste mit Anbietern und Käufern für dieses Produkt zu erstellen, die sich alle in der angegebenen Region befinden.

Eine join-Klausel verwendet zwei Quellsequenzen als Eingabe. Die Elemente in jeder Sequenz müssen entweder eine Eigenschaft sein oder eine Eigenschaft enthalten, die mit einer entsprechenden Eigenschaft in der anderen Sequenz verglichen werden kann. Die join-Klausel vergleicht die angegebenen Schlüssel mithilfe des speziellen equals-Schlüsselworts auf Gleichheit. Alle von der join-Klausel ausgeführten Verknüpfungen sind Gleichheitsverknüpfungen. Die Form der Ausgabe einer join-Klausel ist vom speziellen Typ der Verknüpfung, die Sie ausführen, abhängig. Die folgenden drei Verknüpfungstypen kommen am häufigsten vor:

  • Innere Verknüpfung

  • Gruppenverknüpfung

  • Linke äußere Verknüpfung

Innere Verknüpfung

Im folgenden Beispiel wird eine einfache innere Gleichheitsverknüpfung veranschaulicht. Diese Abfrage erzeugt eine flache Sequenz von "Produktname/Kategorie"-Paaren. Die gleiche Kategoriezeichenfolge wird in mehreren Elementen angezeigt. Wenn ein Element aus categories nicht über entsprechende products verfügt, 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 Gewusst wie: Ausführen innerer Verknüpfungen (C#-Programmierhandbuch).

Gruppenverknüpfung

Eine join-Klausel mit einem into-Ausdruck wird als Gruppenverknüpfung bezeichnet.

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 erzeugt eine hierarchische Ergebnissequenz, in der Elemente in der linken Quellsequenz einem oder mehr entsprechenden Elementen in der rechten Quellsequenz zugeordnet werden. Eine Gruppenverknüpfung hat keine Entsprechung im relationalen Sinn; es ist im Grunde eine Sequenz von Objektarrays.

Wenn keine Elemente aus der rechten Quellsequenz gefunden werden, die mit einem Element in der linken Quelle übereinstimmen, erzeugt die join-Klausel ein leeres Array für dieses Element. Die Gruppenverknüpfung ist im Grunde eine innere Gleichheitsverknüpfung, mit der Ausnahme, dass die Ergebnissequenz in Gruppen organisiert ist.

Wenn Sie die Ergebnisse einer Gruppenverknüpfung auswählen, können Sie auf die Elemente zugreifen, jedoch nicht den Entsprechungsschlüssel identifizieren. Es ist daher im Allgemeinen sinnvoller, die Ergebnisse der Gruppenverknüpfung zu einem neuen Typ zusammenzufassen, der auch den Schlüsselnamen enthält, wie im vorherigen Beispiel gezeigt.

Sie können natürlich 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 Gewusst wie: Ausführen von Gruppenverknüpfungen (C#-Programmierhandbuch).

Linke äußere Verknüpfung

In einer linken äußeren Verknüpfung werden alle Elemente in der linken Quellsequenz zurückgegeben, selbst dann, wenn es keine entsprechenden Elemente in der rechten Sequenz gibt. Um eine linke äußere Verknüpfung in LINQ durchzuführen, verwenden Sie die DefaultIfEmpty-Methode zusammen mit einer Gruppenverknüpfung, um ein rechtes Standardelement anzugeben, das erstellt wird, wenn es keine Entsprechung für ein linkes Element gibt. Sie können null als Standardwert für einen beliebigen Referenztyp verwenden, oder Sie können einen benutzerdefinierten Standardtyp festlegen. Im folgenden Beispiel wird ein benutzerdefinierter Standardtyp gezeigt:

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 Gewusst wie: Ausführen linker äußerer Verknüpfungrn (C#-Programmierhandbuch).

Der Gleichheitsoperator

Eine join-Klausel führt eine Gleichheitsverknüpfung aus. Das heißt, Sie können Übereinstimmungen nur auf der Gleichheit von zwei Schlüsseln basieren. Andere Vergleichstypen, z. B. "größer als" oder "ungleich", werden nicht unterstützt. Um sicherzustellen, dass alle Verknüpfungen Gleichheitsverknüpfungen 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 in einem wichtigen Aspekt vom Operator ==. Bei equals nutzt der linke Schlüssel die äußere Quellsequenz, und der rechte Schlüssel nutzt die innere Quelle. Die äußere Quelle befindet sich nur links von equals im Bereich, und die innere Quelle ist nur rechts eingebunden.

Nicht-Gleichheitsverknüpfungen

Sie können mit mehreren from-Klauseln Nicht-Gleichheitsverknüpfungen, Kreuzverknüpfungen und andere benutzerdefinierte Verknüpfungsoperationen durchführen, um neue Sequenzen einzeln in eine Abfrage einzuführen. Weitere Informationen hierzu finden Sie unter Gewusst wie: Ausführen von benutzerdefinierten Verknüpfungsoperationen (C#-Programmierhandbuch).

Verknüpfungen für Objektauflistungen im Vergleich zu relationalen Tabellen

In einem LINQ-Abfrageausdruck werden Verknüpfungsoperationen für Objektauflistungen ausgeführt. Objektauflistungen können nicht auf genau die gleiche Art wie zwei relationale Tabellen verknüpft werden. In LINQ sind explizite join-Klauseln nur erforderlich, wenn zwei Quellsequenzen nicht durch eine Beziehung gebunden sind. Bei der Arbeit mit LINQ to SQL werden Fremdschlüsseltabellen im Objektmodell als Eigenschaften der Primärtabelle dargestellt. In der Northwind-Datenbank weist die Customer-Tabelle beispielsweise eine Fremdschlüsselbeziehung mit der Orders-Tabelle auf. Wenn Sie die Tabellen dem Objektmodell zuordnen, weist die Customer-Klasse eine Orders-Eigenschaft auf, die die Auflistung von Bestellungen, die diesem Kunden zugewiesen sind, enthält. Im Grunde wurde die Verknüpfung bereits für Sie hergestellt.

Weitere Informationen zum Abfragen von verknüpften Tabellen im Kontext von LINQ to SQL finden Sie unter Gewusst wie: Zuordnen von Datenbankbeziehungen (LINQ to SQL).

Zusammengesetzte Schlüssel

Mit einem zusammengesetzten Schlüssel können Sie mehrere Werte auf Gleichheit prüfen. Weitere Informationen hierzu finden Sie unter Gewusst wie: Verknüpfen mithilfe eines zusammengesetzten Schlüssels (C#-Programmierhandbuch). Zusammengesetzte Schlüssel können auch in einer group-Klausel verwendet werden.

Beispiel

Im folgenden Beispiel werden die Ergebnisse einer inneren Verknüpfung, einer Gruppenverknüpfung und einer linken äußeren Verknüpfung der gleichen Datenquellen mit den gleichen übereinstimmenden Schlüsseln verglichen. Diesen Beispielen wird zusätzlicher Code hinzugefügt, um die Ergebnisse in der Konsolenanzeige zu erläutern.

    class JoinDemonstration
    {
        #region Data

        class Product
        {
            public string Name { get; set; }
            public int CategoryID { get; set; }
        }

        class Category
        {
            public string Name { get; set; }
            public int ID { get; set; }
        }

        // Specify the first data source.
        List<Category> categories = new List<Category>()
        { 
            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 List<Product>()
       {
          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();

            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        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, groupJoinQuery3.Count());
            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:", prodGrouping.Count());
                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, der nicht into folgt, wird in einen Join-Methodenaufruf übersetzt. Eine join-Klausel, der into folgt, wird in einen GroupJoin-Methodenaufruf übersetzt.

Siehe auch

Aufgaben

Gewusst wie: Ausführen linker äußerer Verknüpfungrn (C#-Programmierhandbuch)

Gewusst wie: Ausführen innerer Verknüpfungen (C#-Programmierhandbuch)

Gewusst wie: Ausführen von Gruppenverknüpfungen (C#-Programmierhandbuch)

Gewusst wie: Sortieren der Ergebnisse einer Join-Klausel (C#-Programmierhandbuch)

Gewusst wie: Verknüpfen mithilfe eines zusammengesetzten Schlüssels (C#-Programmierhandbuch)

Konzepte

LINQ-Abfrageausdrücke (C#-Programmierhandbuch)

Verknüpfungsvorgänge

Referenz

group-Klausel (C#-Referenz)

Weitere Ressourcen

Abfrageschlüsselwörter (C#-Referenz)