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
Concepts
Expressions de requête LINQ (Guide de programmation C#)
Historique des modifications
Date |
Historique |
Motif |
---|---|---|
Août 2010 |
Le deuxième exemple a été modifié afin de faciliter son exécution. |
Améliorations apportées aux informations. |