Partager via


join, clause (référence C#)

La join clause est utile pour associer des éléments de différentes séquences sources qui n’ont aucune relation directe dans le modèle objet. La seule exigence est que les éléments de chaque source partagent une valeur qui peut être comparée pour l’égalité. Par exemple, un distributeur alimentaire peut avoir une liste de fournisseurs d’un certain produit et une liste d’acheteurs. Une join clause peut être utilisée, par exemple, pour créer une liste des fournisseurs et acheteurs de ce produit qui se trouvent tous dans la même région spécifiée.

Une join clause prend deux séquences sources comme entrée. Les éléments de chaque séquence doivent être ou contenir une propriété qui peut être comparée à une propriété correspondante dans l’autre séquence. La join clause compare les clés spécifiées pour l’égalité à l’aide du mot clé spécial equals . Toutes les jointures effectuées par la join clause sont des équijoins. La forme de la sortie d’une join clause dépend du type spécifique de jointure que vous effectuez. Voici trois types de jointures les plus courants :

  • Jointure interne

  • Rejoindre un groupe

  • Jointure externe gauche

Jointure interne

L’exemple suivant montre un équijoin interne simple. Cette requête produit une séquence plate de paires « product name / category ». La même chaîne de catégorie apparaît dans plusieurs éléments. Si un élément de categories n’a pas de products correspondant, cette catégorie n’apparaîtra pas dans les résultats.

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

Pour plus d’informations, consultez Effectuer des jointures internes.

Jointure groupée

Une join clause avec une into expression est appelée jointure de groupe.

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

Une jointure de groupe produit une séquence de résultats hiérarchique, qui associe des éléments dans la séquence source de gauche à un ou plusieurs éléments correspondants dans la séquence source de droite. Une jointure de groupe n’a pas d’équivalent en termes relationnels ; il s’agit essentiellement d’une séquence de tableaux d’objets.

Si aucun élément de la séquence source de droite n’est trouvé pour correspondre à un élément dans la source gauche, la join clause produit un tableau vide pour cet élément. Par conséquent, la jointure groupée est fondamentalement une équijointure interne, excepté que la séquence de résultats est organisée en groupes.

Si vous sélectionnez simplement les résultats d’une jointure de groupe, vous pouvez accéder aux éléments, mais vous ne pouvez pas identifier la clé sur laquelle ils correspondent. Par conséquent, il est généralement plus utile de sélectionner les résultats de la jointure groupée dans un nouveau type qui a aussi le nom de clé, comme illustré dans l’exemple précédent.

Vous pouvez également utiliser le résultat d’une jointure de groupe comme générateur d’une autre sous-requête :

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;

Pour plus d’informations, consultez Effectuer des jointures groupées.

Jointure externe gauche

Dans une jointure externe gauche, tous les éléments de la séquence source gauche sont retournés, même si aucun élément correspondant n’est dans la séquence droite. Pour effectuer une jointure externe gauche dans LINQ, utilisez la DefaultIfEmpty méthode en combinaison avec une jointure de groupe pour spécifier un élément côté droit par défaut à produire si un élément de gauche n’a aucune correspondance. Vous pouvez utiliser null comme valeur par défaut pour n’importe quel type de référence, ou vous pouvez spécifier un type par défaut défini par l’utilisateur. Dans l’exemple suivant, un type par défaut défini par l’utilisateur est illustré :

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

Pour plus d’informations, consultez Effectuer des jointures externes gauches.

Opérateur Égal

Une clause join effectue une équijointure. En d’autres termes, vous pouvez baser les correspondances seulement sur l’égalité de deux clés. D’autres types de comparaisons tels que « supérieur à » ou « n'est pas égal à » ne sont pas pris en charge. Pour indiquer clairement que toutes les jointures sont des équijoins, la join clause utilise le equals mot clé au lieu de l’opérateur == . Le equals mot clé ne peut être utilisé que dans une join clause et il diffère de l’opérateur == de certaines manières importantes. Lors de la comparaison de chaînes, equals a une surcharge à comparer par valeur et l’opérateur == utilise l’égalité de référence. Lorsque les deux côtés de la comparaison ont des variables de chaîne identiques et equals== atteignent le même résultat : true. C’est parce que, lorsqu’un programme déclare deux variables de chaîne équivalentes ou plus, le compilateur les stocke tous dans le même emplacement. C’est ce qu’on appelle l’internement. La comparaison des valeurs Null constitue une autre différence importante : null equals null est évalué comme false avec l’opérateur equals, alors que l’opérateur == l’évalue comme true. Enfin, le comportement lié à l’étendue est différent : avec equals, la clé de gauche consomme la séquence source externe et la clé de droite consomme la source interne. La source externe est seulement dans l’étendue du côté gauche de equals et la séquence source interne est seulement dans l’étendue du côté droit.

Non-équijointures

Vous pouvez effectuer des jointures non équivalentes, des jointures croisées et d'autres opérations de jointure personnalisées en utilisant plusieurs clauses from pour introduire de nouvelles séquences indépendamment dans une requête. Pour plus d’informations, consultez Effectuer des opérations de jointure personnalisées.

Jointures sur des collections d’objets et sur des tables relationnelles

Dans une expression de requête LINQ, les opérations de jointure sont effectuées sur les collections d’objets. Les collections d’objets ne peuvent pas être « jointes » exactement de la même façon que deux tables relationnelles. Dans LINQ, les clauses explicites join ne sont requises que lorsque deux séquences sources ne sont liées par aucune relation. Lorsque vous utilisez LINQ to SQL, les tables de clés étrangères sont représentées dans le modèle objet en tant que propriétés de la table primaire. Par exemple, dans la base de données Northwind, la table Customer a une relation de clé étrangère avec la table Orders. Lorsque vous mappez les tables au modèle objet, la classe Customer possède une propriété Orders qui contient la collection Orders associée à ce client. En réalité, la jointure a déjà été effectuée pour vous.

Pour plus d’informations sur l’interrogation sur les tables associées dans le contexte de LINQ to SQL, consultez Guide pratique pour mapper les relations de base de données.

Clés composites

Vous pouvez tester l’égalité de plusieurs valeurs à l’aide d’une clé composite. Pour plus d’informations, consultez Effectuer des opérations de jointure à l’aide de clés composites. Les clés composites peuvent également être utilisées dans une group clause.

Exemple :

L’exemple suivant compare les résultats d’une jointure interne, d’une jointure de groupe et d’une jointure externe gauche sur les mêmes sources de données à l’aide des mêmes clés correspondantes. Un code supplémentaire est ajouté à ces exemples pour clarifier les résultats dans l’affichage de la console.

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

Remarques

Une join clause qui n’est pas suivie into est traduite en un Join appel de méthode. Une clause join qui est suivie par into est traduite en un appel de méthode GroupJoin.

Voir aussi