join-Klausel (C#-Referenz)

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 Joins sind Equijoins. Die Form der Ausgabe einer join-Klausel ist vom speziellen Typ des Joins, den Sie ausführen, abhängig. Die folgenden drei Jointypen kommen am häufigsten vor:

  • Innerer Join

  • Group Join

  • Linker äußerer Join

Innerer Join

Im folgenden Beispiel wird ein einfacher innerer Equijoin 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).

Group Join

Eine join-Klausel mit einem into-Ausdruck wird als Group Join 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 };

Ein Group Join erzeugt eine hierarchische Ergebnissequenz, in der Elemente in der linken Quellsequenz einem oder mehr entsprechenden Elementen in der rechten Quellsequenz zugeordnet werden. Ein Group Join 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. Der Group Join ist im Grunde ein innerer Equijoin, mit der Ausnahme, dass die Ergebnissequenz in Gruppen organisiert ist.

Wenn Sie die Ergebnisse eines Group Joins auswählen, können Sie auf die Elemente zugreifen, jedoch nicht den Entsprechungsschlüssel identifizieren. Es ist daher im Allgemeinen sinnvoller, die Ergebnisse des Group Joins 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 eines Group Joins 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).

Linker äußerer Join

In einem linken äußeren Join werden alle Elemente in der linken Quellsequenz zurückgegeben, selbst dann, wenn es keine entsprechenden Elemente in der rechten Sequenz gibt. Um einen linken äußeren Join in LINQ durchzuführen, verwenden Sie die DefaultIfEmpty-Methode zusammen mit einem Group Join, 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üpfungen (C#-Programmierhandbuch).

Der Gleichheitsoperator

Eine join-Klausel führt einen Equijoin 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 Joins 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 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-Equijoins

Sie können mit mehreren from-Klauseln Nicht-Equijoins, Cross Joins und andere benutzerdefinierte Joinoperationen 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).

Joins für Objektauflistungen im Vergleich zurelationalen Tabellen

In einem LINQ-Abfrageausdruck werden Joinoperationen 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 der Join bereits für Sie hergestellt.

Weitere Informationen zum Abfragen von verknüpften Tabellen im Kontext von LINQ to SQL finden Sie unter Vorgehensweise: 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 eines inneren Joins, eines Group Joins und eines linken äußeren Joins 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üpfungen (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)

Referenz

group-Klausel (C#-Referenz)

Konzepte

LINQ-Abfrageausdrücke (C#-Programmierhandbuch)

Verknüpfungsvorgänge

Weitere Ressourcen

Abfrageschlüsselwörter (C#-Referenz)