join-component (C#-verwijzing)

De join component is handig voor het koppelen van elementen uit verschillende bronreeksen die geen directe relatie hebben in het objectmodel. De enige vereiste is dat de elementen in elke bron een bepaalde waarde delen die voor gelijkheid kunnen worden vergeleken. Een voedseldistributeur kan bijvoorbeeld een lijst hebben met leveranciers van een bepaald product en een lijst met kopers. Een join component kan bijvoorbeeld worden gebruikt om een lijst te maken van de leveranciers en kopers van dat product die zich allemaal in dezelfde opgegeven regio bevinden.

Een join component neemt twee bronreeksen als invoer. De elementen in elke reeks moeten een eigenschap zijn of bevatten die kan worden vergeleken met een bijbehorende eigenschap in de andere reeks. De join component vergelijkt de opgegeven sleutels voor gelijkheid met behulp van het speciale equals trefwoord. Alle joins die door de join component worden uitgevoerd, zijn equijoins. De vorm van de uitvoer van een join component is afhankelijk van het specifieke type join dat u uitvoert. Hier volgen drie meest voorkomende jointypen:

  • Inner join

  • Groepsdeelname

  • Left outer join

Inner join

In het volgende voorbeeld ziet u een eenvoudige binnenste equijoin. Deze query produceert een platte reeks 'productnaam/categorie'-paren. Dezelfde categorietekenreeks wordt weergegeven in meerdere elementen. Als een element van categories geen overeenkomende productselementen bevat, wordt die categorie niet weergegeven in de resultaten.

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

Zie Inner Joins uitvoeren voor meer informatie.

Groepsdeelname

Een join component met een into expressie wordt een groepsdeelname genoemd.

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 };

Een groepsdeelname produceert een hiƫrarchische resultatenreeks, die elementen in de linkerbronvolgorde koppelt aan een of meer overeenkomende elementen in de bronreeks aan de rechterkant. Een groepsdeelname heeft geen equivalent in relationele termen; het is in wezen een reeks objectmatrices.

Als er geen elementen uit de juiste bronreeks worden gevonden die overeenkomen met een element in de linkerbron, produceert de join component een lege matrix voor dat item. Daarom is de groepsdeelname nog steeds een inner-equijoin, behalve dat de resultatenreeks in groepen is ingedeeld.

Als u alleen de resultaten van een groepsdeelname selecteert, hebt u toegang tot de items, maar kunt u de sleutel waarop ze overeenkomen niet identificeren. Daarom is het over het algemeen handiger om de resultaten van de groepsdeelname te selecteren in een nieuw type dat ook de sleutelnaam heeft, zoals wordt weergegeven in het vorige voorbeeld.

U kunt natuurlijk ook het resultaat van een groepsdeelname gebruiken als de generator van een andere subquery:

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;

Zie Gegroepeerde joins uitvoeren voor meer informatie.

Left outer join

In een left outer join worden alle elementen in de linkerbronreeks geretourneerd, zelfs als er geen overeenkomende elementen in de juiste volgorde staan. Als u een left outer join in LINQ wilt uitvoeren, gebruikt u de DefaultIfEmpty methode in combinatie met een groepsdeelname om een standaard element aan de rechterkant op te geven dat moet worden geproduceerd als een element aan de linkerkant geen overeenkomsten heeft. U kunt als standaardwaarde voor elk verwijzingstype gebruiken null of u kunt een door de gebruiker gedefinieerd standaardtype opgeven. In het volgende voorbeeld wordt een door de gebruiker gedefinieerd standaardtype weergegeven:

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 };

Zie Left outer joins uitvoeren voor meer informatie.

De gelijktekeningsoperator

Een join component voert een equijoin uit. Met andere woorden, u kunt alleen overeenkomsten baseren op de gelijkheid van twee sleutels. Andere typen vergelijkingen, zoals 'groter dan' of 'is niet gelijk aan' worden niet ondersteund. Om duidelijk te maken dat alle joins equijoins zijn, gebruikt de join component het equals trefwoord in plaats van de == operator. Het equals trefwoord kan alleen worden gebruikt in een join component en verschilt van de == operator op een aantal belangrijke manieren. Bij het vergelijken van tekenreeksen heeft equals een overbelasting om te vergelijken op waarde en gebruikt de operator == verwijzings gelijkheid. Wanneer beide zijden van de vergelijking identieke tekenreeksvariabelen equals hebben en == hetzelfde resultaat bereiken: true. Dat komt omdat, wanneer een programma twee of meer equivalente tekenreeksvariabelen declareert, de compiler ze allemaal op dezelfde locatie opslaat. Dit staat bekend als intern. Een ander belangrijk verschil is de null-vergelijking: null equals null wordt geƫvalueerd als onwaar met equals de operator, in plaats van == operator die deze als waar evalueert. Ten slotte is het bereikgedrag anders: de equalslinkersleutel verbruikt de buitenste bronreeks en de rechtersleutel verbruikt de binnenste bronbron. De buitenste bron bevindt zich alleen in het bereik aan de linkerkant en equals de binnenste bronreeks bevindt zich alleen in het bereik aan de rechterkant.

Niet-equijoins

U kunt niet-equijoins, cross joins en andere aangepaste joinbewerkingen uitvoeren met behulp van meerdere from componenten om nieuwe reeksen onafhankelijk in een query te introduceren. Zie Aangepaste joinbewerkingen uitvoeren voor meer informatie.

Joins voor objectverzamelingen versus relationele tabellen

In een LINQ-queryexpressie worden joinbewerkingen uitgevoerd op objectverzamelingen. Objectverzamelingen kunnen niet op exact dezelfde manier worden 'samengevoegd' als twee relationele tabellen. In LINQ zijn expliciete join componenten alleen vereist wanneer twee bronreeksen niet aan een relatie zijn gekoppeld. Wanneer u met LINQ naar SQL werkt, worden refererende-sleuteltabellen in het objectmodel weergegeven als eigenschappen van de primaire tabel. In de database Northwind heeft de tabel Klant bijvoorbeeld een refererende-sleutelrelatie met de tabel Orders. Wanneer u de tabellen toe te wijzen aan het objectmodel, heeft de klasse Klant een eigenschap Orders die de verzameling Orders bevat die aan die klant is gekoppeld. In feite is de join al voor u gedaan.

Zie Procedure: Databaserelaties toewijzen voor meer informatie over het uitvoeren van query's in gerelateerde tabellen in de context van LINQ naar SQL.

Samengestelde sleutels

U kunt testen op gelijkheid van meerdere waarden met behulp van een samengestelde sleutel. Zie Join met behulp van samengestelde sleutels voor meer informatie. Samengestelde sleutels kunnen ook worden gebruikt in een group component.

Opmerking

In het volgende voorbeeld worden de resultaten van een inner join, een groepsdeelname en een left outer join op dezelfde gegevensbronnen vergeleken met behulp van dezelfde overeenkomende sleutels. Aan deze voorbeelden wordt extra code toegevoegd om de resultaten in de consoleweergave te verduidelijken.

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.
*/

Opmerkingen

Een join component die niet wordt gevolgd, into wordt omgezet in een Join methode-aanroep. Een join component die wordt gevolgd door into , wordt omgezet in een GroupJoin methode-aanroep.

Zie ook