Partager via


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

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, la clause join ne peut pas être utilisée dans les cas suivants :

  • 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.

Notes

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 est é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 join 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
{
    static void Main()
    {
        // See section Compiling the Code for information about the data files. 
        string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
        string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");

        // Merge the data sources using a named type. 
        // You could use var instead of an explicit type for the query.
        IEnumerable<Student> queryNamesScores =
            // Split each line in the data files into an array of strings. 
            from name in names
            let x = name.Split(',')
            from score in scores
            let s = score.Split(',')
            // Look for matching IDs from the two data files. 
            where x[2] == s[0]
            // If the IDs match, build a Student object. 
            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();
    }
}

class Student
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }
    public List<int> ExamScores { get; set; }
}

/* Output: 
    The average score of Omelchenko Svetlana is 82.5.
    The average score of O'Donnell Claire is 72.25.
    The average score of Mortensen Sven is 84.5.
    The average score of Garcia Cesar is 88.25.
    The average score of Garcia Debra is 67.
    The average score of Fakhouri Fadi is 92.25.
    The average score of Feng Hanying is 88.
    The average score of Garcia Hugo is 85.75.
    The average score of Tucker Lance is 81.75.
    The average score of Adams Terry is 85.25.
    The average score of Zabokritski Eugene is 83.
    The average score of Tucker Michael is 92.
 */

Compilation du code

  • Créez un projet d'application console Visual Studio qui cible .NET Framework 3.5 ou version ultérieure. 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.

  • Remplacez la classe Program par le code dans l'exemple précédent.

  • Suivez les instructions dans Comment : joindre du contenu issu de différents fichiers (LINQ) pour installer les fichiers de données, scores.csv et names.csv.

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

  • Appuyez sur une 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#)

Référence

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

Concepts

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

Opérations de jointure