Partager via


Comment : effectuer des opérations de jointure personnalisées (Guide de programmation C#)

Mise à jour : novembre 2007

Cet exemple indique comment effectuer des opérations de jointure qui ne sont pas possibles avec la clause join. Dans une expression de requête, la clause join est limitée à et optimisée pour les équijointures, qui sont de loin le type le plus courant d'opération de jointure. Lorsque vous effectuez une équijointure, vous obtiendrez probablement toujours les meilleures performances en utilisant la clause join.

Toutefois, il existe des cas où la clause join ne peut pas être utilisée :

  • Lorsque la jointure est prédiquée sur une expression d'inégalité (une non-équijointure).

  • Lorsque la jointure est prédiquée sur plusieurs expressions d'égalité ou inégalité.

  • Lorsque vous devez introduire une variable de portée temporaire pour la séquence de côté droit (interne) avant l'opération de jointure.

Pour effectuer des jointures qui ne sont pas des équijointures, vous pouvez utiliser plusieurs clauses from pour introduire chaque source de données indépendamment. Vous appliquez ensuite une expression de prédicat dans une clause where à la variable de portée pour chaque source. L'expression peut également se présenter sous la forme d'un appel de méthode.

Remarque :

Ne confondez pas ce type d'opération de jointure personnalisée avec l'utilisation de plusieurs clauses from pour accéder aux collections internes. Pour plus d'informations, consultez join, clause (Référence C#).

Exemple

La première méthode de l'exemple suivant affiche une jointure croisée simple. Les jointures croisées doivent être utilisées avec prudence, car elles peuvent produire des ensembles de résultats très grands. Toutefois, elles peuvent être utiles dans certains scénarios pour la création de séquences source par rapport auxquelles les requêtes supplémentaires sont exécutées.

La deuxième méthode produit une séquence de tous les produits dont l'ID de catégorie est répertorié dans la liste de catégories sur le côté gauche. Notez l'utilisation de la clause let et de la méthode Contains pour créer un tableau temporaire. Il serait également possible de créer le tableau avant la requête et d'éliminer la première clause from.

    class CustomJoins
    {

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

        // Specify the second data source.
        List<Product> products = new List<Product>()
       {
          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},
          new Product{Name="Ice Cream", CategoryID=007},
          new Product{Name="Mackerel", CategoryID=012},
        };
        #endregion

        static void Main()
        {
            CustomJoins app = new CustomJoins();
            app.CrossJoin();
            app.NonEquijoin();

            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }

        void CrossJoin()
        {
            var crossJoinQuery = 
                from c in categories
                from p in products
                select new { c.ID, p.Name };

            Console.WriteLine("Cross Join Query:");
            foreach (var v in crossJoinQuery)
            {
                Console.WriteLine("{0,-5}{1}", v.ID, v.Name);
            }            
        }

        void NonEquijoin()
        {
            var nonEquijoinQuery = 
                from p in products
                let catIds = from c in categories
                             select c.ID
                where catIds.Contains(p.CategoryID) == true
                select new { Product = p.Name, CategoryID = p.CategoryID };

            Console.WriteLine("Non-equijoin query:");
            foreach (var v in nonEquijoinQuery)
            {
                Console.WriteLine("{0,-5}{1}",  v.CategoryID, v.Product);
            }
        }
    }
    /* Output:
Cross Join Query:
1    Tea
1    Mustard
1    Pickles
1    Carrots
1    Bok Choy
1    Peaches
1    Melons
1    Ice Cream
1    Mackerel
2    Tea
2    Mustard
2    Pickles
2    Carrots
2    Bok Choy
2    Peaches
2    Melons
2    Ice Cream
2    Mackerel
3    Tea
3    Mustard
3    Pickles
3    Carrots
3    Bok Choy
3    Peaches
3    Melons
3    Ice Cream
3    Mackerel
Non-equijoin query:
1    Tea
2    Mustard
2    Pickles
3    Carrots
3    Bok Choy
Press any key to exit.
     */

Dans l'exemple suivant, la requête doit joindre deux séquences en fonction des clés correspondantes qui, dans le cas de la séquence interne (côté droit), ne peuvent pas être obtenues avant la clause de jointure elle-même. Si cette jointure a été effectuée avec une clause join, la méthode Split devra être appelée pour chaque élément. L'utilisation de plusieurs clauses from permet à la requête d'éviter la charge mémoire inhérente à la répétition de l'appel de méthode. Toutefois, puisque la jointure est optimisée, cela peut se révéler plus rapide que l'utilisation de plusieurs clauses from dans ce cas particulier. Les résultats varieront principalement en fonction du coût de l'appel.

class MergeTwoCSVFiles : StudentClass
{
    static void Main()
    {
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type
        // var could be used instead of an explicit type
        IEnumerable<Student> queryNamesScores =
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            where x[2] == s[0]
            select new Student()
            {
                FirstName = x[0],
                LastName = x[1],
                ID = Convert.ToInt32(x[2]),
                ExamScores = (from scoreAsText in s.Skip(1)
                              select Convert.ToInt32(scoreAsText)).
                              ToList()
            };

        // Optional. Store the newly created student objects in memory
        // for faster access in future queries
        List<Student> students = queryNamesScores.ToList();

        foreach (var student in students)
        {
            Console.WriteLine("The average score of {0} {1} is {2}.",
                student.FirstName, student.LastName, student.ExamScores.Average());
        }

        //Keep console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output: 
    The average score of Adams Terry is 85.25.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Garcia Hugo is 85.75.
    The average score of Mortensen Sven is 84.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Omelchenko Svetlana is 82.5.
    The average score of Tucker Lance is 81.75.
    The average score of Tucker Michael is 92.
    The average score of Zabokritski Eugene is 83.
 */

Cet exemple se trouve également dans l'article Comment : remplir des collections d'objets issues de plusieurs sources (LINQ). Pour compiler et exécuter cet exemple, suivez les instructions de cet article.

Compilation du code

  • Créez un projet Visual Studio qui cible la version 3.5 du .NET Framework. Par défaut, le projet possède une référence à System.Core.dll et une directive using pour l'espace de noms System.Linq.

  • Copiez le code dans votre projet.

  • Appuyez sur F5 pour compiler et exécuter le programme.

  • Appuyez sur n'importe quelle touche pour quitter la fenêtre de console.

Voir aussi

Tâches

Comment : classer les résultats d'une clause Join (Guide de programmation C#)

Concepts

Expressions de requête LINQ (Guide de programmation C#)

Opérations de jointure

Référence

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