juillet
2008
Avec C# 2.0, il s’avérait extrêmement laborieux d’effectuer des jointures entre les DataTables d’un DataSet.
Dans ce genre de situation, il y a deux cas :
- Soit on a des relations (System.Data.DataRelation) entre nos DataTables et on peut s’en sortir en utilisant les méthodes GetChildRows/GetParentRows au niveau d’une DataRow.
- Soit on a pas des relations entre nos DataTables et là, on peut s’amuser à faire son petit manager maison.
Avec Linq apparu avec C# 3.0, vous allez tout de suite comprendre la simplicité qu’il en résulte en matière de jointure au niveau d’un DataSet.
Pour les exemples qui vont suivre, je vais m’appuyer sur un DataSet typé avec quelques DataTable générées à partir de la base de données Northwind de Microsoft. Mais rien ne vous empêchera de réaliser les exemples avec un DataSet non typé.
Il est important d’avoir à l’esprit que lors de l’utilisation de Linq avec des DataSets, Linq ne s’occupera en aucun cas du chargement des données, ce n’est pas son but contrairement à Linq to SQL. C’est donc à vous de vous assurer que les données sont chargées dans vos DataTables avant de commencer à effectuer des jointures.
Profitons en pour écrire notre méthode qui s’occupera de créer une instance de notre DataSet typé et de remplir les DataTable de données en provenance de la base de données SQL Server Northwind :
public dsNorthwind CreateDataSet() new DataSetJoin.dsNorthwindTableAdapters.CustomersTableAdapter().Fill(northwind.Customers); return northwind; |
Premier test de jointure
Allons-y doucement et essayons de récupérer la liste des commandes des clients :
public void Sample1() var result = from order in northwind.Orders select new { OrderId = order.OrderID, CustomerName = customers.ContactName }; dataGridView1.DataSource = result.ToList(); |
Première tentative un peu ratée car on se retrouve entre produit cartésien entre les tables « Products » et « OrderDetails ».
On va donc définir le critère qui lie ces deux tables et qui est représenté par « CustomerID ».
public void Sample2() var result = from order in northwind.Orders where order.CustomerID == customers.CustomerID select new { OrderId = order.OrderID, CustomerName = customers.ContactName }; dataGridView1.DataSource = result.ToList(); |
Cependant rien empêche d’ajouter des critères supplémentaires comme par exemple la liste des clients ayant un « ContactName » commençant par ‘A’ :
public void Sample3() var result = from order in northwind.Orders where order.CustomerID == customers.CustomerID select new { OrderId = order.OrderID, CustomerName = customers.ContactName }; dataGridView1.DataSource = result.ToList(); |
Test de jointure normalisée
Malgré la possibilité de faire des jointures au niveau de la clause WHERE, il est pourtant déconseillé de procéder de la sorte pour diverses raisons :
- Distinction difficile entre les critères de filtrage et la jointure en elle-même
- Lisibilité pénible
- …
C’est pourquoi SQL a introduit l’opérateur de jointure normalisé Sql2 « JOIN« . Heureusement pour nous, cet opérateur est disponible au sein de la syntaxe C# 3.0.
On va donc réécrire notre première requête qui est de récupérer la liste des commandes de nos clients mais cette fois en utilisant l’opérateur JOIN.
public void Sample4() var result = from order in northwind.Orders select new { OrderId = order.OrderID, CustomerName = customers.ContactName }; dataGridView1.DataSource = result.ToList(); |
Dans le cas d’une requête JOIN, on récupère uniquement les informations qui ont une correspondance dans les deux tables.
Le résultat de la jointure interne entre les clients et les commandes effectuées par les clients est représentée par l’intersection entre les deux tables. Sur l’image ci-dessus, la partie en orange.
Test de jointure externe
Tout comme dans une jointure interne, la jointure externe permet de récupérer comme résultat l’intersection de deux tables mais en plus, les informations d’une des deux tables dont les informations ne font pas partie de cette intersection.
Concrètement, si on prend comme exemple de récupérer la liste des produits faisant partie des commandes mais aussi ceux qui n’ont pas été utilisé dans des commandes, on appelle ça une jointure externe gauche.
A l’opposé, on peut vouloir récupérer la liste des produits faisant partie des commandes mais aussi les commandes qui n’ont pas de produits, on appelle ça une jointure externe droite.
Il n’y a hélas pas d’opérateur permettant d’effectuer des jointures externes (gauches ou droites). Cela ne va pourtant pas nous empêcher d’obtenir des résultats similaires car vous allez pouvoir constater que C# a tout ce qu’il faut pour réaliser ce type de jointure. L’exemple ci-dessous permet d’effectuer un jointure gauche en récupérant les produits qui n’ont jamais été utilisés dans une commande :
public void Sample5() var result = from product in northwind.Products where pod == null select new { ProductName = product.ProductName}; dataGridView1.DataSource = result.ToList(); |
La première partie de la requête Linq s’occupe de faire une jointure interne entre Products et OrderDetails :
… |
La seconde partie introduit un mot clé du langage C# dont nous n’avons pas encore parlé et qui est « into« . Comme l’explique MSDN, le mot clé « into » peut être utilisé pour définir un identificateur temporaire pour y stocker le résultat d’une clause group, join ou encore select. Dans le cas de l’utilisation de into avec join, on parle de jointure groupée, c’est à dire que le résultat obtenu par la jointure va être réorganisé par groupes. Chaque élément de la table source (donc gauche) sera associé à un tableau qui contient les éléments correspondants provenant de la table de droite. Dans le cas où un élement de la source n’a pas d’élément correspondant, il lui sera associé un tableau vide :
… |
Maintenant que nous avons tous les éléments à notre disposition, nous allons pouvoir utiliser la méthode DefaultIfEmpty() sur notre identificateur « productorderdetails » afin de spécifier un élément de droite par défaut lorsque l’élément de gauche n’a pas de correspondance. Vous avez pu voir que je n’ai pas passé de paramètre à cette méthode pour dire que l’élément de droite est NULL mais rien ne vous empêche de spécifier un type par défaut :
… |
Enfin, on utilise la clause WHERE pour écarter les produits qui sont utilisés dans des commandes :
… |
Et nous avons réalisé une jointure externe gauche avec Linq. Pour ce qui est de la jointure externe droite, comme vous vous en doutez déjà (sinon vous ne seriez pas là), il suffit de faire une jointure gauche en inversant les élément de la jointure.
Facile non ?
Vous pouvez télécharger le projet exemple ici (Pensez à mettre à jour la connectionString dans le fichier de configuration pour pointer sur votre base de données Northwind).
22 Commentaires + Ajouter un commentaire
Déménagement
Next Microsoft CEO
Articles récents
- Changement d’adresse de ce blog
- Article : les nouveautés de C# 5.0
- Modèle Entity Framework généré avec SQL Server 2008 et utilisé sous SQL Server 2005
- Les photos, vidéos et cadeaux des TechDays 2011
- La première journée en ligne sur TechDays TV
- Suivez les TechDays 2011 comme si vous y étiez sur la chaîne TechDays TV
- Téléchargez le programme des conférence des TechDays 2011
- Dernier jour des TechDays = plus de chance de remporter des cadeaux sur le stand de dvp !
- Venez à notre rencontre lors des TechDays 2011
- Les flux à ne pas rater lors des TechDays 2011
- Les TechDays 2011 s’invite sur votre smartphone Windows Phone 7
- Exclu des cadeaux du concours Developpez.com lors des TechDays 2011
- Ouverture de la galerie d’images sur notre site des TechDays
- Partagez votre expérience des Microsoft TechDays
- Grand concours Microsoft “FAN des TechDays 2011” : épisode 2 !
- Mettez-vous aux couleurs des TechDays 2011 !
- Developpez.com aux TechDays 2011
- Le Challenge Azure 2010 est fini !
- Suivez la keynote de la pdc10 en direct sur internet à 18h !
- MVP Visual C# 2010 – 2011 !!!
Merci pour cet article très instructif.
Cepandant l’exemple « Sample5″ ne renvoit rien avec les données par défaut de la base Northwind, on croit alors à une erreur de la requête…
Ci-dessous un nouvel exemple qui renvoit bien des résultats, ce qui prouve que la requête fonctionne correctement.
public void Sample5()
{
dsNorthwind northwind = CreateDataSet();
var result = from customer in northwind.Customers
join order in northwind.Orders
on customer.CustomerID equals order.CustomerID
into customerorders
from order2 in customerorders.DefaultIfEmpty()
where order2 == null
select new { CompanyName = customer.CompanyName };
dataGridView1.DataSource = result.ToList();
}