Pour afficher l’article en anglais, activez la case d’option Anglais. Vous pouvez aussi afficher la version anglaise dans une fenêtre contextuelle en faisant glisser le pointeur de la souris sur le texte.
Traduction
Anglais

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

 

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

Une clause join prend deux séquences source 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 clause join compare les clés spécifiées pour déterminer leur égalité en utilisant le mot clé spécial equals. Toutes les jointures effectuées par la clause join sont des équijointures. La forme de la sortie d'une clause join dépend du type spécifique de jointure que vous effectuez. Les trois types de jointures les plus courants sont

  • Jointure interne

  • Jointure groupée

  • Jointure externe gauche

L'exemple suivant présente une équijointure interne simple. Cette requête produit une séquence en deux dimensions des paires « nom de produit/catégorie ». La même chaîne de catégorie apparaîtra dans plusieurs éléments. Si un élément de categories n'a aucun 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 Comment : effectuer des jointures internes (Guide de programmation C#).

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

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 groupée produit une séquence de résultat hiérarchique, qui associe des éléments dans la séquence source gauche à un ou plusieurs éléments correspondants dans la séquence source droite. Une jointure groupée n'a aucun équivalent en termes relationnels ; il s'agit essentiellement d'une séquence de tableaux d'objets.

Si aucun élément de la bonne séquence source est recherché pour correspondre à un élément dans la source gauche, la clause join produira un tableau vide pour cet élément. Par conséquent, la jointure groupée est encore fondamentalement une équijointure interne, mais la séquence de résultat est organisée en groupes.

Si vous sélectionnez uniquement les résultats d'une jointure groupée, vous pouvez accéder aux éléments, mais vous ne pouvez pas identifier la clé à 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 également le nom de la clé, comme indiqué dans l'exemple précédent.

Bien sûr, vous pouvez également utiliser le résultat d'une jointure groupée 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 Comment : effectuer des jointures groupées (Guide de programmation C#).

Dans une jointure externe gauche, tous les éléments de la séquence source gauche sont retournés, même s'ils n'ont aucun élément correspondant dans la séquence droite. Pour effectuer une jointure externe gauche dans LINQ, utilisez la méthode DefaultIfEmpty en association avec une jointure groupée pour spécifier un élément de droite par défaut à produire si un élément de gauche n'a aucune correspondance. Vous pouvez utiliser null comme valeur par défaut pour tout type référence ou vous pouvez spécifier un type par défaut défini par l'utilisateur. Un type par défaut défini par l'utilisateur est présenté dans l'exemple suivant :

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 Procédures : effectuer des jointures externes gauches (Guide de programmation C#).

Une clause join effectue une équijointure. En d'autres termes, vous pouvez baser uniquement les correspondances sur l'égalité de deux clés. D'autres types de comparaisons, comme « supérieur à » ou « différent de », ne sont pas pris en charge. Pour préciser clairement que toutes les jointures sont des équijointures, la clause join utilise le mot clé equals au lieu de l'opérateur ==. Le mot clé equals peut être utilisé uniquement dans une clause join et diffère de l'opérateur == sur un plan important. Avec equals, la clé gauche consomme la séquence source externe et la clé droite consomme la source interne. La source externe est uniquement dans la portée sur le côté gauche de equals et la séquence source interne est uniquement dans la portée sur le côté droit.

Vous pouvez effectuer des non-équijointures, des jointures croisées et d'autres opérations de jointure personnalisées en utilisant plusieurs clauses from pour introduire indépendamment de nouvelles séquences dans une requête. Pour plus d'informations, consultez Comment : effectuer des opérations de jointure personnalisées (Guide de programmation C#).

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 manière que deux tables relationnelles. Dans LINQ, les clauses join explicites sont requises uniquement lorsque deux séquences source ne sont pas liées par une quelconque relation. Lorsque vous travaillez avec LINQ to SQL, les tables de clés étrangères sont représentées dans le modèle objet comme des propriétés de la table primaire. Par exemple, la table Customer a une relation de clé étrangère avec la table Orders dans la base de données Northwind. Lorsque vous mappez les tables au modèle objet, la classe Customer a une propriété Orders qui contient la collection d'Orders associée à ce Customer. En effet, la jointure a déjà été effectuée pour vous.

Pour plus d'informations sur l'interrogation de tables connexes dans le contexte de LINQ to SQL, consultez Procédure : mapper des relations de base de données.

Vous pouvez effectuer un test d'égalité de plusieurs valeurs à l'aide d'une clé composite. Pour plus d'informations, consultez Comment : effectuer des opérations de jointure à l'aide de clés composites (Guide de programmation C#). Des clés composites peuvent également être utilisées dans une clause group.

Exemple

L'exemple suivant compare les résultats d'une jointure interne, d'une jointure groupée et d'une jointure externe gauche sur les mêmes sources de données en utilisant les mêmes clés correspondantes. Du 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 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.
*/

Une clause join qui n'est pas suivie par into est traduite en un appel de méthode Join<TOuter, TInner, TKey, TResult>. Une clause join qui est suivie par into est traduite en un appel de méthode GroupJoin<TOuter, TInner, TKey, TResult>.

Afficher: