Vous êtes sur la page 1sur 632

Rfrence

LINQ
http://www.free-livres.com/

Language Integrated Query en C# 2008


Joseph C. Rattz

Rseaux et tlcom Programmation

Gnie logiciel

Scurit Systme dexploitation

LINQ
Language Integrated Query en C# 2008
Joseph C. Rattz, Jr.

Traduction : Michel Martin, MVP Relecture technique : Mitsuru Furuta, Microsoft France Pierrick Gourlain, MVP Client Application Matthieu Mezil, MVP C#

Pearson Education France a apport le plus grand soin la ralisation de ce livre an de vous fournir une information complte et able. Cependant, Pearson Education France nassume de responsabilits, ni pour son utilisation, ni pour les contrefaons de brevets ou atteintes aux droits de tierces personnes qui pourraient rsulter de cette utilisation. Les exemples ou les programmes prsents dans cet ouvrage sont fournis pour illustrer les descriptions thoriques. Ils ne sont en aucun cas destins une utilisation commerciale ou professionnelle. Pearson Education France ne pourra en aucun cas tre tenu pour responsable des prjudices ou dommages de quelque nature que ce soit pouvant rsulter de lutilisation de ces exemples ou programmes. Tous les noms de produits ou marques cits dans ce livre sont des marques dposes par leurs propritaires respectifs.

Publi par Pearson Education France 47 bis, rue des Vinaigriers 75010 PARIS Tl. : 01 72 74 90 00 www.pearson.fr Mise en pages : TyPAO ISBN : 978-2-7440-4106-8 Copyright 2009 Pearson Education France Tous droits rservs

Titre original : Pro LINQ Language Integrated Query in C# 2008 Traduit de lamricain par Michel Martin Relecture technique : Mitsuru Furuta, Pierrick Gourlain, Matthieu Mezil ISBN original : 978-1-59059-789-9 Copyright 2007 by Joseph C. Rattz, Jr. All rights reserved dition originale publie par Apress 2855 Telegraph Avenue, Suite 600, Berkeley, CA 94705 www.apress.com

Aucune reprsentation ou reproduction, mme partielle, autre que celles prvues larticle L. 122-5 2 et 3 a) du code de la proprit intellectuelle ne peut tre faite sans lautorisation expresse de Pearson Education France ou, le cas chant, sans le respect des modalits prvues larticle L. 122-10 dudit code. No part of this book may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording or by any information storage retrieval system, without permission from Pearson Education, Inc.

Table des matires


propos de lauteur .......................................................................................................... Traducteur et relecteurs techniques ................................................................................ Partie I LINQ et C# 2008 1 Hello LINQ .................................................................................................................... Un changement de paradigme .................................................................................... Interrogation XML .......................................................................................... Interrogation dune base de donnes SQL Server ........................................... Introduction ................................................................................................................ LINQ et linterrogation des donnes ............................................................... Composants ..................................................................................................... Comment travailler avec LINQ ....................................................................... LINQ ne se limite pas aux requtes ............................................................................ Quelques conseils avant de commencer ..................................................................... Utilisez le mot-cl var si vous ntes pas laise ........................................... Utilisez les oprateurs Cast ou OfType pour les collections hrites ............. Prfrez loprateur OfType loprateur Cast .............................................. Les requtes aussi peuvent tre bogues ......................................................... Sachez tirer parti des requtes diffres .......................................................... Utiliser le log du DataContext ....................................................................... Utilisez le forum LINQ ................................................................................... Rsum ....................................................................................................................... 2 Amliorations de C# 3.0 pour LINQ .......................................................................... Les nouveauts du langage C# 3.0 ............................................................................. Les expressions lambda ................................................................................... Arbres dexpressions ....................................................................................... Le mot-cl var, linitialisation dobjets et les types anonymes ...................... Mthodes dextension ..................................................................................... Mthodes partielles ......................................................................................... Expressions de requte .................................................................................... Rsum ....................................................................................................................... 3 3 4 5 6 7 7 9 9 12 12 14 15 15 16 17 18 18 19 19 20 25 26 31 37 39 49 XI XIII

IV

Table des matires

Partie II LINQ to Objects 3 Introduction LINQ to Objects .................................................................................. Vue densemble de LINQ to Objects .......................................................................... IEnumerable<T>, squences et oprateurs de requte standard ................................ IEnumerable<T>, yield et requtes diffres ......................................................... Dlgus Func ................................................................................................. Les oprateurs de requte standard ............................................................................. Rsum ............................................................................................................ 4 Les oprateurs diffrs ................................................................................................. Espaces de noms rfrencs ....................................................................................... Assemblies rfrencs ................................................................................................ Classes communes ...................................................................................................... Les oprateurs diffrs, par groupes fonctionnels ...................................................... Restriction ....................................................................................................... Projection ........................................................................................................ Partage ............................................................................................................ Concatnation .................................................................................................. Tri .................................................................................................................... Oprateurs de jointure ..................................................................................... Oprateurs de regroupement ........................................................................... Oprateurs dinitialisation ............................................................................... Oprateurs de conversion ................................................................................ Oprateurs ddis aux lments ...................................................................... Oprateurs de gnration ................................................................................. Rsum ....................................................................................................................... 5 Les oprateurs non diffrs ......................................................................................... Espaces de noms rfrencs ....................................................................................... Classes communes ...................................................................................................... Les oprateurs non diffrs, par groupes fonctionnels ............................................... Oprateurs de conversion ................................................................................ Oprateurs dgalit ........................................................................................ Oprateurs agissant au niveau des lments ................................................... Quanticateurs ................................................................................................ Fonctions de comptage .................................................................................... Rsum ....................................................................................................................... 53 53 54 55 58 59 61 63 63 64 64 65 65 67 76 83 85 100 104 110 115 122 126 129 131 131 131 134 134 145 148 160 165 178

Table des matires

Partie III LINQ to XML 6 Introduction LINQ to XML ..................................................................................... Introduction ................................................................................................................ Se passer de lAPI W3C DOM XML ......................................................................... Rsum ....................................................................................................................... 7 LAPI LINQ to XML .................................................................................................... Espaces de noms rfrencs ....................................................................................... Amliorations de lAPI ............................................................................................... La construction fonctionnelle simplie la cration darbres XML ................. Llment, point central dun objet XML ....................................................... Noms, espaces de noms et prxes ................................................................. Extraction de valeurs de nuds ....................................................................... Le modle dobjet LINQ to XML .............................................................................. Excution diffre des requtes, suppression de nuds et bogue dHalloween ......... Cration XML ............................................................................................................ Cration dlments avec XElement ............................................................... Cration dattributs avec XAttribute ............................................................ Cration de commentaires avec XComment ..................................................... Cration de conteneurs avec XContainer ....................................................... Cration de dclarations avec XDeclaration ................................................. Cration de types de documents avec XDocumentType .................................. Cration de documents avec XDocument ......................................................... Cration de noms avec XName ......................................................................... Cration despaces de noms avec XNamespace ............................................... Cration de nuds avec XNode ........................................................................ Cration dinstructions de traitement avec XProcessingInstruction ......... Cration dlments streaming avec XStreamingElement .......................... Cration de textes avec XText ......................................................................... Dnition dun objet CData avec XCData ....................................................... Sauvegarde de chiers XML ...................................................................................... Sauvegardes avec XDocument.Save() ........................................................... Sauvegarde avec XElement.Save ................................................................... Lecture de chiers XML ............................................................................................ Lecture avec XDocument.Load() ................................................................... Lecture avec XElement.Load() ..................................................................... Extraction avec XDocument.Parse() ou XElement.Parse() ....................... Dplacements XML .................................................................................................... Proprits de dplacement ............................................................................... Mthodes de dplacement ............................................................................... 183 185 185 187 189 189 190 190 192 194 196 199 200 202 202 205 206 207 207 208 209 210 211 211 211 213 215 215 216 216 217 218 218 219 220 221 222 225

VI

Table des matires

Modication de donnes XML ................................................................................... Ajout de nuds ............................................................................................... Suppression de nuds ..................................................................................... Mise jour de nuds ...................................................................................... XElement.SetElementValue() sur des objets enfants de XElement ............ Attributs XML ............................................................................................................ Cration dun attribut ...................................................................................... Dplacements dans un attribut ........................................................................ Modication dattributs ................................................................................... Annotations XML ....................................................................................................... Ajout dannotations avec XObject.AddAnnotation() .................................. Accs aux annotations avec XObject.Annotation() ou XObject.Annotations() .......................................................................... Suppression dannotations avec XObject.RemoveAnnotations() .............. Exemples dannotations .................................................................................. vnements XML ....................................................................................................... XObject.Changing ....................................................................................... XObject.Changed ........................................................................................ Quelques exemples dvnements .................................................................. Le bogue dHalloween .................................................................................... Rsum ....................................................................................................................... 8 Les oprateurs LINQ to XML ..................................................................................... Introduction aux oprateurs LINQ to XML ............................................................... Oprateur Ancestors ................................................................................................. Prototypes ........................................................................................................ Exemples ......................................................................................................... Oprateur AncestorsAndSelf ................................................................................... Prototypes ........................................................................................................ Exemples ......................................................................................................... Oprateur Attributes ............................................................................................... Prototypes ........................................................................................................ Exemples ......................................................................................................... Oprateur DescendantNodes ..................................................................................... Prototype ......................................................................................................... Exemple ........................................................................................................... Oprateur DescendantNodesAndSelf ....................................................................... Prototype ......................................................................................................... Exemple ........................................................................................................... Oprateur Descendants ............................................................................................. Prototypes ........................................................................................................ Exemples ......................................................................................................... Oprateur DescendantsAndSelf ............................................................................... Prototypes ........................................................................................................ Exemples .........................................................................................................

238 238 242 245 248 250 250 250 253 258 258 258 258 259 262 262 262 263 267 267 269 270 270 270 271 274 274 275 277 277 277 279 279 279 280 280 281 282 282 282 284 284 284

Table des matires

VII

Oprateur Elements ................................................................................................... Prototypes ........................................................................................................ Exemples ......................................................................................................... Oprateur InDocumentOrder ..................................................................................... Prototype ......................................................................................................... Exemple ........................................................................................................... Oprateur Nodes ......................................................................................................... Prototype ......................................................................................................... Exemple ........................................................................................................... Oprateur Remove ....................................................................................................... Prototypes ........................................................................................................ Exemples ......................................................................................................... Rsum ....................................................................................................................... 9 Les autres possibilits de XML ................................................................................... Espaces de noms rfrencs ....................................................................................... Requtes ..................................................................................................................... La description du chemin nest pas une obligation ......................................... Une requte complexe ..................................................................................... Transformations .......................................................................................................... Transformations avec XSLT ............................................................................ Transformations avec la construction fonctionnelle ........................................ Astuces ............................................................................................................ Validation .................................................................................................................... Les mthodes dextension ............................................................................... Prototypes ........................................................................................................ Obtention dun schma XML .......................................................................... Exemples ......................................................................................................... XPath .......................................................................................................................... Prototypes ........................................................................................................ Rsum ....................................................................................................................... Partie IV LINQ to DataSet 10 LINQ to DataSet ......................................................................................................... Rfrence des assemblies ........................................................................................... Espaces de noms rfrencs ....................................................................................... Code commun utilis dans les exemples .................................................................... Oprateurs ddis aux DataRow .................................................................................. Oprateur Distinct ........................................................................................ Oprateur Except ............................................................................................ Oprateur Intersect ......................................................................................

287 287 287 289 289 289 290 290 291 292 292 292 294 295 295 296 296 298 303 304 306 308 314 314 314 315 317 328 328 329

333 334 334 334 336 336 340 342

VIII

Table des matires

Oprateur Union .............................................................................................. Oprateur SequencialEqual .......................................................................... Oprateurs ddis aux champs ................................................................................... Oprateur Field<T> ........................................................................................ Oprateur SetField<T> .................................................................................. Oprateurs ddis aux DataTable .............................................................................. Oprateur AsEnumerable ................................................................................ Oprateur CopyToDataTable<DataRow> ........................................................ Rsum ....................................................................................................................... 11 Possibilits complmentaires des DataSet ................................................................ Espaces de noms rfrencs ....................................................................................... DataSets typs ........................................................................................................... Un exemple plus proche de la ralit .......................................................................... Rsum ....................................................................................................................... Partie V LINQ to SQL 12 Introduction LINQ to SQL ..................................................................................... Introduction LINQ to SQL ...................................................................................... La classe DataContext ................................................................................... Classes dentits .............................................................................................. Associations .................................................................................................... Dtection de conit daccs concurrentiel ...................................................... Rsolution de conit daccs concurrentiel .................................................... Prrequis pour excuter les exemples ......................................................................... Obtenir la version approprie de la base de donnes Northwind .................... Gnration des classes dentit de la base de donnes Northwind ................. Gnration du chier de mappage XML de la base de donnes Northwind ... Utilisation de lAPI LINQ to SQL ............................................................................. IQueryable<T> ......................................................................................................... Quelques mthodes communes .................................................................................. La mthode GetStringFromDb() ................................................................... La mthode ExecuteStatementInDb() ......................................................... Rsum ....................................................................................................................... 13 Astuces et outils pour LINQ to SQL ......................................................................... Introduction aux astuces et aux outils pour LINQ to SQL ......................................... Astuces ....................................................................................................................... La proprit DataContext.Log ...................................................................... La mthode GetChangeSet() ......................................................................... Utilisation de classes partielles ou de chiers de mappage ............................. Utilisation de mthodes partielles ...................................................................

344 346 347 351 356 359 359 360 365 367 367 367 369 372

377 378 380 381 382 383 383 383 384 384 385 386 386 386 387 388 388 391 391 392 392 393 393 394

Table des matires

IX

Outils .......................................................................................................................... SQLMetal ........................................................................................................ Le Concepteur Objet/Relationnel .................................................................... Utiliser SQLMetal et le Concepteur O/R ................................................................... Rsum ....................................................................................................................... 14 Oprations standard sur les bases de donnes ......................................................... Prrequis pour excuter les exemples ......................................................................... Mthodes communes ....................................................................................... Utilisation de lAPI LINQ to SQL .................................................................. Oprations standard de bases de donnes ................................................................... Insertions ......................................................................................................... Requtes .......................................................................................................... Mises jour ..................................................................................................... Suppressions .................................................................................................... Surcharger les mthodes de mise jour des bases de donnes .................................. Surcharge de la mthode Insert .................................................................... Surcharge de la mthode Update .................................................................... Surcharge de la mthode Delete .................................................................... Exemple ........................................................................................................... Surcharge dans le Concepteur Objet/Relationnel ............................................ Considrations ................................................................................................. Traduction SQL .......................................................................................................... Rsum ....................................................................................................................... 15 Les classes dentit LINQ to SQL ............................................................................. Prrequis pour excuter les exemples ......................................................................... Les classes dentit ..................................................................................................... Cration de classes dentit ............................................................................. Schma de chier de mappage externe XML ................................................. Projection dans des classes dentit/des classes de non-entit ........................ Dans une projection, prfrez linitialisation dobjet la construction paramtre ............................................................. Extension des classes dentit avec des mthodes partielles ...................................... Les classes API importantes de System.Data.Linq ................................................. EntitySet<T> ................................................................................................. EntityRef<T> ................................................................................................. Table<T> ......................................................................................................... IExecuteResult ............................................................................................. ISingleResult<T> ........................................................................................ IMultipleResults ......................................................................................... Rsum ....................................................................................................................... 16 La classe DataContext................................................................................................ Prrequis pour excuter les exemples .........................................................................

394 394 401 414 415 417 417 418 418 418 418 423 446 450 453 453 454 454 454 457 457 457 459 461 461 461 462 493 494 496 499 501 502 502 504 505 506 506 508 509 509

Table des matires

Mthodes communes ....................................................................................... Utilisation de lAPI LINQ to SQL .................................................................. La classe [Your]DataContext .................................................................................. La classe DataContext .............................................................................................. Principaux objectifs ......................................................................................... Datacontext() et [Your]DataContext() .................................................... SubmitChanges() ........................................................................................... DatabaseExists() ......................................................................................... CreateDatabase() ......................................................................................... DeleteDatabase() ........................................................................................ CreateMethodCallQuery() ........................................................................... ExecuteQuery() ............................................................................................ Translate() ................................................................................................... ExecuteCommand() ......................................................................................... ExecuteMethodCall() ................................................................................... GetCommand() ................................................................................................. GetChangeSet() ............................................................................................. GetTable() ..................................................................................................... Refresh() ....................................................................................................... Rsum ....................................................................................................................... 17 Les conits daccs concurrentiels ............................................................................ Prrequis pour excuter les exemples ......................................................................... Mthodes communes ....................................................................................... Utilisation de lAPI LINQ to SQL .................................................................. Conits daccs concurrentiels ................................................................................... Contrle daccs concurrentiel optimiste ........................................................ Contrle daccs concurrentiel pessimiste ...................................................... Une approche alternative pour les middle-tier et les serveurs ......................... Rsum ....................................................................................................................... 18 Informations complmentaires sur SQL .................................................................. Prrequis pour excuter les exemples ......................................................................... Utilisation de lAPI LINQ to SQL .................................................................. Utilisation de lAPI LINQ to XML ................................................................. Les vues dune base de donnes ................................................................................. Hritage des classes dentit ....................................................................................... Transactions ................................................................................................................ Rsum ....................................................................................................................... Index ...................................................................................................................................

509 509 510 510 513 520 532 539 540 541 542 543 546 547 549 557 558 560 562 568 571 571 571 571 571 572 585 588 591 593 593 593 593 593 595 601 603 607

propos de lauteur
Joseph C. Rattz Jr a commenc sa carrire de dveloppeur en 1990, lorsquun ami lui a demand de laide pour dvelopper lditeur de texte "ANSI Master" sur un ordinateur Commodore Amiga. Un jeu de pendu (The Gallows) lui a rapidement fait suite. Aprs ces premiers programmes crits en Basic compil, Joe sest tourn vers le langage C, des ns de vitesse et de puissance. Il a alors dvelopp des applications pour les magazines JumpDisk (priodique avec CD consacr aux ordinateurs Amiga) et Amiga World. Comme il dveloppait dans une petite ville et sur une plate-forme isole, Joe a appris toutes les "mauvaises" faons dcrire du code. Cest en tentant de faire voluer ses applications quil a pris conscience de limportance de la maintenabilit du code. Deux ans plus tard, Joe a intgr la socit Policy Management Systems en tant que programmeur pour dvelopper une application client/serveur dans le domaine de lassurance pour OS/2 et Presentation Manager. Danne en anne, il a ajout le C++, Unix, Java, ASP, ASP.NET, C#, HTML, DHTML et XML sa palette de langages alors quil travaillait pour SCT, DocuCorp, IBM et le comit dAtlanta pour les jeux Olympiques, CheckFree, NCR, EDS, Delta Technology, Radiant Systems et la socit Genuine Parts. Joe apprcie particulirement le dveloppement dinterfaces utilisateurs et de programmes excuts ct serveur. Sa phase favorite de dveloppement est le dbogage. Joe travaille actuellement pour la socit Genuine Parts Company (maison mre de NAPA), dans le dpartement Automotive Parts Group Information System, o il dveloppe le site web Storefront. Ce site gre les stocks de NAPA et fournit un accs leurs comptes et donnes travers un rseau dordinateurs AS/400. Vous pouvez le contacter sur le site www.linqdev.com.

Traducteur et relecteurs techniques


propos du traducteur
Michel Martin est un passionn des technologies Microsoft. Nomm MVP par Microsoft depuis 2003, il anime des ateliers de formation, ralise des CD-ROM dautoformation vido et a crit plus de 250 ouvrages techniques, parmi lesquels Dveloppez des gadgets pour Windows Vista et Windows Live (Pearson, 2007) et le Programmeur Visual Basic 2008 (Pearson, 2008). Il a rcemment cr le rseau social eFriends Network, accessible ladresse http://www.efriendsnetwork.com.

propos des relecteurs techniques


Mitsuru Furuta est responsable technique en charge des relations dveloppeurs chez Microsoft France. Il blogue sur http://blogs.msdn.com/mitsufu. Pierrick Gourlain est architecte logiciel. Nomm MVP par Microsoft depuis 2007, il est passionn de nouvelles technologies, plus particulirement de LINQ, WPF, WCF, WF et des langages dynamiques. Il collabore plusieurs projets open-source hbergs sur codeplex (http://www.codeplex.com). Matthieu Mezil est consultant formateur, nomm MVP C# par Microsoft depuis avril 2008. Passionn par .NET, il sest spcialis sur lEntity Framework. Il blogue sur http://blogs.codes-sources.com/matthieu (fr) et http://msmvps.com/blogs/matthieu (en).

I
LINQ et C# 2008

1
Hello LINQ
Listing 1.1 : Hello Linq.
using System; using System.Linq; string[] greetings = {"hello world", "hello LINQ", "hello Pearson"}; var items = from s in greetings where s.EndsWith("LINQ") select s; foreach (var item in items) Console.WriteLine(item);

INFO Le code du Listing 1.1 a t insr dans un projet bas sur le modle "Application Console", de Visual Studio 2008. Si cette directive nest pas dj prsente dans le squelette de lapplication, ajoutez une instruction using System.Linq pour rfrencer cet espace de noms.

Lexcution de ce code avec le raccourci clavier Ctrl+F5 afche le message suivant dans la console :
Hello LINQ

Un changement de paradigme
Avez-vous remarqu un changement par rapport votre style de programmation ? En tant que dveloppeur .NET, vous ntes certainement pas pass ct. travers cet exemple trivial, une requte SQL (Structured Query Language) a t excute sur un

LINQ et C# 2008

Partie I

tableau de Strings1. Intressez-vous la clause where. Vous ne rvez pas, jai bien utilis la mthode EndsWidth sur un objet String. Vous vous demandez certainement quel est le type de cette variable. C# fait-il toujours des vrications statiques des types ? Oui, la compilation ! Cette prouesse est rendue possible par LINQ ( Language INtegrated Query). Interrogation XML Aprs avoir examin le code du Listing 1.1, ce deuxime exemple va commencer vous faire entrevoir le potentiel mis entre les mains du dveloppeur .NET par LINQ. En utilisant lAPI LINQ to XML, le Listing 1.2 montre avec quelle facilit il est possible dinteragir et dinterroger des donnes XML (eXtensible Markup Language). Remarquez en particulier comment les donnes XML sont manipules travers lobjet books.
Listing 1.2 : Requte XML base sur LINQ to XML.
using System; using System.Linq; using System.Xml.Linq; XElement books = XElement.Parse( @"<books> <book> <title>Pro LINQ: Language Integrated Query en C# 2008</title> <author>Joe Rattz</author> </book> <book> <title>Pro WF: Windows Workflow en .NET 3.0</title> <author>Bruce Bukovics</author> </book> <book> <title>Pro C# 2005 et la plateforme.NET 2.0, Troisime dition</title> <author>Andrew Troelsen</author> </book> </books>"); var titles = from book in books.Elements("book") where (string) book.Element("author") == "Joe Rattz" select book.Element("title"); foreach(var title in titles) Console.WriteLine(title.Value);

INFO Si lassembly System.Xml.Linq.dll napparat pas dans les rfrences du projet, ajoutezla. Remarquez galement la rfrence lespace de noms System.Xml.Linq.

1. Lordre dinterrogation est invers par rapport une requte SQL traditionnelle. Par ailleurs, une instruction "s in" a t ajoute pour fournir une rfrence lensemble des lments source. Ici, le tableau de chanes "hello world", "hello LINQ" et "hello Pearson".

Chapitre 1

Hello LINQ

Appuyez sur Ctrl+F5 pour excuter ce code. Voici le rsultat afch dans la console.
Pro LINQ: Language Integrated Query en C# 2008

Avez-vous remarqu comment les donnes XML ont t dcoupes dans un objet de type XElement sans quil ait t ncessaire de dnir un objet XmlDocument ? Les extensions de lAPI XML sont un des avantages de LINQ to XML. Au lieu dtre centr sur les objets XmlDocument, comme le prconise le W3C Document Object Model (DOM), LINQ to XML permet au dveloppeur dinteragir tous les niveaux du document en utilisant la classe XElement.
INFO Outre ses possibilits dinterrogation, LINQ to XML fournit galement une interface de travail XML plus puissante et plus facile utiliser.

Notez galement que la mme syntaxe SQL est utilise pour interroger les donnes XML, comme sil sagissait dune base de donnes. Interrogation dune base de donnes SQL Server Ce nouvel exemple montre comment utiliser LINQ to SQL pour interroger des tables dans des bases de donnes. Le Listing 1.3 interroge la base de donnes exemple Microsoft Northwind.
Listing 1.3 : Une simple interrogation de base de donnes base sur une requte LINQ to SQL.
using System; using System.Linq; using System.Data.Linq; using nwind; Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var custs = from c in db.Customers where c.City == "Rio de Janeiro" select c; foreach (var cust in custs) Console.WriteLine("{0}", cust.CompanyName);

INFO Ce code fait rfrence lassembly System.Data.Linq.dll. Si cette assembly nest pas spcie dans les premires lignes du listing, ajoutez-la. Notez quil est galement fait rfrence lespace de noms System.Data.Linq.

LINQ et C# 2008

Partie I

Pour que cet exemple fonctionne, il est ncessaire de faire appel lutilitaire en ligne de commande SQLMetal ou au concepteur dobjets relationnels, an de gnrer des classes dentits qui pointent vers la base de donnes Northwind. Reportez-vous au Chapitre 12 pour en savoir plus sur lutilisation de SQLMetal. Les classes dentits de cet exemple faisant partie de lespace de noms nwind, la clause using nwind; a t utilise en dbut de listing pour y faire rfrence.
INFO Il se peut que vous deviez changer la chane de connexion passe au constructeur Northwind dans ce listing. Reportez-vous aux sections relatives DataContext() et [Your]DataContext() du Chapitre 16 pour prendre connaissance des diffrents modes de connexion possibles.

Appuyez sur Ctrl+F5 pour excuter ce code. Le rsultat ci-aprs devrait safcher dans la console :
Hanari Carnes Que Delcia Ricardo Adocicados

Cet exemple utilise la table Customers de la base de donnes Northwind. Il se contente de slectionner les clients qui rsident Rio de Janeiro. premire vue, il ny a rien de nouveau ou de diffrent dans ce code. Vous remarquerez pourtant que la requte est intgre dans le code. Les fonctionnalits de lditeur sont donc galement accessibles au niveau de la requte ; en particulier la vrication de la syntaxe et lIntellisense. Lcriture " laveuglette" des requtes et la dtection des erreurs lexcution font donc bel et bien partie du pass ! Vous voulez baser une clause where sur un champ de la table Customers, mais vous narrivez pas vous rappeler le nom des champs ? Intellisense afchera les noms des champs et vous naurez plus qu choisir dans la liste. Dans lexemple prcdent, il suft de taper c. pour quIntellisense liste tous les champs de la table Customers. Vous verrez au Chapitre 2 que les requtes LINQ peuvent utiliser deux syntaxes : la syntaxe " point" object.method(), traditionnelle dans le langage C#, et une nouvelle syntaxe propre LINQ. Les requtes prsentes jusquici utilisent cette nouvelle syntaxe mais, bien entendu, vous pouvez continuer utiliser la syntaxe traditionnelle.

Introduction
La plate-forme .NET et les langages qui laccompagnent (C# et VB) sont aujourdhui prouvs. Cependant, il reste un point douloureux pour les dveloppeurs : laccs aux sources de donnes. La manipulation de bases de donnes et de code XML se rvle gnralement lourde et parfois problmatique.

Chapitre 1

Hello LINQ

Les problmes rencontrs dans la manipulation des bases de donnes sont multiples. Pour commencer, le langage nest pas en mesure dinteragir avec les donnes au niveau natif. Cela signie que, frquemment, les erreurs de syntaxe ne sont pas dtectes jusqu lexcution. De mme, les champs incorrectement rfrencs ne sont pas dtects. De telles erreurs peuvent tre dsastreuses, en particulier si elles se produisent pendant lexcution dune routine de gestion derreurs. Rien nest plus frustrant quun mcanisme de gestion derreurs mis en chec cause dune erreur syntaxique qui na jamais t dtecte ! Un autre problme peut provenir dune diffrence entre les types des donnes stocks dans une base de donnes ou dans des lments XML, par exemple, et les types grs par le langage de programmation. Les donnes date et heure sont en particulier concernes. Lextraction, litration et la manipulation de donnes XML risquent galement dtre trs fastidieuses. Souvent, alors quun simple fragment XML doit tre manipul, il est ncessaire de crer un XmlDocument pour se conformer lAPI W3C DOM XML. Au lieu dajouter de nouvelles classes et mthodes pour pallier ces dciences, les ingnieurs de Microsoft ont dcid daller plus loin en modiant la syntaxe des requtes dinterrogation. Cest ainsi que LINQ a vu le jour. Cette technologie, directement accessible dans les langages de programmation, permet dinterroger tous types de donnes, des tableaux mmoire aux collections en passant par les bases de donnes, les documents XML et bien dautres ensembles de donnes. LINQ et linterrogation des donnes LINQ est essentiellement un langage dinterrogation. Il peut retourner un ensemble dobjets, un objet unique ou un sous-ensemble de champs appartenant un objet ou un ensemble dobjets. Cet ensemble dobjets est appel une "squence". La plupart des squences LINQ sont de type IEnumerable<T>, o T est le type des objets stocks dans la squence. Par exemple, une squence dentiers est stocke dans une variable de type IEnumerable<int>. Comme vous le verrez dans la suite du livre, la plupart des mthodes LINQ retournent un IEnumerable<T>. Dans les exemples tudis jusquici, toutes les requtes ont retourn un IEnumerable<T> ou un type hrit. Le mot-cl "var" a parfois t utilis par souci de simplication. Vous verrez au Chapitre 2 quil sagit dun raccourci dcriture. Composants La puissance et luniversalit de LINQ devraient le faire adopter dans de nombreux domaines. En fait, tous les types de donnes stocks sont de bons candidats aux requtes LINQ. Ceci concerne les bases de donnes, Active Directory, le Registre de Windows, le systme de chiers, les feuilles de calcul Excel, etc.

LINQ et C# 2008

Partie I

Microsoft a dni plusieurs domaines de prdilection pour LINQ. Il ne fait aucun doute que cette liste sera complte par la suite.
LINQ to Objects LINQ to Objects est le nom donn lAPI IEnumerable<T> pour les oprateurs de requte standard. Vous lutiliserez par exemple pour requter des tableaux et des collections de donnes en mmoire. Les oprateurs de requte standard LINQ to Objects sont les mthodes statiques de la classe System.Linq.Enumerable. LINQ to XML LINQ to XML est le nom de lAPI ddie au travail sur les donnes XML (cette interface tait prcdemment appele XLINQ). LINQ to XML ne se contente pas de dnir des librairies XML an dassurer la compatibilit avec LINQ. Il apporte galement une solution plusieurs dciences du standard XML DOM et facilite le travail avec les donnes XML. titre dexemple, il nest dsormais plus ncessaire de crer un XmlDocument pour traiter une portion rduite de XML. Qui sen plaindra ? Pour pouvoir travailler avec LINQ to XML, vous devez faire rfrence lassembly System.Xml.Linq.dll dans votre projet :
using System.Xml.Linq;

LINQ to DataSet LINQ to DataSet est le nom de lAPI permettant de travailler avec des DataSets. De nombreux dveloppeurs utilisent ces types dobjets. Sans quaucune rcriture de code ne soit ncessaire, ils pourront dsormais tirer avantage de la puissance de LINQ pour interroger leurs DataSets. LINQ to SQL LINQ to SQL est le nom de lAPI IQueryable<T>, qui permet dappliquer des requtes LINQ aux bases de donnes Microsoft SQL Server (cette interface tait prcdemment connue sous le nom DLinq). Pour pouvoir utiliser LINQ to SQL, vous devez faire rfrence lassembly System.Data.Linq.dll :
using System.Data.Linq;

LINQ to Entities LINQ to Entities est une API alternative utilise pour interfacer des bases de donnes. Elle dcouple le modle objet entity de la base de donnes elle-mme en ajoutant un mappage logique entre les deux. Ce dcouplage procure une puissance et une exibilit accrues. tant donn que LINQ to Entities ne fait pas partie du framework LINQ, nous ne nous y intresserons pas dans cet ouvrage. Cependant, si LINQ to SQL ne vous

Chapitre 1

Hello LINQ

semble pas assez exible, vous devriez vous intresser LINQ to Entities ; en particulier si vous avez besoin dune plus grande souplesse entre les entits et la base de donnes, si vous manipulez des donnes provenant de plusieurs tables ou si vous voulez personnaliser la modlisation des entits. Comment travailler avec LINQ Il nexiste aucun produit LINQ acheter ou installer : cest juste le nom qui a t donn loutil dinterrogation de C# 3.0 et au Framework .NET 3.5, apparu dans Visual Studio 2008. Pour obtenir des informations jour sur LINQ et Visual Studio 2008, connectezvous sur les pages www.linqdev.com et http://apress.com/book/bookDisplay .html?bID=10241.

LINQ ne se limite pas aux requtes


LINQ tant labrviation de Language INtegrated Query (langage dinterrogation intgr), vous pourriez penser quil se limite linterrogation de donnes. Comme vous le verrez dans la suite du livre, son domaine daction va beaucoup plus loin... Vous est-il dj arriv de devoir remanier les donnes renvoyes par une mthode avant de pouvoir les passer en argument une autre mthode ? Supposons par exemple que vous appeliez la mthode A. Cette mthode retourne un tableau de string contenant des valeurs numriques stockes en tant que chanes de caractres. Vous devez alors appeler une mthode B qui demande un tableau dentiers en entre. Puis mettre en place une boucle pour convertir un un les lments du tableau. Quelle plaie ! LINQ apporte une rponse lgante ce problme. Supposons que nous ayons un tableau de string reu dune mthode A, comme indiqu dans le Listing 1.4.
Listing 1.4 : Une requte XML base sur LINQ to XML.
string[] numbers = { "0042", "010", "9", "27" };

Dans cet exemple, le tableau de string a t dclar de faon statique. Avant dappeler la mthode B, il est ncessaire de convertir ce tableau de chanes en un tableau dentiers :
int[] nums = numbers.Select(s => Int32.Parse(s)).ToArray();

Cette conversion pourrait-elle tre plus simple ? Voici le code utiliser pour afcher le tableau dentiers nums :
foreach(int num in nums) Console.WriteLine(num);

10

LINQ et C# 2008

Partie I

Et voici lafchage rsultant dans la console :


42 10 9 27

Peut-tre pensez-vous que cette conversion sest contente de supprimer les zros devant les nombres. Pour nous en assurer, nous allons trier les donnes numriques. Si tel est le cas, 9 sera afch en dernier et 10, en premier. Le Listing 1.5 effectue la conversion et le tri des donnes.
Listing 1.5 : Conversion dun tableau de chanes en entiers et tri croissant.
string[] numbers = { "0042", "010", "9", "27" }; int[] nums = numbers.Select(s => Int32.Parse(s)).OrderBy(s => s).ToArray(); foreach(int num in nums) Console.WriteLine(num);

Voici le rsultat :
9 10 27 42

Cela fonctionne, mais il faut bien avouer que cet exemple est simpliste. Nous allons maintenant nous intresser des donnes plus complexes. Supposons que nous disposions de la classe Employee et quune de ses mthodes retourne le nom des employs. Supposons galement que nous disposions dune classe Contact et quune de ses mthodes liste les contacts dun des employs. Supposons enn que vous souhaitiez obtenir la liste des contacts de chacun des employs. La tche semble assez simple. Cependant, la mthode qui retourne le nom des employs fournit un ArrayList dobjets Employee, et la mthode qui liste les contacts ncessite un tableau de type Contact. Voici le code des classes Employee et Contact :
namespace LINQDev.HR { public class Employee { public int id; public string firstName; public string lastName; public static ArrayList GetEmployees() { // Le "vrai" code ferait certainement une requte // sur une base de donnes ce point prcis ArrayList al = new ArrayList();

Chapitre 1

Hello LINQ

11

// Ajout des donnes dans le tableau ArrayList al al.Add(new Employee { id = 1, firstName = "Joe", lastName = "Rattz"} ); al.Add(new Employee { id = 2, firstName = "William", lastName = "Gates"} ); al.Add(new Employee { id = 3, firstName = "Anders", lastName = "Hejlsberg"} ); return(al); } } } namespace LINQDev.Common { public class Contact { public int Id; public string Name; public static void PublishContacts(Contact[] contacts) { // Cette mthode se contente dafficher les contacts dans la console foreach(Contact c in contacts) Console.WriteLine("Contact Id: {0} Contact: {1}", c.Id, c.Name); } } }

Comme vous pouvez le voir, la classe Employee et la mthode GetEmployee sont dans lespace de noms LINQDev.HR, et la mthode GetEmployees retourne un ArrayList. Quant la mthode PublishContacts, elle se trouve dans lespace de noms LINQDev.Common et demande un tableau dobjets Contact en entre. Avant larrive de LINQ, vous auriez d passer en revue les ArrayList retourns par la mthode GetEmployees et crer un nouveau tableau de type Contact an dassurer la compatibilit avec la mthode PublishContacts. Comme le montre le Listing 1.6, LINQ facilite grandement les choses.
Listing 1.6 : Appel des mthodes GetEmployees et PublishContacts.
ArrayList alEmployees = LINQDev.HR.Employee.GetEmployees(); LINQDev.Common.Contact[] contacts = alEmployees .Cast<LINQDev.HR.Employee>() .Select(e => new LINQDev.Common.Contact { Id = e.id, Name = string.Format("{0} {1}", e.firstName, e.lastName) }) .ToArray<LINQDev.Common.Contact>(); LINQDev.Common.Contact.PublishContacts(contacts);

Pour convertir le tableau ArrayList dobjets Employee en un tableau dobjets Contact, nous lavons transform en une squence IEnumerable<Employee> en utilisant loprateur de requte standard Cast. Cette transformation est ncessaire car une collection hrite ArrayList est renvoye par GetEmployees. Syntaxiquement parlant, ce sont les objets de la classe System.Object et non ceux de la classe Employee qui sont stocks dans lArrayList. Le casting vers des objets Employee est donc ncessaire. Si la mthode GetEmployees avait renvoy une collection gnrique List, cette tape naurait pas t ncessaire. Malheureusement, ce type de collection ntait pas disponible lors de lcriture de ce code hrit.

12

LINQ et C# 2008

Partie I

Le casting termin, loprateur Select est appliqu sur la squence dobjets Employee. Dans lexpression lambda (le code pass comme argument de la mthode Select), un objet Contact est instanci et initialis en utilisant les valeurs retournes par les objets Employee (vous en saurez plus en consultant la section rserves aux mthodes anonymes au Chapitre 2). Pour terminer, la squence dobjets Contact est convertie en un tableau dobjets Contact en utilisant loprateur ToArray. Ceci an dassurer la compatibilit avec la mthode PublishContacts. Voici le rsultat afch dans la console :
Contact Id: 1 Contact: Joe Rattz Contact Id: 2 Contact: William Gates Contact Id: 3 Contact: Anders Hejlsberg

Jespre que vous tes maintenant convaincu que LINQ ne se limite pas linterrogation de donnes. En parcourant les autres chapitres de ce livre, essayez de trouver de nouveaux champs dapplication de LINQ.

Quelques conseils avant de commencer


Pendant lcriture de cet ouvrage, jai parfois t troubl, embrouill, voire bloqu alors que jexprimentais LINQ. Pour vous viter de tomber dans les mmes piges, je vais vous donner quelques conseils. Tous les concepts propres LINQ nayant pas encore t introduits, il serait logique que ces conseils gurent la n de louvrage. Rassurez-vous : je ne vais pas vous imposer la lecture complte de louvrage ! Mais ne vous formalisez pas si vous ne comprenez pas entirement ce qui va tre dit dans les pages suivantes Utilisez le mot-cl var si vous ntes pas laise Il nest pas ncessaire dutiliser le mot-cl var lorsque vous affectez une squence de classes anonymes une variable, mais cela peut vous aider passer ltape de la compilation, en particulier si vous ne savez pas exactement quel type de donnes vous tes en train de manipuler. Bien entendu, il est prfrable de connatre le type des donnes T des IEnumerable<T> mais, parfois, en particulier lorsque vous commencez en programmation LINQ, cela peut se rvler difcile. Si le code ne veut pas se compiler cause dune incompatibilit dans un type de donnes, pensez transformer ce type en utilisant le mot-cl var. Supposons que vous ayez le code suivant :
// Ce code produit une erreur la compilation Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); IEnumerable<?> orders = db.Customers .Where(c => c.Country == "USA" && c.Region == "WA") .SelectMany(c => c.Orders);

Chapitre 1

Hello LINQ

13

Il se peut que vous ne sachiez pas exactement quel est le type des donnes de la squence dIEnumerable. Une astuce bien pratique consiste affecter le rsultat de la requte une variable dont le type est spci automatiquement grce au mot-cl var, puis obtenir son type grce la mthode GetType (voir Listing 1.7).
Listing 1.7 : Un exemple de code qui utilise le mot-cl var.
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var orders = db.Customers .Where(c => c.Country == "USA" && c.Region == "WA") .SelectMany(c => c.Orders); Console.WriteLine(orders.GetType());

Dans cet exemple, le type de la variable orders est spci par lintermdiaire du motcl var. Voici le type afch dans la console :
System.Data.Linq.DataQuery`1[nwind.Order]

Dans tout le charabia retourn par le compilateur, nwind.Order est certainement la partie la plus importante, puisquelle indique le type de la squence. Si lexpression afche dans la console vous intrigue, excutez lexemple dans le dbogueur et examinez la variable orders dans la fentre Espion Express. Son type est le suivant :
System.Linq.IQueryable<nwind.Order> {System.Data.Linq.DataQuery<nwind.Order>}

La squence est donc de type nwind.Order. Il sagit en fait dun IQueryable<nwind.Order>, mais vous pouvez laffecter un IEnumerable<nwind.Order>, puisque IQueryable<T> hrite de IEnumerable<T>. Vous pouvez donc rcrire le code prcdent et passer en revue les rsultats en utilisant les instructions du Listing 1.8.
Listing 1.8 : Le mme code que dans le Listing 1.7, sauf au niveau des codes explicites.
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); IEnumerable<Order> orders = db.Customers .Where(c => c.Country == "USA" && c.Region == "WA") .SelectMany(c => c.Orders); foreach(Order item in orders) Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderID, item.ShipName);

INFO Pour que ce code fonctionne, vous devez spcier une directive using pour les espaces de noms System.Collections.Generic et System.Linq (ce deuxime espace de noms est obligatoire ds que vous utilisez des instructions en rapport avec LINQ).

14

LINQ et C# 2008

Partie I

Ce code produit le rsultat suivant :


3/21/1997 12:00:00 AM - 10482 - Lazy K Kountry Store 5/22/1997 12:00:00 AM - 10545 - Lazy K Kountry Store 4/17/1998 12:00:00 AM - 11032 - White Clover Markets 5/1/1998 12:00:00 AM - 11066 - White Clover Markets

Utilisez les oprateurs Cast ou OfType pour les collections hrites La grande majorit des oprateurs de requte LINQ ne peut tre utilise que sur des collections qui implmentent linterface IEnumerable<T>. Aucune des collections hrites de C# (celles prsentes dans lespace de noms System.Collection) nimplmente cette interface. Mais, alors, comment utiliser LINQ avec des collections hrites ? Deux oprateurs de requte standard sont l pour convertir des collections hrites en squences IEnumerable<T> : Cast et OfType (voir Listing 1.9).
Listing 1.9 : Conversion dune collection hrite en un IEnumerable<T> avec loprateur Cast.
// Cration dune collection hrite ArrayList arrayList = new ArrayList(); // Linitialisation de collections ne fonctionne pas // avec les collections hrites arrayList.Add("Adams"); arrayList.Add("Arthur"); arrayList.Add("Buchanan"); IEnumerable<string> names = arrayList.Cast<string>().Where(n => n.Length < 7); foreach(string name in names) Console.WriteLine(name);

Le Listing 1.10 reprsente le mme exemple, en utilisant cette fois-ci loprateur OfType.
Listing 1.10 : Utilisation de loprateur OfType.
// Cration dune collection hrite ArrayList arrayList = new ArrayList(); // Linitialisation de collections ne fonctionne pas // avec les collections hrites arrayList.Add("Adams"); arrayList.Add("Arthur"); arrayList.Add("Buchanan"); IEnumerable<string> names = arrayList.OfType<string>().Where(n => n.Length < 7); foreach(string name in names) Console.WriteLine(name);

Ces deux exemples produisent le mme rsultat :


Adams Arthur

Ces deux oprateurs sont quelque peu diffrents : Cast essaye de convertir tous les lments de la collection dans le type spci. Une exception est gnre si un des

Chapitre 1

Hello LINQ

15

lments ne peut pas tre converti. Au contraire, OfType ne convertit que les lments qui peuvent ltre. Prfrez loprateur OfType loprateur Cast Les gnriques ont t implments dans C# pour permettre une vrication de type statique (cest--dire pendant la compilation) sur les collections. Avant lapparition des gnriques, il ny avait aucun moyen de sassurer que les lments dune collection hrite (un ArrayList ou un Hashtable, par exemple) taient tous de mme type et avaient le type requis. Rien par exemple nempchait linsertion dun objet Textbox dans un ArrayList suppos ne contenir que des objets Label. Avec lapparition des gnriques dans C# 2.0, les dveloppeurs peuvent dsormais sassurer quune collection ne contient que des lments dont le type est spci. Bien que les oprateurs OfType et Cast soient utilisables sur une collection hrite, Cast ncessite que tous les objets de la collection aient le type attendu. Pour viter de gnrer des exceptions en cas dincompatibilit de type, prfrez-lui loprateur OfType. Par son intermdiaire, seuls les objets du type spci seront stocks dans la squence IEnumerable<T>, et aucune exception ne sera gnre. Le cas chant, les objets dont le type nest pas celui attendu ne seront pas convertis. Les requtes aussi peuvent tre bogues Au Chapitre 3, vous verrez que les requtes LINQ sont souvent diffres. Elles ne sont donc pas excutes ds leur invocation. Considrez par exemple le code suivant, extrait du Listing 1.1 :
var items = from s in greetings where s.EndsWith("LINQ") select s; foreach (var item in items) Console.WriteLine(item);

Contrairement ce que vous pourriez penser, la requte nest pas excute linitialisation de la variable items. Elle ne sera excute que lorsquune ligne de code aura besoin de son rsultat ; typiquement lors de lnumration du rsultat de la requte. Ici, le rsultat de la requte nest pas calcul jusqu ce que linstruction foreach soit excute. On oublie souvent que lexcution dune requte est diffre jusqu lnumration de sa squence. Une requte mal formule pourrait ainsi produire une erreur bien des lignes plus loin, lorsque sa squence est numre, et le programmeur pourrait avoir du mal penser que la requte en est lorigine. Examinons le code du Listing 1.11.

16

LINQ et C# 2008

Partie I

Listing 1.11 : Cette requte contient une erreur intentionnelle qui nest leve qu lnumration.
string[] strings = { "un", "deux", null, "trois" }; Console.WriteLine("Avant lappel Where()"); IEnumerable<string> ieStrings = strings.Where(s => s.Length == 3); Console.WriteLine("Aprs lappel Where()"); foreach(string s in ieStrings) { Console.WriteLine("Traitement " + s); }

Le troisime lment du tableau a pour valeur null. Lexpression null.Length va produire une exception lors de lnumration de la squence ieStrings, et en particulier de son troisime lment. Pourtant, la ligne lorigine de lerreur est allgrement passe Voici le rsultat obtenu lexcution de ce code :
Avant lappel Where() Aprs lappel Where() Traitement un Traitement deux Unhandled Exception: System.NullReferenceException: Object reference not set to an instance of an object.

Loprateur Where na pas produit dexception. Lexception a seulement t leve lorsque lon a essay de lire le troisime lment de la squence. Imaginez que la squence ieStrings soit passe une fonction qui numre la squence dans une liste droulante ou un contrle quivalent. Penseriez-vous que lexception provient de la requte LINQ ? Il y a de grandes chances pour que vous cherchiez lerreur dans le code de la fonction Sachez tirer parti des requtes diffres Au Chapitre 3, vous en apprendrez bien plus sur les requtes diffres. Cependant, je voudrais ds prsent insister sur le fait que, si une requte diffre retourne un IEnumerable<T>, cet objet peut tre numr autant de fois que ncessaire sans pour autant devoir rappeler la requte. La plupart des codes de cet ouvrage appellent une requte et stockent l IEnumerable<T> retourn dans une variable. Une instruction foreach est alors applique sur la squence IEnumerable<T> des ns dmonstratives. Si ce code est excut plusieurs reprises, il nest pas ncessaire de rappeler la requte chaque excution. Il serait plus judicieux dcrire une mthode dinitialisation et dy placer toutes les requtes ncessaires. Cette mthode serait appele une fois. Vous pourriez alors numrer la squence de votre choix pour obtenir la dernire version des rsultats.

Chapitre 1

Hello LINQ

17

Utiliser le log du DataContext Lorsque vous travaillerez avec LINQ to SQL, vous devrez garder lesprit que la classe relative la base de donnes, gnre par SQLMetal, hrite de System.Data.Linq.DataContext. Cette classe dispose donc de quelques fonctionnalits prinstalles. Entre autres de lobjet TextWriter Log. Si vous avez dj expriment une rupture de code lie aux donnes, vous serez ravi dapprendre quil est possible dutiliser lobjet Log du DataContext pour observer les donnes rsultant de la requte, tout comme vous le feriez dans SQL Server Enterprise Manager ou Query Analyzer (voir lexemple du Listing 1.12).
Listing 1.12 : Un exemple dutilisation du log du DataContext.
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); db.Log = Console.Out; IQueryable<Order> orders = from c in db.Customers from o in c.Orders where c.Country == "USA" && c.Region == "WA" select o; foreach(Order item in orders) Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderID, item.ShipName);

Ce code produit la sortie suivante dans la console :


SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate], [t1].[RequiredDate], [t1].[ShippedDate], [t1].[ShipVia], [t1].[Freight], [t1].[ShipName], [t1].[ShipAddress], [t1].[ShipCity], [t1].[ShipRegion], [t1].[ShipPostalCode], [t1].[ShipCountry] FROM [dbo].[Customers] AS [t0], [dbo].[Orders] AS [t1] WHERE ([t0].[Country] = @p0) AND ([t0].[Region] = @p1) AND ([t1].[CustomerID] = [t0].[CustomerID]) -- @p0: Input String (Size = 3; Prec = 0; Scale = 0) [USA] -- @p1: Input String (Size = 2; Prec = 0; Scale = 0) [WA] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1 3/21/1997 12:00:00 AM - 10482 - Lazy K Kountry Store 5/22/1997 12:00:00 AM - 10545 - Lazy K Kountry Store 6/19/1997 12:00:00 AM - 10574 - Trails Head Gourmet Provisioners 6/23/1997 12:00:00 AM - 10577 - Trails Head Gourmet Provisioners 1/8/1998 12:00:00 AM - 10822 - Trails Head Gourmet Provisioners 7/31/1996 12:00:00 AM - 10269 - White Clover Markets 11/1/1996 12:00:00 AM - 10344 - White Clover Markets 3/10/1997 12:00:00 AM - 10469 - White Clover Markets 3/24/1997 12:00:00 AM - 10483 - White Clover Markets 4/11/1997 12:00:00 AM - 10504 - White Clover Markets 7/11/1997 12:00:00 AM - 10596 - White Clover Markets 10/6/1997 12:00:00 AM - 10693 - White Clover Markets 10/8/1997 12:00:00 AM - 10696 - White Clover Markets 10/30/1997 12:00:00 AM - 10723 - White Clover Markets 11/13/1997 12:00:00 AM - 10740 - White Clover Markets 1/30/1998 12:00:00 AM - 10861 - White Clover Markets 2/24/1998 12:00:00 AM - 10904 - White Clover Markets 4/17/1998 12:00:00 AM - 11032 - White Clover Markets 5/1/1998 12:00:00 AM - 11066 - White Clover Markets

18

LINQ et C# 2008

Partie I

Utilisez le forum LINQ Il y a fort parier que, tt ou tard, vous vous retrouverez dans une situation bloquante en exprimentant LINQ. Nhsitez pas faire appel au forum ddi LINQ sur MSDN.com, en vous connectant ladresse www.linqdev.com. Ce forum est suivi par les dveloppeurs Microsoft. Vous y trouverez de nombreuses ressources trs intressantes.

Rsum
Je sens que vous tes impatient de passer au chapitre suivant. Je voudrais cependant vous rappeler quelques petites choses avant que vous ne tourniez les pages. LINQ va changer la faon dont les dveloppeurs .NET interrogent leurs donnes. Les diteurs de logiciels vont certainement ajouter un sticker "Compatible LINQ" sur leurs produits, tout comme ils le font actuellement avec XML. Gardez bien en mmoire que LINQ nest pas juste une nouvelle librairie que vous ajoutez vos projets. Il sagit dune tout autre approche pour interroger vos donnes, consistant en plusieurs composants qui dpendent de la source de donnes interroger. Alors que nous crivons ces lignes, vous pouvez utiliser LINQ pour interroger des collections de donnes en mmoire avec LINQ to Objects, des chiers XML avec LINQ to SQL, des DataSets avec LINQ to DataSets et des bases de donnes SQL Server avec LINQ to SQL. Rappelez-vous galement que LINQ nest pas simplement un langage de requte. Dans un de mes projets, jai utilis LINQ avec succs non seulement pour interroger des sources de donnes, mais galement pour modier le format des donnes an de les prsenter dans une fentre WinForm. Enn, jespre que vous tiendrez compte des astuces que jai mentionnes la n de ce chapitre. Si vous ne comprenez pas entirement certaines dentre elles, ce nest pas un problme. Vous en saisirez toutes les subtilits au fur et mesure de votre progression dans le livre. Stockez-les dans un coin de votre tte : elles vous feront gagner du temps. Aprs vous tre intress aux exemples et conseils de ce chapitre, vous tes peut-tre perplexe devant la syntaxe de LINQ. Ne vous en faites pas, au prochain chapitre vous allez dcouvrir en dtail toutes les modications apportes au langage C# 3.0 par Microsoft et comprendrez plus facilement le code.

2
Amliorations de C# 3.0 pour LINQ
Le chapitre prcdent vous a initi au monde merveilleux de LINQ. Jy ai donn quelques exemples pour attiser votre apptit et des astuces qui pourront vous paratre quelque peu prmatures. Certaines syntaxes vous laissent peut-tre perplexe, car le code revt un aspect entirement nouveau. C# a en effet d tre remani pour supporter les fonctionnalits avances de LINQ. Dans ce chapitre, vous allez dcouvrir les facettes les plus innovantes de C# 3.0.

Les nouveauts du langage C# 3.0


Pour que LINQ sintgre parfaitement dans C#, des amliorations signicatives ont d tre apportes au langage. Toutes les amliorations dterminantes ont t dictes par le support de LINQ. Bien que chacune dentre elles soit intressante en tant que telle, cest lensemble qui fait de C# 3.0 un langage si puissant. Pour bien comprendre la syntaxe de LINQ, vous devez au pralable vous intresser certaines nouvelles fonctionnalits de C# 3.0. Ce chapitre va passer en revue les nouveauts suivantes :
m m m m m m

les expressions lambda ; les arbres dexpressions ; le mot-cl var, linitialisation des objets et des collections et les types anonymes ; les mthodes dextension ; les mthodes partielles ; les expressions de requte.

20

LINQ et C# 2008

Partie I

Les assemblies et espaces de noms ncessaires la bonne excution des exemples de ce chapitre ne seront pas mentionns sils ont dj t utiliss au Chapitre 1. En revanche, les nouveaux assemblies et espaces de noms seront signals lors de leur premire utilisation. Les expressions lambda Bien quinventes en 1936 par le mathmaticien amricain Alonzo Church et utilises dans des langages aussi anciens que LISP, les expressions lambda sont une nouveaut du langage C# 3.0. Leur but premier vise simplier la syntaxe des algorithmes. Avant de nous intresser aux expressions lambda, nous allons nous attarder quelques instants sur la possibilit de passer un algorithme dans un argument dune mthode.
Utilisation de mthodes nommes Avant la sortie de C# 2.0, lorsquune mthode/une variable avait besoin dun dlgu, le dveloppeur devait crer une mthode nomme et passer ce nom chaque utilisation du dlgu.

Supposons que deux dveloppeurs travaillent sur un mme projet. Le dveloppeur numro 1 cre un code rutilisable et le dveloppeur numro 2 utilise ce code pour crer une application. Supposons que le dveloppeur 1 dnisse une mthode gnrique permettant de ltrer des tableaux dentiers, en permettant de spcier lalgorithme de tri utiliser. Dans un premier temps, il cre un dlgu qui reoit un entier et retourne la valeur true si la valeur passe peut tre incluse dans le tableau. Ainsi, il cr une classe utilitaire et ajoute le dlgu et la mthode de ltre. Voici le code utilis :
public class Common { public delegate bool IntFilter(int i); public static int[] FilterArrayOfInts(int[] ints, IntFilter filter) { ArrayList aList = new ArrayList(); foreach (int i in ints) { if (filter(i)) { aList.Add(i); } } return ((int[])aList.ToArray(typeof(int))); } }

Le dveloppeur numro 1 a plac le dlgu et la mthode FilterArrayOfInt() dans une DLL (Dynamic Link Library) an de les rendre accessibles dans plusieurs applications.

Chapitre 2

Amliorations de C# 3.0 pour LINQ

21

La mthode FilterArrayOfInt() du listing prcdent admet deux paramtres en entre : le tableau trier et un dlgu qui fait rfrence la mthode de tri utiliser. Le tableau dentiers tri est renvoy par la mthode. Supposons maintenant que le dveloppeur numro 2 veuille limiter le tri aux entiers impairs. Voici la mthode de tri utilise :
public class Application { public static bool IsOdd(int i) { return ((i & 1) == 1); } }

En se basant sur le code de la mthode FilterArrayOfInts, la mthode IsOdd sera appele pour tous les entiers du tableau qui lui seront passs. Ce ltre ne retournera la valeur true que dans le cas o lentier pass est impair. Le Listing 2.1 donne un exemple dutilisation de la mthode FilterArrayOfInts.
Listing 2.1 : Appel de la mthode commune FilterArrayOfInts.
using System.Collections; int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int[] oddNums = Common.FilterArrayOfInts(nums, Application.IsOdd); foreach (int i in oddNums) Console.WriteLine(i);

Voici le rsultat :
1 3 5 7

Comme vous pouvez le remarquer, pour passer le dlgu dans le second paramtre de la mthode FilterArrayOfInts, il suft dindiquer son nom. En dnissant un autre ltre, le rsultat peut tre tout autre. Il est ainsi possible de dnir un ltre pour les nombres pairs, pour les nombres premiers ou pour un tout autre critre. Les dlgus sont intressants chaque fois que le code doit tre utilis plusieurs reprises.
Utiliser des mthodes anonymes Cet exemple fonctionne la perfection, mais la longue il peut tre fastidieux dcrire tous les ltres et autres dlgus dont vous avez besoin : la plupart de ces mthodes seront appeles une seule fois et il peut tre frustrant de crer autant de mthodes que de tris ncessaires. Depuis C# 2.0, les dveloppeurs peuvent faire appel aux mthodes anonymes, an de passer du code comme argument et ainsi dviter lutilisation de dlgus.

22

LINQ et C# 2008

Partie I

Dans cet exemple, plutt que crer la mthode IsOdd, le code de ltrage est pass dans largument (voir Listing 2.2).
Listing 2.2 : Appel du filtre par lintermdiaire dune mthode anonyme.
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int[] oddNums = Common.FilterArrayOfInts(nums, delegate(int i) { return ((i & 1) == 1); }); foreach (int i in oddNums) Console.WriteLine(i);

Comme vous le voyez, il nest plus ncessaire de dnir une mthode de ltrage. Cette technique est particulirement intressante si le code qui remplace le dlgu a peu de chances dtre utilis plusieurs reprises. Le rsultat est bien entendu identique celui de lexemple prcdent :
1 3 5 7

Les mthodes anonymes ont un inconvnient : elles sont verbeuses et difciles lire. Il serait vraiment agrable de pouvoir crire le code de la mthode dune manire plus concise !
Utiliser les expressions lambda En C#, les expressions lambda consistent en une liste de paramtres spars entre eux par des virgules1, suivis de loprateur lambda (=>) puis dune expression ou dune dclaration.
(param1, param2, paramN) => expr

Si lexpression/la dclaration est plus complexe, vous pouvez utiliser un bloc dlimit par les caractres { et } :
(param1, param2, paramN) => { statement1; statement2; ... statementN; return(lambda_expression_return_type); }

Dans cet exemple, le type de donnes renvoy par linstruction return doit correspondre au code de retour spci par le dlgu. Voici un exemple dexpression lambda :
x => x

1. Si les paramtres sont au nombre de deux (ou plus), ils doivent tre dlimits par des parenthses.

Chapitre 2

Amliorations de C# 3.0 pour LINQ

23

Cette expression lambda pourrait se lire "x conduit x" ou encore "entre x sortie x". Cela signie que la variable dentre x est galement renvoye par lexpression lambda. tant donn que la fonction ne compte quun seul paramtre en entre, il nest pas ncessaire de lentourer de parenthses. Il est important davoir lesprit que le dlgu dtermine le type de lentre x ainsi que le type qui doit tre retourn. Par exemple, si le dlgu dnit une chane en entre et retourne un boolen, lexpression x => x ne peut pas tre utilise. Dans ce cas, la partie droite de loprateur lambda doit retourner un boolen. Par exemple :
x => x.Length > 0

Cette expression lambda pourrait se lire "x conduit x.Length > 0" ou encore "entre x, sortie x.Length > 0". tant donn que la partie droite de loprateur lambda est quivalente un boolen, le dlgu doit indiquer que la mthode renvoie un boolen, sans quoi une erreur se produira la compilation. Lexpression lambda ci-aprs tente de retourner la longueur de largument fourni en entre. Le dlgu doit donc spcier que la valeur retourne est de type entier ( int).
s => s.Length

Si plusieurs paramtres sont passs en entre de lexpression lambda, sparez-les par des virgules et entourez-les par des parenthses, comme dans lexpression suivante :
(x, y) => x == y

Les expressions lambda complexes peuvent tre spcies lintrieur dun bloc, comme dans :
(x, y) => { if (x > y) return (x); else return (y); }

ATTENTION Gardez lesprit que le dlgu doit indiquer le type des paramtres en entre et de llment renvoy. Dans tous les cas, assurez-vous que ces lments sont en accord avec les types dnis dans le dlgu.

Pour vous rafrachir la mmoire, voici la dclaration delegate dnie par le programmeur numro 1 :
delegate bool IntFilter(int i);

Lapplication dveloppe par le programmeur numro 2 devra accepter un paramtre de type int et retourner une valeur de type bool. Cela peut se dduire de la mthode appele et du but du ltre, mais dans tous les cas rappelez-vous que cest le dlgu qui dicte les types en entre et en sortie.

24

LINQ et C# 2008

Partie I

En utilisant une expression lambda, lexemple prcdent se transforme en le Listing 2.3.


Listing 2.3 : Appel du filtre avec une expression lambda.
int[] nums = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; int[] oddNums = Common.FilterArrayOfInts(nums, i => ((i & 1) == 1)); foreach (int i in oddNums) Console.WriteLine(i);

Ce code est vraiment concis. Sil vous semble quelque peu droutant, une fois que vous y serez habitu vous verrez quel point il est rutilisable et facile maintenir. Bien entendu, les rsultats sont les mmes que dans les exemples prcdents :
1 3 5 7

Pour rcapituler, voici quelques instructions concernant les trois approches dont nous venons de parler :
int[] oddNums = // Approche mthode nomme Common.FilterArrayOfInts(nums, Application.IsOdd); int[] oddNums = // Approche mthode anonyme Common.FilterArrayOfInts(nums, delegate(int i){return((i & 1) == 1);}); int[] oddNums = // Approche expression lambda Common.FilterArrayOfInts(nums, i => ((i & 1) == 1));

La premire version semble plus courte que les autres, mais vous devez garder lesprit quelle est associe une mthode nomme dans laquelle est dni le traitement effectuer. Cette alternative sera certainement le meilleur choix si la mthode doit tre rutilise et/ou si lalgorithme mis en uvre est complexe et/ou doit tre con des spcialistes.
ASTUCE Les algorithmes complexes et/ou rutiliss sont mieux grs par des mthodes nommes. Ils sont alors accessibles tout dveloppeur, mme sil ne saisit pas toutes les nuances du code mis en uvre.

Cest au dveloppeur de choisir quelle mthode est la plus approprie dans son cas prcis : une mthode nomme, une mthode anonyme ou une expression lambda. Les expressions lambda peuvent tre passes comme argument des requtes LINQ. tant donn que ces requtes ont toutes les chances dutiliser des arguments usage unique ou en tout cas peu rutiliss, lalternative des oprateurs lambda offre une

Chapitre 2

Amliorations de C# 3.0 pour LINQ

25

grande exibilit et noblige pas le programmeur crire une mthode nomme pour chaque requte. Arbres dexpressions Les arbres dexpressions permettent de reprsenter sous la forme darbres les expressions lambda utilises dans des requtes. Ils autorisent lvaluation simultane de tous les oprateurs impliqus dans une requte. Ils semblent donc parfaitement adapts la manipulation de sources de donnes telles que celles embarques dans une base de donnes. Dans la plupart des exemples passs en revue jusquici, les oprateurs de requte ont t excuts de faon squentielle. Examinons le code ci-aprs :
int[] nums = new int[] { 6, 2, 7, 1, 9, 3 }; IEnumerable<int> numsLessThanFour = nums .Where(i => i < 4) .OrderBy(i => i);

Cette requte utilise les oprateurs Where et OrderBy, qui attendent des mthodes dlgues en argument. Lorsque ce code est compil, LIL (Intermediate Language) .NET fabriqu est identique celui que produirait une mthode anonyme pour chacun des oprateurs des expressions lambda. lexcution, les oprateurs Where puis OrderBy sont appels successivement. Cette excution squentielle des oprateurs semble convenir dans cet exemple, mais supposez que cette requte soit applique dans une source de donnes volumineuse (une base de donnes, par exemple). Cela aurait-il un sens de ltrer les donnes une premire fois avec loprateur Where, puis une seconde avec loprateur OrderBy. Cette technique nest videmment pas applicable aux requtes de bases de donnes ni potentiellement dautres types de requtes. Cest ici que les arbres dexpressions prennent toute leur importance. Ils autorisent en effet lvaluation et lexcution simultanes de tous les oprateurs dune requte. Le compilateur est donc maintenant en mesure de coder deux types de codes pour une expression lambda : du code IL ou un arbre dexpressions. Cest le prototype de loprateur qui dtermine quel type de code sera gnr. Si sa dclaration lautorise accepter une mthode dlgue, du code IL sera gnr. Si sa dclaration lautorise accepter une expression dune mthode dlgue, un arbre dexpressions sera gnr. titre dexemple, nous allons nous intresser deux implmentations diffrentes de loprateur Where. La premire est loprateur de requte standard Where de lAPI LINQ to Objects, dnie dans la classe System.Linq.Enumerable :
public static IEnumerable<T> Where<T>( this IEnumerable<T> source, Func<T, bool> predicate);

26

LINQ et C# 2008

Partie I

La seconde implmentation de loprateur Where provient de lAPI LINQ to SQL et de la classe System.Linq.Queryable :
public static IQueryable<T> Where<T>( this IQueryable<T> source, System.Linq.Expressions.Expression<Func<int, bool>> predicate);

Comme vous pouvez le voir, le premier oprateur Where accepte la mthode dlgue Func en argument. Du code IL sera donc gnr par le compilateur pour lexpression lambda de cet oprateur. Reportez-vous au Chapitre 3 pour avoir plus dinformations sur le dlgu Func. Pour linstant, il vous suft de comprendre que le dlgu Func dnit la signature de largument. Le deuxime oprateur Where accepte un arbre dexpressions (Expression) en argument. Le compilateur gnrera donc un arbre dexpressions pour reprsenter les donnes. Les oprateurs qui admettent une squence IEnumerable<T> comme premier argument utilisent des dlgus pour manipuler les expressions lambda. En revanche, les oprateurs qui admettent une squence IQueryable<T> comme premier argument utilisent des arbres dexpressions.
INFO Le compilateur produit du code IL pour les mthodes dextension des squences IEnumerable<T>, alors quil produit des arbres dexpressions pour les mthodes dextension des squences IQueryable<T>.

Le dveloppeur qui se contente dutiliser LINQ nest pas oblig de connatre les tenants et les aboutissants des arbres dexpressions. Cest la raison pour laquelle cet ouvrage nira pas plus loin dans les fonctionnalits avances des arbres dexpressions. Le mot-cl var, linitialisation dobjets et les types anonymes Il est quasiment impossible de sintresser au mot-cl var et linfrence de type sans aborder linitialisation des objets et les types anonymes. De mme, il est quasiment impossible de sintresser linitialisation dobjets et aux types anonymes en passant sous silence le mot-cl var. tant donn leurs fortes imbrications, plutt que dcrire sparment ces trois nouveauts du langage C#, je vais vous les prsenter simultanment. Examinez la dclaration ciaprs :
var1 mySpouse = new {2 FirstName = "Vickey"3, LastName = "Rattz" };

1. Le mot-cl var apparat clairement devant le nom de la variable. 2. Un type anonyme sera utilis, car loprateur new est utilis sans prciser une classe nomme. 3. Lobjet anonyme sera explicitement initialis en utilisant la nouvelle fonctionnalit dinitialisation dobjet.

Chapitre 2

Amliorations de C# 3.0 pour LINQ

27

Dans cet exemple, la variable mySpouse est dclare en utilisant le mot-cl var. Cette variable se voit assigner un type anonyme dont le type est connu grce aux nouveauts de C# en matire dinitialisation dobjets. Cette simple ligne de code tire parti du mot-cl var, des types anonymes et de linitialisation dobjets. Pour rsumer, le mot-cl var permet de dduire le type dun objet en tenant compte du type des donnes utilises pour linitialiser. Les types anonymes permettent donc de crer des types de classes la vole. Comme le laisse prvoir le mot "anonyme", ces nouveaux types de donnes nont pas de nom. Il nest pas simple de crer une donne anonyme sans connatre ses variables membres, et vous ne pouvez pas connatre ses variables membres sans connatre leurs types. Enn, vous ne pouvez pas connatre le type de ses membres jusqu ce quils soient initialiss. Mais, rassurez-vous, la fonctionnalit dinitialisation de C# 3.0 gre tout ce fatras pour vous ! Lorsque cette ligne de code passera entre les mains du compilateur, une nouvelle classe de type anonyme sera cre. Elle contiendra deux membres de type String : FirstName et LastName.
Le mot-cl var est implicitement typ pour les variables locales Lintroduction des types anonymes dans le langage C# a induit un problme sousjacent : si une variable dont le type nest pas dni est instancie avec un objet de type anonyme, quel sera le type de la variable ? Considrez le code ci-aprs :
// Ce code nest pas compilable! ??? unnamedTypeVar = new {firstArg = 1, secondArg = "Joe" };

Quel type dclareriez-vous pour la variable unnamedTypeVar ? Pour rsoudre ce problme, le mot-cl var a t dni par les ingnieurs en charge du dveloppement du langage C# chez Microsoft. Ce mot-cl informe le compilateur quil doit implicitement dnir le type de la variable en utilisant linitialiseur de la variable. Si vous ne dnissez pas un initialiseur, il en rsultera une erreur la compilation. Le Listing 2.4 reprsente un code qui dclare une variable avec le mot-cl var sans linitialiser.
Listing 2.4 : Une dclaration de variable invalide utilisant le mot-cl var.
var name;

Voici lerreur gnre par le compilateur.


Implicitly-typed local variables must be initialized

tant donn que le type des variables est vri de faon statique la compilation, il est ncessaire de dnir un initialiseur pour que le compilateur puisse faire son travail jusquau bout. Mais, attention, vous ne devrez pas affecter une valeur dun autre type

28

LINQ et C# 2008

Partie I

cette variable dans la suite du code, sans quoi une erreur se produira la compilation. Examinons le code du Listing 2.5.
Listing 2.5 : Une affectation incorrecte une variable dclare avec le mot-cl var.
var name = "Joe"; // Jusquici, tout va bien name = 1; // Ceci est incorrect! Console.WriteLine(name);

Ce code ne passera pas ltape de la compilation, car le type de la variable est implicitement dni String par sa premire affectation. Il est donc impossible de lui affecter une valeur entire par la suite. Voici lerreur gnre par le compilateur :
Cannot implicitly convert type int to string

Comme vous le voyez, le compilateur soccupe de la cohrence du type des donnes affectes la variable. Pour en revenir la dclaration du type anonyme unnamedTypeVar, la syntaxe utiliser est celle du Listing 2.6.
Listing 2.6 : Un type anonyme affect une variable dclare avec le mot-cl var.
var unnamedTypeVar = new {firstArg = 1, secondArg = "Joe" }; Console.WriteLine(unnamedTypeVar.firstArg + ". " + unnamedTypeVar.secondArg);

Voici le rsultat de ce code :


1.Joe

Lutilisation du mot-cl var apporte deux avantages : la vrication de type statique et la exibilit apporte par le support des types anonymes. Ce dernier point deviendra trs important lorsque nous nous intresserons aux oprateurs de projection dans la suite de louvrage. Dans les exemples passs en revue jusquici, le mot-cl var tait obligatoire. En effet, si vous affectez un objet rsultant dune classe anonyme une variable, cette dernire doit tre dclare avec le mot-cl var. Notez cependant que le mot-cl var peut tre utilis chaque dclaration de variable, condition que cette dernire soit correctement initialise. Pour des questions de maintenance du code, il nest cependant pas conseill dabuser de cette technique : les dveloppeurs devraient toujours connatre le type des donnes quils manipulent. Bien sr, vous connaissez le type de vos donnes aujourdhui, mais quen sera-t-il dans six mois ? Et si un autre programmeur prend la relve ?
ASTUCE An de faciliter la maintenance de votre code, nabusez pas du mot-cl var. Ne lutilisez que lorsque cela est ncessaire. Par exemple lorsque vous affectez un objet de type anonyme une variable.

Chapitre 2

Amliorations de C# 3.0 pour LINQ

29

Expressions dinitialisation dobjets et de collections Les types anonymes autorisant lutilisation de types de donnes dynamiques, le mode dinitialisation des objets et des collections a t simpli, essentiellement grce aux expressions lambda ou aux arbres dexpressions.

Initialisation dobjets Vous pouvez dsormais spcier les valeurs des membres et proprits public dune classe pendant son instanciation :
public class Address { public string address; public string city; public string state; public string postalCode; }

Sans la fonctionnalit dinitialisation ajoute C# 3.0, vous nauriez pas pu utiliser un constructeur spcialis, et vous auriez d dnir un objet de type Address, comme dans le Listing 2.7.
Listing 2.7 : Instanciation et initialisation de la classe avec lancienne mthode.
Address address = new Address(); address.address = "105 Elm Street"; address.city = "Atlanta"; address.state = "GA"; address.postalCode = "30339";

Cette technique serait trs lourde dans une expression lambda. Supposons que vous ayez dni une requte partir dune source de donnes et que vous vouliez projeter certains membres dans un objet Address en utilisant loprateur Select :
// Ce code ne passera pas la compilation IEnumerable<Address> addresses = somedatasource .Where(a => a.State = "GA") .Select(a => new Address(???)???);

Il nexiste aucun moyen simple dinitialiser les membres de lobjet Address. Nayez crainte : linitialisation dobjet de C# 3.0 est la solution. Bien sr, il serait possible de crer un constructeur qui vous permettrait de passer les valeurs initialiser linstanciation de lobjet. Mais quel travail ! Le Listing 2.8 montre comment rsoudre le problme par lintermdiaire dun type anonyme construit la vole.
Listing 2.8 : Instanciation et initialisation de la classe avec la nouvelle mthode.
Address address = new Address { address = "105 Elm Street", city = "Atlanta", state = "GA", postalCode = "30339" };

30

LINQ et C# 2008

Partie I

Les expressions lambda autorisent ce genre de manipulation, y compris en dehors des requtes LINQ ! Le compilateur instancie les membres nomms avec les valeurs spcies. Les ventuels membres non spcis utiliseront le type de donnes par dfaut. Initialisation de collections Les ingnieurs de Microsoft ont galement mis au point une technique dinitialisation de collections. Il vous suft pour cela de spcier les valeurs de la collection, tout comme vous le feriez pour un objet. Une restriction : la collection doit implmenter linterface System.Collections.Generic.ICollection<T>. Les collections C# hrites (celles qui se trouvent dans lespace de noms System.Collection) ne sont pas concernes. Le Listing 2.9 donne un exemple dinitialisation de collection.
Listing 2.9 : Un exemple dinitialisation de collection.
using System.Collections.Generic; List<string> presidents = new List<string> { "Adams", "Arthur", "Buchanan" }; foreach(string president in presidents) { Console.WriteLine(president); }

Voici le rsultat obtenu lorsque vous excutez le programme en appuyant sur Ctrl+F5 :
Adams Arthur Buchanan

Vous pouvez galement utiliser cette technique pour crer facilement des collections initialises dans le code, mme si vous nutilisez pas LINQ.
Types anonymes C# tant dans limpossibilit de crer de nouveaux types de donnes la compilation, il est difcile de dnir une nouvelle API agissant au niveau du langage pour les requtes gnriques. Les ingnieurs qui ont mis au point le langage C# 3.0 ont relev cette prouesse : dsormais, il est possible de crer dynamiquement des classes non nommes et des proprits dans ces classes. Ce type de classe est appel "type anonyme".

Un type anonyme na pas de nom et est gnr la compilation, en initialisant un objet en cours dinstanciation. tant donn que la classe na pas de type, toute variable affecte un objet dun type anonyme doit pouvoir le dclarer. Cest l quintervient le motcl new de C# 3.0. Un type anonyme ne peut pas tre estim sil est issu dun oprateur Select ou SelectMany. Sans les types anonymes, des classes nommes devraient tre dnies pour rece-

Chapitre 2

Amliorations de C# 3.0 pour LINQ

31

voir des donnes issues des oprateurs Select ou SelectMany. Ceci se rvlerait trs lourd et peu pratique mettre en place. Dans la section relative linitialisation dobjets, jai introduit le code dinstanciation et dinitialisation suivant :
Address address = new Address { address = "105 Elm Street", city = "Atlanta", state = "GA", postalCode = "30339" };

Pour utiliser un type anonyme la place de la classe nomme Address, il suft domettre le nom de la classe. Notez cependant quil est impossible de stocker le nouvel objet instanci dans une variable de type Address, car lobjet nest pas encore de type Address. Son type nest connu que du compilateur. Il est donc galement ncessaire de changer le type de donnes de la variable address en utilisant le mot-cl var (voir Listing 2.10).
Listing 2.10 : Instanciation et initialisation dun type anonyme en utilisant linitialisation dobjets.
var address = new { address = "105 Elm Street", city = "Atlanta", state = "GA", postalCode = "30339" }; Console.WriteLine("address = {0}: city = {1}: state = {2}: zip = {3}", address.address, address.city, address.state, address.postalCode); Console.WriteLine("{0}", address.GetType().ToString());

La dernire ligne a t ajoute pour afcher le nom de la classe anonyme gnre par le compilateur. Voici le rsultat :
address = 105 Elm Street: city = Atlanta: state = GA: zip = 30339 <>f__AnonymousType5`4[System.String,System.String,System.String,System.String]

Ce nom peu orthodoxe laisse clairement entendre quil a t gnr par un compilateur (le nom gnr par votre compilateur a de grandes chances dtre diffrent). Mthodes dextension Une mthode dextension est une mthode ou une classe statique qui peut tre invoque comme sil sagissait dune mthode dinstance dune classe diffrente. Vous pourriez par exemple crer la mthode statique dextension ToDouble dans la classe statique StringConversions. Cette mthode serait appele comme sil sagissait dune mthode dun objet de type string.

32

LINQ et C# 2008

Partie I

Avant dentrer dans le dtail des mthodes dextension, nous allons nous intresser au problme qui leur a donn naissance. Nous allons comparer les mthodes statiques (class) aux mthodes dinstance (object). Les mthodes dinstance peuvent seulement tre appeles dans les instances dune classe, aussi appeles objets. Il est impossible dappeler une mthode dinstance dans la classe elle-mme. Au contraire, les mthodes statiques ne peuvent tre appeles qu lintrieur dune classe.
Rappel sur les mthodes dinstance et les mthodes statiques La mthode ToUpper de la classe string est un exemple dune mthode dinstance : elle ne peut tre appele que sur un objet string. En aucun cas sur la classe string elle-mme.

Dans le code du Listing 2.11, la mthode ToUpper est appele sur lobjet name.
Listing 2.11 : Appel dune mthode dinstance dun objet.
// Ce code passe ltape de la compilation string name = "Joe"; Console.WriteLine(name.ToUpper());

Ce code est compilable. Son excution afche la conversion en majuscules de la variable name :
JOE

Si vous essayez dappeler la mthode ToUpper sur la classe string, vous obtiendrez une erreur de compilation, car ToUpper est une mthode dinstance. Elle ne peut donc tre appele qu partir dun objet et non dune classe. Le Listing 2.12 donne un exemple dun tel code.
Listing 2.12 : Tentative dappel dune mthode dinstance sur une classe.
// Ce code ne passe pas ltape de la compilation string.ToUpper();

Voici lerreur afche par le compilateur :


An object reference is required for the nonstatic field, method, or property string.ToUpper()

Cet exemple peut sembler un peu bizarre, puisque aucune valeur na t communique ToUpper. Si vous essayiez de passer une valeur ToUpper, cela reviendrait appeler une variante de la mthode ToUpper. Ceci est impossible puisquil nexiste aucun prototype de ToUpper dont la signature contienne un string. Faites la diffrence entre la mthode ToUpper et la mthode Format de la classe string. Cette dernire est statique. Elle doit donc tre applique la classe string et non un

Chapitre 2

Amliorations de C# 3.0 pour LINQ

33

objet string. Essayons dinvoquer cette mthode sur un objet string (voir Listing 2.13).
Listing 2.13 : Tentative dappel dune mthode de classe sur un objet.
string firstName = "Joe"; string lastName = "Rattz"; string name = firstName.Format("{0} {1}", firstName, lastName); Console.WriteLine(name);

Ce code produit lerreur suivante lors de la compilation :


Member string.Format(string, object, object) cannot be accessed with an instance reference; qualify it with a type name instead

Appliquons maintenant la mthode Format sur la classe string elle-mme (voir Listing 2.14).
Listing 2.14 : Appel dune mthode de classe sur une classe.
string firstName = "Joe"; string lastName = "Rattz"; string name = string.Format("{0} {1}", firstName, lastName); Console.WriteLine(name);

Ce code passe la compilation et donne le rsultat suivant lexcution :


Joe Ratz

Outre le mot-cl static, il suft souvent dobserver la signature dune mthode pour savoir quil sagit dune mthode dinstance. Considrez par exemple la mthode ToUpper. Elle ne comprend aucun autre argument que la version surcharge de la rfrence lobjet. Si elle ne dpend pas dune instance string dune donne interne, quelle valeur string pourrait-elle mettre en majuscules ?
Rsolution du problme par les mthodes dextension Supposons que vous soyez un dveloppeur et que vous deviez mettre en place une nouvelle faon dinterroger des objets. Supposons que vous dcidiez de crer une mthode Where pour traiter la clause Where. Comment procderiez-vous ?

Loprateur Where devrait-il tre trait dans une mthode dinstance ? Dans ce cas, quelle classe ajouteriez-vous cette mthode, tant donn que vous voulez que la mthode Where puisse interroger toute collection dobjets. Aucune rponse logique cette question ! En adoptant cette approche, vous devriez modier un trs grand nombre de classes si vous vouliez que la mthode soit universelle. La mthode doit donc tre statique. Comme nous allons le voir dans les lignes suivantes, si lon se rfre aux requtes SQL traditionnelles, incluant plusieurs clauses where, jointures, regroupements et/ou tris, une mthode statique nest pas vraiment approprie.

34

LINQ et C# 2008

Partie I

Supposons que vous ayez dni un nouveau type de donnes : une squence dobjets gnriques que nous appellerons Enumerable. La mthode Where devrait oprer sur un Enumerable et retourner un autre Enumerable ltr. De plus, la mthode Where devrait accepter un argument qui permette au dveloppeur de prciser la logique utilise pour ltrer les enregistrements de donnes depuis ou dans lEnumerable. Cet argument, que jappellerai le prdicat, pourrait tre spci dans une mthode nomme, une mthode anonyme ou une expression lambda.
ATTENTION Les trois codes qui suivent sont purement dmonstratifs. Ils ne passeront pas ltape de la compilation.

tant donn que la mthode Where demande une entre ltrer de type Enumerable, et que la mthode est statique, cette entre doit tre spcie dans un argument de la mthode Where. Ceci pourrait se matrialiser comme suit :
static Enumerable Enumerable.Where(Enumerable input, LambdaExpression predicate) { }

En ignorant pour linstant la smantique dune expression lambda, un appel la mthode Where pourrait seffectuer par les instructions suivantes :
Enumerable enumerable = {"one", "two", "three"}; Enumerable filteredEnumerable = Enumerable.Where(enumerable, lambdaExpression);

Cela ne sannonce pas trop mal. Mais que faire si nous avons besoin de plusieurs clauses Where ? Puisque lEnumerable sur lequel travaille la mthode Where doit tre un argument de la mthode, le chanage des mthodes revient les imbriquer. Voici comment appeler trois clauses Where :
Enumerable enumerable = {"one", "two", "three"}; Enumerable finalEnumerable = Enumerable.Where(Enumerable.Where(Enumerable.Where(enumerable, lX1), lX2), lX3);

Vous devez lire la dernire instruction de la partie la plus interne vers la partie la plus externe. Trs difcile lire ! Pouvez-vous imaginer quoi ressemblerait une requte plus complexe ? Si seulement il y avait un autre moyen
La solution Une solution lgante consisterait appeler la mthode statique Where sur chaque objet Enumerable, plutt que sur la classe. Il ne serait alors plus ncessaire de passer chaque Enumerable dans la mthode Where, puisque lobjet Enumerable aurait accs ses propres Enumerable. La requte prcdente deviendrait donc :
Enumerable enumerable = {"one", "two", "three"}; Enumerable finalEnumerable = enumerable.Where(lX1).Where(lX2).Where(lX3);

Chapitre 2

Amliorations de C# 3.0 pour LINQ

35

ATTENTION Les codes qui prcdent ainsi que le code qui suit sont purement dmonstratifs. Ils ne passeront pas ltape de la compilation.

Ce code pourrait tre rcrit comme suit :


Enumerable enumerable = {"one", "two", "three"}; Enumerable finalEnumerable = enumerable .Where(lX1) .Where(lX2) .Where(lX3);

Ce code est bien plus lisible : la dclaration peut maintenant tre lue de gauche droite et de haut en bas. Comme vous pouvez le voir, cette syntaxe est trs simple suivre. Cest la raison pour laquelle vous verrez de nombreuses requtes LINQ exprimes de la sorte dans la documentation ofcielle et dans cet ouvrage. Pour terminer, vous avez besoin dune mthode statique qui puisse tre appele dans une mthode de classe. Ce sont exactement les possibilits offertes par les mthodes dextension. Elles ont t ajoutes C# pour permettre dappeler lgamment une mthode statique sans avoir passer le premier argument de la mthode. Cela permet dappeler la mthode dextension comme sil sagissait de la mthode du premier argument. Les appels chans aux mthodes dextension sont donc bien plus lisibles. Les mthodes dextension permettent LINQ dappliquer des oprateurs de requte standard aux types qui implmentent linterface IEnumerable<T>.
INFO Les mthodes dextension peuvent tre appeles sur une instance de classe (un objet) et non sur la classe elle-mme.

Dclarations et invocations de mthodes dextension Il suft dutiliser le mot-cl this comme premier argument dune mthode pour la transformer en une mthode dextension.

La mthode dextension peut tre utilise sur nimporte quel objet dont le type est le mme que celui de son premier argument. Si, par exemple, le premier argument de la mthode dextension est de type string, elle apparatra comme une mthode dinstance string et pourra tre applique tout objet string. Ayez toujours lesprit que les mthodes dextension ne peuvent tre dclares que dans des classes statiques.

36

LINQ et C# 2008

Partie I

Voici un exemple dune mthode dextension :


namespace Netsplore.Utilities { public static class StringConversions { public static double ToDouble(this string s) { return Double.Parse(s); } public static bool ToBool(this string s) { return Boolean.Parse(s); } } }

Les classes et mthodes utilises sont toutes statiques. Pour utiliser ces mthodes dextension, il suft dappeler les mthodes statiques sur des instances dobjets, comme dans le Listing 2.15. tant donn que la mthode ToDouble est statique et que son premier argument est this, ToDouble est une mthode dextension.
Listing 2.15 : Appel dune mthode dextension.
using Netsplore.Utilities; double pi = "3.1415926535".ToDouble(); Console.WriteLine(pi);

Voici le rsultat du WriteLine :


3.1415926535

Il est important de spcier la directive using sur lespace de noms Netsplore.Utilities. Si vous lomettez, le compilateur ne trouvera pas les mthodes dextension et vous obtiendrez une erreur du type suivant :
string does not contain a definition for ToDouble and no extension method ToDouble accepting a first argument of type string could be found (are you missing a using directive or an assembly reference?)

Comme indiqu prcdemment, il nest pas permis de dclarer une mthode dextension lintrieur dune classe non statique. Si vous le faites, vous obtiendrez le message derreur suivant :
Extension methods must be defined in a non-generic static class

Prcdence des mthodes dextension Les instances dobjets conventionnelles ont une prcdence sur les mthodes dextension lorsque leur signature est identique la signature dappel.

Les mthodes dextension sont un concept trs utile, en particulier si vous voulez tendre une classe "scelle" ou dont vous ne connaissez pas le code. Les mthodes dextension

Chapitre 2

Amliorations de C# 3.0 pour LINQ

37

prcdentes ajoutent des mthodes la classe string. Si les mthodes dextension nexistaient pas, vous ne pourriez pas le faire, car la classe string est scelle. Mthodes partielles Les mthodes partielles ajoutent un mcanisme de gestion dvnements ultralger au langage C#. Oubliez les conclusions que vous tes certainement en train de tirer sur les mthodes partielles : le seul point commun entre les mthodes partielles et les classes partielles est quune mthode partielle ne peut exister que dans une classe partielle. Avant de passer en revue les autres rgles sur les mthodes partielles, nous allons nous intresser leur nature. Le prototype ou la dnition dune mthode partielle est spci dans sa dclaration, mais cette dernire ninclut pas limplmentation de la mthode. Aucun code IL nest donc mis par le compilateur lors de la dclaration de la mthode, lappel de la mthode ou lvaluation des arguments passs la mthode. Cest comme si la mthode navait jamais exist ! Le terme "mthode partielle" peut sembler inappropri si lon compare le comportement dune mthode partielle celui dune classe partielle. Le terme "mthode fantme" aurait certainement t plus judicieux
Un exemple de mthode partielle Voici un exemple de classe partielle dans lequel est dnie une mthode partielle.

La classe MyWidget
public partial class MyWidget { partial void MyWidgetStart(int count); partial void MyWidgetEnd(int count); public MyWidget() { int count = 0; MyWidgetStart(++count); Console.WriteLine("In the constructor of MyWidget."); MyWidgetEnd(++count); Console.WriteLine("count = " + count); } }

Cette classe partielle MyWidget contient une mthode partielle galement nomme MyWidget. Les deux premires lignes dnissent les mthodes partielles MyWidgetStart et MyWidgetStop. Toutes deux acceptent un paramtre et retournent void (cette dernire caractristique est une obligation des mthodes partielles). Le bloc de code suivant est le constructeur. Comme vous pouvez le voir, il dnit lint count et linitialise 0. La mthode MyWidgetStart est alors appele, un message est afch dans la console, la mthode MyWidgetStop est appele puis la valeur de count est afche dans la console. La valeur de count est incrmente chaque passage dans

38

LINQ et C# 2008

Partie I

la mthode partielle. Ceci an de prouver que, si une mthode partielle nest pas implmente, ses arguments ne sont pas valus. Le code du Listing 2.16 dnit un objet de classe MyWidget.
Listing 2.16 : Instanciation de la classe MyWidget.
MyWidget myWidget = new MyWidget();

Appuyez sur Ctrl+F5 pour excuter le code. Voici le rsultat obtenu dans la console :
In the constructor of MyWidget. count = 0

Comme vous pouvez le voir, aprs que le constructeur de MyWidget eut incrment deux reprises la variable count, la valeur afche la n du constructeur est gale zro. Ceci vient du fait que les arguments des mthodes partielles ne sont pas implments. Aucun code IL nest donc mis par le compilateur. Nous allons maintenant ajouter une implmentation pour les deux mthodes partielles : Une autre dclaration de MyWidget contenant limplmentation des mthodes partielles
public partial class MyWidget { partial void MyWidgetStart(int count) { Console.WriteLine("In MyWidgetStart(count is {0})", count); } partial void MyWidgetEnd(int count) { Console.WriteLine("In MyWidgetEnd(count is {0})", count); } }

Limplmentation ayant t rajoute, excutez nouveau le code du Listing 2.16. Vous obtiendrez lafchage suivant dans la console :
In MyWidgetStart(count is 1) In the constructor of MyWidget. In MyWidgetEnd(count is 2) count = 2

Comme vous pouvez le voir, les mthodes partielles ont t implmentes et les arguments, passs et valus (la variable count vaut 2 la n de la sortie cran).
Pourquoi utiliser les mthodes partielles ? Vous vous demandez peut-tre pourquoi utiliser des mthodes partielles. Certains rtorqueront quelles sapparentent lhritage et aux mthodes virtuelles. Mais, alors, pourquoi alourdir le langage avec les mthodes partielles ? Tout simplement parce quelles sont plus efcaces si vous prvoyez dutiliser des procdures potentiellement

Chapitre 2

Amliorations de C# 3.0 pour LINQ

39

non implmentes. Elles permettent dcrire du code pouvant tre tendu par une personne tierce via le paradigme des classes partielles sans dgradation de performances. Les mthodes partielles ont certainement t ajoutes C# pour les besoins des outils de gnration de classes dentits de LINQ to SQL. titre dexemple, chaque proprit mappe dune classe dentits possde une mthode partielle qui est appele avant que la proprit ne change et une autre qui est appele aprs que la proprit eut chang. Ceci permet dajouter un autre module en dclarant la mme classe dentit, dimplmenter ces mthodes partielles et dtre averti chaque fois quune proprit est sur le point dtre modie et aprs sa modication. Cela nest-il pas intressant ? Le code ne sera ni plus volumineux ni plus lent. Alors, ne vous en privez pas !
Les rgles Les mthodes partielles doivent respecter quelques rgles. Ces dernires ne sont pas trop contraignantes, et lon y gagne vraiment au change en termes de exibilit et de possibilits offertes au programmeur. Les voici :
m m m

Elles ne doivent tre dnies et implmentes que dans des classes partielles. Elles doivent tre prxes par le mot-cl partiel. Elles sont prives mais ne doivent pas utiliser le mot-cl private, sinon une erreur sera gnre la compilation. Elles doivent retourner void. Elles peuvent ne pas tre implmentes. Elles peuvent tre static. Elles peuvent avoir des arguments.

m m m m

Expressions de requte Un des avantages du langage C# est la dclaration foreach. Cette instruction est remplace par le compilateur par une boucle qui appelle des mthodes telles que GetEnumerator et MoveNext. La simplicit de cette instruction la rendue universelle lorsquil sagit dnumrer des tableaux et collections. La syntaxe des requtes LINQ est trs proche de celle de SQL et vraiment apprcie par les dveloppeurs. Les exemples des pages prcdentes utilisent cette syntaxe, propre C# 3.0, connue sous le nom "expressions de requtes". Pour raliser une requte LINQ, il nest pas obligatoire dutiliser une expression de requte. Une alternative consiste utiliser la notation " point" standard de C#, en appliquant des mthodes des objets et des classes. Dans de nombreux cas, lutilisation de la notation standard est favorable au niveau des instructions, car trs dmonstrative. Plusieurs exemples de ce livre prfrent la syntaxe " point" traditionnelle aux expressions

40

LINQ et C# 2008

Partie I

de requte. Il ny a aucune concurrence entre ces deux types dcritures. Cependant, la facilit avec laquelle vous crirez vos premires expressions de requte peut se rvler enthousiasmante Pour avoir une ide des diffrences entre les deux types de notations, le Listing 2.17 met en uvre une requte fonde sur la syntaxe traditionnelle de C#.
Listing 2.17 : Une requte utilisant la notation point traditionnelle.
string[] names = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> sequence = names .Where(n => n.Length < 6) .Select(n => n); foreach (string name in sequence) { Console.WriteLine("{0}", name); }

Le Listing 2.18 est la requte quivalente fonde sur les expressions de requte.
Listing 2.18 : La requte quivalente fonde sur les expressions de requte.
string[] names = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> sequence = from n in names where n.Length < 6 select n; foreach (string name in sequence) { Console.WriteLine("{0}", name); }

La premire chose qui saute aux yeux quant lexpression de requte est que, contrairement au SQL, la dclaration from prcde le select. Une des raisons majeures ayant motiv ce changement vient de lIntelliSense. Sans cette inversion, si vous tapiez select suivi dune espace dans lditeur de Visual Studio 2008, IntelliSense naurait aucune ide des lments afcher dans la liste droulante. En indiquant do proviennent les donnes, IntelliSense a une ide prcise des variables proposer dans la liste droulante.

Chapitre 2

Amliorations de C# 3.0 pour LINQ

41

Ces deux exemples donnent le rsultat suivant :


Adams Bush Ford Grant Hayes Nixon Polk Taft Tyler

Grammaire des expressions de requte Les expressions de requte doivent se conformer aux rgles de grammaire suivantes :

1. Une expression de requte doit toujours commencer par la clause from. 2. Peuvent ensuite venir zro, une ou plusieurs clauses from, let et/ou where. La clause from dnit une ou plusieurs numrations qui passent en revue les lments dune ou de plusieurs squences. La clause let dnit une variable et lui affecte une valeur. La clause where ltre les lments dune squence ou ralise une jointure de plusieurs squences dans la squence de sortie. 3. La suite de lexpression de requte peut contenir une clause orderby qui trie les donnes sur un ou plusieurs champs. Le tri peut tre ascendant ( ascending) ou descendant (descending). 4. Une clause select ou group doit alors faire suite. 5. La suite de lexpression de requte peut contenir une clause de continuation optionnelle into, zro, une ou plusieurs clauses join, ainsi quun ou plusieurs autres blocs syntaxiques, partir du point numro 2. La clause into redirige les rsultats de la requte dans une squence de sortie imaginaire. Cette squence se comporte comme une clause from pour lexpression suivante commenant par le point numro 2. Pour une description plus technique de la grammaire des expressions de requte, utilisez le diagramme suivant provenant de la documentation ofcielle MSDN sur LINQ.
Expression de requte from-clause query-body Clause from from typeopt identifier in expression join-clausesopt Clauses join join-clause join-clauses join-clause Clause join join typeopt identifier in expression on expression equals expression join typeopt identifier in expression on expression equals expression into identifier Corps de la requte from-let-where-clausesopt orderby-clauseopt select-or-group-clause query-continuationopt

42

LINQ et C# 2008

Partie I

Clauses from, let et where from-let-where-clause from-let-where-clauses from-let-where-clause Clause from, let et where from-clause let-clause where-clause Clause let let identifier = expression Clause where where boolean-expression Clause orderby orderby orderings Tris ordering orderings , ordering Tri expression ordering-directionopt Direction du tri ascending descending Clause select ou group select-clause group-clause Clause select select expression Clause group group expression by expression Continuation de la requte into identifier join-clausesopt query-body

Traduction des expressions de requte Supposons que vous ayez cr une expression de requte syntaxiquement correcte. Pour la traduire en code " point" C#, le compilateur recherche des "motifs". La traduction seffectue en plusieurs tapes. Chacune dentre elles recherche un ou plusieurs motifs spciques. Le compilateur ritre la traduction pour tous les motifs correspondant ltape actuelle avant de passer la suivante. Par ailleurs, ltape n de la traduction ne peut se faire que si les n1 tapes prcdentes ont t acheves.

Identicateurs transparents Certaines traductions insrent des variables dnumration comprenant des identicateurs transparents. Dans les descriptions de la section suivante, les identicateurs transparents sont identis par des astrisques (*). Ce signe ne doit pas tre confondu avec le caractre de remplacement "*". Lors de la traduction, il arrive que certaines numrations additionnelles soient gnres par le compilateur et que des identicateurs transparents soient utiliss pour les numrer (ces identicateurs nexistent que pendant le processus de traduction).

Chapitre 2

Amliorations de C# 3.0 pour LINQ

43

tapes de la traduction Dans cette section, nous allons utiliser les conventions du Tableau 2.1, o des lettres reprsentent les variables utilises dans des portions spciques dune requte.
Tableau 2.1 : Variables de traduction.

Variable c e f g i k l o s v w

Description Variable temporaire gnre par le compilateur Variable dnumration Champ slectionn ou nouveau type anonyme Un lment group Un imaginaire dans une squence lment cl group ou joint Une variable dnie avec let Un lment class La squence dentre Une valeur affecte une variable par let Une clause where

Exemple aucun from e in customers from e in customers select f from e in s group g by k from e in s into i from e in s group g by k from e in s let l = v from e in s orderby o from e in s from e in s let l = v from e in s where w

Attention ! Le processus de traduction est complexe. Que cela ne vous dcourage pas ! En effet, vous navez pas besoin de comprendre ce qui va tre dit dans les dtails pour crire des requtes LINQ. Les informations donnes dans cette section sont un plus. Il y a fort parier que vous nen aurez que rarement besoin, voire jamais. Dans la suite, les tapes de la traduction seront spcies sous la forme motif> traduction. Je vais prsenter ces tapes en me conformant lenchanement logique du compilateur. Il serait sans doute plus simple de comprendre le processus de traduction en utilisant lenchanement inverse de celui du compilateur. En effet, la premire tape ne met en uvre que le premier motif. Elle donne naissance plusieurs autres motifs non traduits quil faut encore traiter. tant donn que chaque tape de traduction ncessite que ltape prcdente soit entirement traduite, lorsque le processus est termin il ne reste plus aucun terme traduire. Cest la raison pour laquelle la dernire tape de la traduction est plus aise comprendre que la premire. Et la description inverse des tapes de traduction est galement la meilleure faon de comprendre ce qui se passe. Ceci tant dit, voici les tapes de traduction, dcrites dans lordre du compilateur.

44

LINQ et C# 2008

Partie I

Clauses Select et Group avec une clause into Si une expression de requte contient une clause into, la traduction suivante est effectue :
from 1 into i 2

>

from i in from 1 2

Voici un exemple :
from c in customers group c by c.Country into g select new { Country = g.Key, CustCount = g.Count() } from g in from c in customers group c by c.Country select new { Country = g.Key, custCount = g.Count() }

>

Lorsque toutes les tapes de la traduction ont t passes, le code est nalement traduit en :
customers.GroupBy(c => c.Country) .Select(g => new { Country = g.Key, CustCount = g.Count() })

Types explicites de variables dnumration Si votre expression de requte contient une clause from qui spcie explicitement le type dune variable dnumration, la traduction suivante sera effectue :
from T e in s

> >

from e in s.Cast<T>()

Voici un exemple :
from Customer c in customers select c from c in customers.Cast<Customer>()

Lorsque toutes les tapes de la traduction ont t passes, le code est nalement traduit en :
customers.Cast<Customer>()

Si lexpression de requte contient une clause join qui spcie explicitement un type de variable dnumration, la traduction suivante est effectue :
join T e in s on k1 equals k2

>

join e in s.Cast<T>() on k1 equals k2

Voici un exemple :
from c in customers join Order o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }

>

from c in customers join o in orders.Cast<Order>() on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total }

Lorsque toutes les tapes de la traduction ont t passes, le code est nalement traduit en :
.Join(orders.Cast<Order>(), c => c.CustomerID, o => o.CustomerID, <c, o) => new { c.Name, o.OrderDate, o.Total })

Chapitre 2

Amliorations de C# 3.0 pour LINQ

45

ASTUCE La saisie explicite de variables dnumration est ncessaire lorsque la collection de donnes numre est hrite des collections de C# (ArrayList, par exemple). Le casting opr convertit la collection hrite en une squence qui implmente IEnumerable<T> an dassurer la compatibilit avec les oprateurs de requte.

Clauses join Si lexpression de requte contient une clause from suivie dune clause join, mais pas dune clause into suivie dune clause select, la traduction suivante est opre (t est une variable temporaire cre par le compilateur) :
from e1 in s1 join e2 in s2 on k1 equals k2 select f from t in s1 .Join(s2, e1 => k1, e2 => k2, (e1, e2) => f) select t

>

Voici un exemple :
from c in customers join o in orders on c.CustomerID equals o.CustomerID select new { c.Name, o.OrderDate, o.Total } from t in customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) =>new { c.Name, o.orderDate o.Total }) select t

>

Lorsque toutes les tapes de la traduction ont t passes, le code est nalement traduit en :
customers .Join(orders, c => c.CustomerID, o => o.CustomerID, (c, o) => new { c.Name, o.OrderDate, o.Total })

Si lexpression de requte contient une clause from suivie dune clause join, puis dune clause into suivie dune clause select, la traduction suivante est opre (t est une variable temporaire cre par le compilateur) :
from e1 in s1 join e2 in s2 on k1 equals k2 into i select f from t in s1 .GroupJoin(s2, e1 => k1, > e2 => k2, (e1, i) => f) select t

Voici un exemple :
from c in customers join o in orders on c.CustomerID equals o.CustomerID into co select new { c.Name, Sum = co.Sum(o => o.Total) } Sum = co.Sum( o => co.Total) from t in customers .groupJoin(orders, > c => c.CustomerID, o => o.CustomerID, (c, co) => new { c.Name,

Select t

46

LINQ et C# 2008

Partie I

En utilisant les tapes de traduction suivantes, le code est nalement traduit en :


Customers .GroupJoin(orders, c => c.CustomerIDc.CustomerID, o => o.CustomerID, (c, co) => new { c.Name, Sum = co.Sum(o = o.Total) })

Si lexpression de requte contient une clause from suivie dune clause join mais pas dune clause into suivie par quelque chose dautre quune clause select, la traduction suivante est opre (* est un oprateur transparent) :
from e1 in s1 join e2 in s2 on k1 equals k2 from * in from e1 in s1 join e2 in s2 on k1 equals k2 select new { e1, e2 }

>

Le motif gnr correspond au premier motif de la section "Clauses Join" : la requte contient une clause from suivie dune clause join. La clause into est absente, mais une clause select est prsente. Une nouvelle traduction sera donc opre. Si lexpression de requte contient une clause from suivie dune clause join, puis dune clause into suivie par quelque chose dautre quune clause select, la traduction suivante est opre (* est un oprateur transparent) :
from e1 in s1 join e2 in s2 on k1 equals k2 into i from * from e1 in s1 join e2 in s2 on k1 equals k2 into i select new { e1, i }

>

Le motif gnr correspond au deuxime motif de la section "Clauses Join" : on trouve une clause from suivie dune clause join, dune clause into puis dune clause select. Une nouvelle traduction sera donc opre.
Les clauses Let et Where Si lexpression de requte contient une clause from suivie immdiatement dune clause let, la traduction suivante est effectue (* est un identicateur transparent) :
from e in s let l = v

>

from * in from e1 in s1 select new { e, l = v }

Voici un exemple (t est un identicateur gnr par le compilateur. Il reste invisible et inaccessible par le code) :
from c in customers let cityStateZip = c.City + ", " + c.State + " " + c.Zip select new { c.Name, cityStateZip } from * in from c in customers select new { c, cityStateZip = c.City + ", " + c.State + " " + c.Zip }

>

select new { c.Name, cityStateZip }

Chapitre 2

Amliorations de C# 3.0 pour LINQ

47

Une fois les autres tapes de traduction termines, le code est nalement transform en :
customers .Select(c => new { c, cityStateZip = c.City + ", " + c.State + " " + c.Zip }) .Select(t => new { t.c.Name, t.cityStateZip })

Si lexpression de requte contient une clause from suivie dune clause where, la traduction suivante est opre :
from e in s where w

>

from e in s .Where(e => w)

Voici un exemple :
from c in customers where c.Country == "USA" select new { c.Name, c.Country }

>

from c in customers .Where (c => c.Country == "USA") select new { c.Name, c.Country }

Une fois les autres tapes de traduction termines, le code est nalement transform en :
customers .Where(c => c.Country == "USA") .Select(c => new { c.Name, c.Country })

Clauses from multiples Si lexpression de requte contient deux clauses from suivies par une requte select, la traduction suivante est opre :
from e1 in s1 from e2 in s2 select f

>

from c in s1 .SelectMany(e1 => from e2 in s2 select f) select c

Voici un exemple (t est une variable temporaire gnre par le compilateur) :


from c in customers from o in c.Orders select new { c.Name, o.OrderID, o.OrderDate } from t in customers .SelectMany(c => from o in c.Orders select new { c.Name, o.OrderID, o.OrderDate }) Select t

>

Une fois les autres tapes de traduction termines, le code est nalement transform en :
customers .SelectMany(c => c.Orders.Select(o => new { c.Name, o.OrderID, o.OrderDate }))

Si lexpression de requte contient deux clauses from suivies par quelque chose dautre quune clause select, la traduction suivante est opre (* est un identicateur transparent) :
from e1 in s1 from e2 in s2 from * in from e1 in s1 from e2 in s2 select new { e1, e2 }

>

48

LINQ et C# 2008

Partie I

Voici un exemple (* est un identicateur transparent) :


from c in customers from o in c.Orders orderby o.OrderDate descending select new {c.Name, o.OrderID, o.OrderDate } from * in from c in customers from o in c.Orders select new { c, o } orderby o.OrderDate descending select new { c.Name, o.OrderID, o.OrderDate }

>

Le code ainsi obtenu doit ritrer la premire tape de traduction. En effet, le motif rsultant contient une clause from suivie par une autre clause from puis par une clause select, ce qui correspond au premier modle de la section "Clauses from multiples". Il sagit donc dun exemple dans lequel certaines tapes doivent tre appeles plusieurs fois pour que la traduction soit complte. Une fois toutes les tapes de traduction termines, le code est nalement transform en :
customers .SelectMany(c => c.Orders.Select(o => new { c, o })) .OrderByDescending(t => t.o.OrderDate) .Select(t => new { t.c.Name, t.o.OrderID, t.o.OrderDate})

Clauses OrderBy Les traductions suivantes prennent place dans un tri ascendant :
from e in s orderby o1, o2

>

from e in s .OrderBy(e => o1).ThenBy(e => o2)

Voici un exemple :
from c in customers orderby c.Country, c. Name select new { c.Country, c.Name} select new { c.Country, c.Name } from c in customers .OrderBy(c => c.Country) .TheBy(c.Name)

Une fois toutes les tapes de traduction termines, le code est nalement transform en :
customers .OrderBy(c => c.Country) .ThenByDescending(c.Name) .Select(c => new { c.Country, c.Name }

Clauses Select Dans une expression de requte, si vous slectionnez la totalit de llment stock dans la squence, llment slectionn a le mme identicateur que la variable dnumration de la squence. La traduction suivante est opre :
from e in s select f

>

Voici un exemple :
from c in customers select c

>

customers

Chapitre 2

Amliorations de C# 3.0 pour LINQ

49

Si llment slectionn na pas le mme identicateur que la variable dnumration de la squence, cela signie quil nest pas slectionn en totalit (la slection peut porter sur un membre de llment ou sur un type anonyme construit partir de plusieurs membres de llment). La traduction suivante est effectue :
from e in s select f

>

s.Select(e => f)

Voici un exemple :
from c in customers select c.Name

>

customers.Select(c => c.Name)

Clauses group Dans lexpression de requte, si llment regroup a le mme identicateur que lnumrateur de la squence, cela signie que le regroupement porte sur la totalit de llment stock dans la squence. La traduction est la suivante :
from e in s group g by k

>

s.GroupBy(e => k)

Voici un exemple :
from c in customers group c by c.Country

>

customers.GroupBy(c => c.Country)

Si llment regroup na pas le mme identicateur que lnumrateur de la squence, cela signie quil nest pas regroup en totalit. La traduction suivante est effectue :
from e in s group g by k

>

s.GroupBy(e => k, e => g)

Voici un exemple :
from c in customers group new { c.Country, c. Name} by c.Country customers .GroupBy(c => c.Country, c => new { c.Country c.Name })

>

Toutes les tapes de la traduction ont t effectues et lexpression de requte a t entirement traduite en une notation " point" traditionnelle.

Rsum
De nombreuses fonctionnalits ont t ajoutes au langage C#. Bien que ces ajouts aient t dicts par limplmentation de LINQ, vous avez tout intrt les utiliser en dehors du contexte LINQ. Les expressions dinitialisation dobjets et de collections sont particulirement intressantes, car elles rduisent la taille du code de faon drastique. Cette fonctionnalit, combine avec le mot-cl var et aux types anonymes, facilite grandement la cration de donnes et de types de donnes la vole.

50

LINQ et C# 2008

Partie I

Les mthodes dextension permettent dajouter des fonctionnalits aux classes scelles et aux classes dont vous navez pas le code source. Si elles nliminent pas la raison dtre des mthodes anonymes, les expressions lambda reprsentent une nouvelle faon de dnir de nouvelles fonctionnalits, simplement et de faon concise. Lorsque vous commencerez les utiliser, vous serez peut-tre dconcert, mais, le temps aidant, vous les apprcierez leur juste valeur. Les arbres dexpressions permettent aux diteurs de logiciels tiers de conserver un mode de stockage propritaire tout en supportant les performances avances de LINQ. Les mthodes partielles ajoutent un mcanisme de gestion dvnements ultralger au langage C#. Elles sont utilises pour accder des moments cls dans les classes dentits LINQ to SQL. Si les expressions de requte peuvent sembler confuses de prime abord, il ne faut pas bien longtemps pour quun dveloppeur se sente laise leur contact. Elles ont en effet un air de parent avec les requtes SQL. Chacune de ces amliorations du langage est intressante en soi, mais cest leur utilisation conjointe qui est la base de LINQ. LINQ devrait tre la prochaine grande tendance en programmation. Les dveloppeurs .NET apprcieront certainement de pouvoir linscrire dans leur CV. En tout cas, moi, jen suis er ! Vous avez maintenant une ide de ce quest LINQ, ainsi que des fonctionnalits et syntaxes C# affrentes. Il est temps de passer la prochaine tape. En tournant les pages, vous allez apprendre appliquer des requtes LINQ des collections en mmoire (array ou arraylist, par exemple) et aux collections gnriques de C# 2.0, et vous dcouvrirez diffrentes fonctions pour alimenter vos requtes. Cette portion de LINQ est aujourdhui connue sous le nom de "LINQ to Objects".

II
LINQ to Objects

3
Introduction LINQ to Objects
Listing 3.1 : Une requte LINQ to Objects lmentaire.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string president = presidents.Where(p => p.StartsWith("Lin")).First(); Console.WriteLine(president);

INFO Ce code a t ajout au prototype dune application console Visual Studio 2008.

Le Listing 3.1 donne une ide de ce quest LINQ to Objects : par son intermdiaire, il est possible dinterroger des donnes en mmoire laide de requtes proches du langage SQL. Lancez le programme avec Ctrl+F5. Vous obtenez le rsultat suivant :
Lincoln

Vue densemble de LINQ to Objects


Si LINQ est aussi agrable et facile utiliser, cest en partie parce quil est parfaitement intgr dans le langage C#. Plutt quavoir composer avec de nouvelles classes spciques LINQ, vous pouvez utiliser les mmes collections1 et tableaux que prcdemment. Vous avez donc les avantages inhrents LINQ sans devoir retoucher (ou trs
1. Les collections doivent implmenter linterface IEnumerable<T> ou IEnumerable pour pouvoir tre interrogeables par LINQ.

54

LINQ to Objects

Partie II

peu) le code existant. LINQ to Objects sexcute travers linterface IEnumerable<T>, les squences et les oprateurs de requte standard. titre dexemple, pour trier un tableau dentiers, vous pouvez utiliser une requte LINQ, tout comme sil sagissait dune requte SQL. Un autre exemple. Si vous voulez trouver un objet Customer spcique dans un ArrayList of Customer, LINQ to Objects est assurment la rponse. Pour beaucoup dentre vous, les chapitres sur LINQ to Objects seront utiliss en tant que rfrence. Ils ont t construits dans cette optique et je vous conseille de les parcourir en totalit. Ne vous contentez pas de lire les sections des seuls oprateurs qui vous intressent, sans quoi votre formation sera incomplte.

IEnumerable<T>, squences et oprateurs de requte standard


IEnumerable<T>, prononc "Inumrable de T", est une interface implmente par les tableaux et les classes de collections gnriques de C# 2.0. Cette interface permet dnumrer les lments dune collection.

Une squence est un terme logique dune collection qui implmente linterface IEnumerable<T>. Si vous avez une variable de type IEnumerable<T>, vous pouvez dire que vous avez une squence de T. Par exemple, si vous avez un IEnumerable de string, ce qui scrit IEnumerable<string>, vous pouvez dire que vous avez une squence de string.
INFO Toutes les variables dclares en tant que IEnumerable<T> sont considres comme squences de T.

La plupart des oprateurs de requte standard sont des mthodes dextension de la classe statique System.Linq.Enumerable et ont un premier argument prototyp par un IEnumerable<T>. tant donn que ces oprateurs sont des mthodes dextension, il est prfrable de les appeler travers une variable de type IEnumerable<T> plutt que passer une variable de type IEnumerable<T> en premier argument. Les mthodes doprateurs de requte standard de la classe System.Linq.Enumerable qui ne sont pas des mthodes dextension sont des mthodes statiques. Elles doivent tre appeles dans la classe System.Linq.Enumerable. La combinaison de ces mthodes doprateurs de requte standard vous permet deffectuer des requtes complexes sur une squence IEnumerable<T>. Les collections hrites ces collections non gnriques qui existaient avant C# 2.0 supportent linterface IEnumerable, et non linterface IEnumerable<T>. Cela signie que vous ne pouvez pas appeler directement ces mthodes dextension dont le premier

Chapitre 3

Introduction LINQ to Objects

55

argument est un IEnumerable<T> sur une collection hrite. Cependant, vous pouvez toujours excuter des requtes LINQ sur des collections hrites en invoquant loprateur de requte standard Cast ou OfType. Cet oprateur produira une squence qui implmente linterface IEnumerable<T>, vous permettant ainsi daccder la panoplie complte des oprateurs de requte standard.
INFO Utilisez les oprateurs Cast ou OfType pour excuter des requtes LINQ sur des collections C# hrites et non gnriques.

Pour accder aux oprateurs de requte standard, vous devez ajouter une directive using System.Linq; dans votre code (si cette dernire nest pas dj prsente). Il nest pas ncessaire dajouter une rfrence un assembly car le code ncessaire est contenu dans lassembly System.Core.dll, qui est automatiquement ajout aux projets par Visual Studio 2008.

IEnumerable<T>, yield et requtes diffres


La plupart des oprateurs de requte standard sont prototyps pour retourner un IEnumerable<T> (une squence). Mais, attention, les lments de la squence ne sont pas retourns ds lexcution de loprateur : ils ne seront "cds" que lors de lnumration de la squence. Cest la raison pour laquelle on dit que ces requtes sont diffres. Le terme "cder" fait rfrence au mot-cl yield, ajout dans C# 2.0 pour faciliter lcriture dnumrateurs. Examinez le code du Listing 3.2.
Listing 3.2 : Une requte triviale.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Where(p => p.StartsWith("A")); foreach(string item in items) Console.WriteLine(item);

La requte apparat en gras dans ce listing. Lorsque cette ligne sexcute, elle retourne un objet. Ce nest que pendant lnumration de cet objet que la requte Where est rellement excute. Si une erreur se produit dans la requte, elle ne sera dtecte qu lnumration.

56

LINQ to Objects

Partie II

Voici le rsultat de la requte :


Adams Arthur

Cette requte sest comporte comme prvu. Nous allons maintenant introduire une erreur intentionnelle dans la requte. Le code qui suit va essayer deffectuer un tri en se basant sur le cinquime caractre du nom des prsidents. Lorsque lnumration atteint un nom dont la longueur est infrieure cinq caractres, une exception sera gnre. Rappelez-vous que lexception ne se produira pas avant lnumration de la squence rsultat (voir Listing 3.3).
Listing 3.3 : Une requte triviale avec une exception introduite intentionnellement.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Where(s => Char.IsLower(s[4])); Console.WriteLine("After the query."); foreach (string item in items) Console.WriteLine(item);

Ce code ne produit aucune erreur la compilation, mais voici les rsultats afchs dans la console :
Adams Arthur Buchanan Unhandled Exception: System.IndexOutOfRangeException: Index was outside the bounds of the array.

Tout se passe bien jusquau quatrime lment. Bush produit une exception lors de lnumration. La leon tirer de cet exemple est quune compilation russie ne suft pas pour assurer quune requte est vierge de tout bogue. Sachez par ailleurs que, les requtes qui retournent un IEnumerable<T> tant diffres, il suft dexcuter une seule fois le code de la requte. Vous pouvez ensuite numrer les donnes autant de fois que vous le souhaitez. Si, entre deux numrations, les donnes changent, les rsultats seront diffrents (voir Listing 3.4).
Listing 3.4 : Un exemple dans lequel les rsultats de la requte changent dune numration lautre.
// Cration dun tableau de int int[] intArray = new int[] { 1,2,3 };

Chapitre 3

Introduction LINQ to Objects

57

IEnumerable<int> ints = intArray.Select(i => i); // Affichage des rsultats foreach(int i in ints) Console.WriteLine(i); // Modification dun lment dans la source intArray[0] = 5; Console.WriteLine("---------"); // Nouvel affichage des rsultats foreach(int i in ints) Console.WriteLine(i);

Lorsque loprateur Select est appel, un objet est retourn et stock dans la variable IEnumerable<T> ints. La requte na pas encore t excute. Elle est juste stocke dans lobjet ints. Les rsultats de la requte nexistent donc pas encore, mais lobjet ints sait comment les obtenir. Lorsque linstruction foreach est appele pour la premire fois, ints excute la requte et obtient successivement les diffrents lments de la squence. Un peu plus bas, un des lments est modi dans son tableau dorigine, intArray[]. Linstruction foreach est appele nouveau. Cela provoque une nouvelle excution de la requte. Cette numration retourne tous les lments de intArray[] et donc galement llment qui a t modi. Dans cet ouvrage (et dans beaucoup dautres relatifs LINQ), vous pourrez lire quune requte retourne une squence et non un objet qui implmente linterface IEnumerable<T>. Ceci est un abus de langage : les lments de la squence ne sont obtenus qu son numration. Voici les rsultats afchs par ce code :
1 2 3 --------5 2 3

La requte na t appele quune fois et, pourtant, les rsultats des deux numrations sont diffrents. Cela conrme si besoin tait que la requte est bien diffre. Dans le cas contraire, les rsultats des deux numrations seraient identiques. Selon les cas, ceci peut tre un avantage ou un inconvnient. Si vous ne voulez pas que la requte soit diffre, utilisez un oprateur qui ne retourne pas un IEnumerable<T>. Par exemple ToArray, ToList, ToDictionary ou ToLookup. Les rsultats seront alors gs dans une mmoire cache et ne changeront pas.

58

LINQ to Objects

Partie II

Le Listing 3.5 est le mme que le prcdent, un dtail prs : en utilisant un oprateur ToList, la requte retourne non pas un IEnumerable<int> mais un List<int>.
Listing 3.5 : En retournant un objet List, la requte est excute immdiatement et les rsultats sont mis dans un cache.
// Cration dun tableau de int int[] intArray = new int[] { 1,2,3 }; List<int> ints = intArray.Select(i => i).ToList; // Affichage des rsultats foreach(int i in ints) Console.WriteLine(i); // Modification dun lment dans la source intArray[0] = 5; Console.WriteLine("---------"); // Nouvel affichage des rsultats foreach(int i in ints) Console.WriteLine(i);

Voici les rsultats :


1 2 3 --------1 2 3

Comme on pouvait sy attendre, les rsultats ne changent pas dune numration la suivante. La requte est donc bien excute immdiatement. Loprateur Select est diffr, et loprateur ToList ne lest pas. En appliquant ToList au rsultat du Select, lobjet retourn par Select est numr et la requte nest plus diffre. Dlgus Func Plusieurs des oprateurs de requte standard sont prototyps pour accepter un dlgu Func comme argument. Cela vous vite davoir dclarer des dlgus explicitement. Voici les dclarations de dlgus Func :
public public public public public delegate delegate delegate delegate delegate TR TR TR TR TR Func<TR>(); Func<T0, TR>(T0 a0); Func<T0, T1, TR>(T0 a0, T1 a1); Func<T0, T1, T2, TR>(T0 a0, T1 a1, T2 a2); Func<T0, T1, T2, T3, TR>(T0 a0, T1 a1, T2 a2, T3 a3);

Dans ces dclarations, TR fait rfrence au type de donne retourne. Cet argument est toujours le dernier de la liste. Quant T0 T3, ils reprsentent les paramtres passs la mthode. Plusieurs dclarations sont ncessaires, car tous les oprateurs de requte

Chapitre 3

Introduction LINQ to Objects

59

standard nutilisent pas le mme nombre de paramtres en entre. Dans tous les cas, les dlgus admettent un nombre maximal de 4 paramtres. Examinons un des prototypes de loprateur Where :
public static IEnumerable<T> Where<T>( this IEnumerable<T> source, Func<T, bool> predicate);

En observant le prdicat Func<T, bool>, vous pouvez en dduire que la mthode ou lexpression lambda naccepte quun seul argument, T, et retourne un boolen. Cette dernire dduction vient du fait que le type de retour est toujours le dernier paramtre de la liste. Vous utiliserez la dclaration Func, comme indiqu dans le Listing 3.6.
Listing 3.6 : Cet exemple utilise une dclaration de dlgu Func.
// Cration dun tableau dentiers int[] ints = new int[] { 1,2,3,4,5,6 }; // Dclaration du dlgu Func<int, bool> GreaterThanTwo = i => i > 2; // Mise en place (et non excution) de la requte IEnumerable<int> intsGreaterThanTwo = ints.Where(GreaterThanTwo); // Affichage des rsultats foreach(int i in intsGreaterThanTwo) Console.WriteLine(i);

Lexcution de ce code produit les rsultats suivants :


2 4 5 6

Les oprateurs de requte standard


Le Tableau 3.1 dresse la liste alphabtique des principaux oprateurs de requte standard. Les prochains chapitres vont sparer les oprateurs diffrs des oprateurs non diffrs. Ce tableau facilitera donc votre reprage dans le livre.
Tableau 3.1 : Les oprateurs de requte standard

Oprateur Aggregate All Any AsEnumerable

Objet Agrgat Dnombrement Dnombrement Conversion

Diffr

Supporte lexpression de requte

60

LINQ to Objects

Partie II

Tableau 3.1 : Les oprateurs de requte standard (suite)

Oprateur Average Cast Concat Contains Count DefaultIfEmpty Distinct ElementAt

Objet Agrgat Conversion Concatnation Dnombrement Agrgat lment Ensemble lment Gnration Ensemble lment lment Regroupement Jointure Ensemble Jointure lment lment Agrgat Agrgat Agrgat Conversion Tri Tri Gnration Gnration Tri Projection

Diffr

Supporte lexpression de requte

u u

u u

ElementAtOrDefault lment Empty Except First FirstOrDefault GroupBy GroupJoin Intersect Join Last LastOrDefault LongCount Max Min OfType OrderBy OrderByDescending Range Repeat Reverse Select

u u

u u u u

u u

u u u u u u u u u u

Chapitre 3

Introduction LINQ to Objects

61

Tableau 3.1 : Les oprateurs de requte standard (suite)

Oprateur SelectMany SequenceEqual Single SingleOrDefault Skip SkipWhile Sum Take TakeWhile ThenBy ThenByDescending ToArray ToDictionary ToList ToLookup Union Where

Objet Projection galit lment lment Partage Partage Agrgat Partage Partage Tri Tri Conversion Conversion Conversion Conversion Ensemble Restriction

Diffr

Supporte lexpression de requte

u u u u u u u u

u u u

Rsum Ce chapitre a introduit le terme "squence" et le type de donnes associ, IEnumerable<T>. Si vous ntes pas laise avec ces expressions, soyez rassur : elles deviendront vite une seconde nature pour vous ! Pour linstant, contentez-vous de voir les IEnumerable<T> comme une squence dobjets auxquels vous allez appliquer des traitements via des mthodes. Ce chapitre a mis en vidence limportance de lexcution diffre des requtes. Selon les cas, elle peut constituer un avantage ou un inconvnient. Cette caractristique est vraiment importante. Cest pourquoi nous allons sparer les oprateurs diffrs (au Chapitre 4) des oprateurs non diffrs (au Chapitre 5) dans la suite de cet ouvrage.

4
Les oprateurs diffrs
Au chapitre prcdent, nous nous sommes intresss aux squences, aux types de donnes qui les reprsentent et aux consquences de leur excution diffre. Conscient de limportance de ce dernier point, jai choisi de traiter des oprateurs diffrs et non diffrs dans deux chapitres spars. Ce chapitre va sintresser aux oprateurs diffrs, par groupes fonctionnels. Il est facile de reconnatre un tel oprateur : il retourne un IEnumerable<T> ou un IOrderEnumerable<T>. Attention, pour pouvoir excuter les exemples de ce chapitre, assurez-vous que vous avez rfrenc les espaces de noms (directive using), les assemblies et les codes communs ncessaires !

Espaces de noms rfrencs


Les exemples de ce chapitre vont utiliser les espaces de noms System.Linq, System.Collections, System.Collections.Generic et System.Data.Linq. Si elles ne sont pas dj prsentes, vous devez donc ajouter les directives using suivantes dans votre code :
using System.Linq; using System.Collections; using System.Collections.Generic; using System.Data.Linq;

Si vous parcourez le code source (disponible sur le site www.pearson.fr), vous verrez que jai galement ajout une directive using sur lespace de noms System.Diagnostic. Cette directive nest pas ncessaire si vous saisissez directement les exemples de ce chapitre. Elle nest l que pour les besoins propres du code source.

64

LINQ to Objects

Partie II

Assemblies rfrencs
Pour que le code de ce chapitre fonctionne, vous devez galement rfrencer lassembly System.Data.Linq.dll.

Classes communes
Certains exemples de ce chapitre ncessitent des classes additionnelles pour fonctionner en totalit. En voici la liste. La classe Employee permet de travailler sur les employs dune entreprise. Elle contient des mthodes statiques qui retournent un tableau demploys de type ArrayList.
public class Employee { public int id; public string firstName; public string lastName; public static ArrayList GetEmployeesArrayList() { ArrayList al = new ArrayList(); al.Add(new Employee al.Add(new Employee al.Add(new Employee al.Add(new Employee al.Add(new Employee return (al); } public static Employee[] GetEmployeesArray() { return ((Employee[])GetEmployeesArrayList().ToArray()); } } { { { { { id id id id id = = = = = 1, firstName = 2, firstName = 3, firstName = 4, firstName = 101, firstName "Joe", lastName = "Rattz" }); "William", lastName = "Gates" }); "Anders", lastName = "Hejlsberg" }); "David", lastName = "Lightman" }); = "Kevin", lastName = "Flynn" });

La classe EmployeeOptionEntry reprsente le montant des stock-options des employs. Elle contient une mthode statique qui retourne un tableau de stock-options.
public class EmployeeOptionEntry { public int id; public long optionsCount; public DateTime dateAwarded; public static EmployeeOptionEntry[] GetEmployeeOptionEntries() { EmployeeOptionEntry[] empOptions = new EmployeeOptionEntry[] { new EmployeeOptionEntry { id = 1, optionsCount = 2, dateAwarded = DateTime.Parse("1999/12/31") }, new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("1992/06/30") }, new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("1994/01/01") }, new EmployeeOptionEntry { id = 3, optionsCount = 5000, dateAwarded = DateTime.Parse("1997/09/30") },

Chapitre 4

Les oprateurs diffrs

65

new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("2003/04/01") new EmployeeOptionEntry { id = 3, optionsCount = 7500, dateAwarded = DateTime.Parse("1998/09/30") new EmployeeOptionEntry { id = 3, optionsCount = 7500, dateAwarded = DateTime.Parse("1998/09/30") new EmployeeOptionEntry { id = 4, optionsCount = 1500, dateAwarded = DateTime.Parse("1997/12/31") new EmployeeOptionEntry { id = 101, optionsCount = 2, dateAwarded = DateTime.Parse("1998/12/31") }; return (empOptions); } }

},

},

},

},

Les oprateurs diffrs, par groupes fonctionnels


Dans les pages qui suivent, nous avons organis les diffrents oprateurs de requte standard diffrs par grands groupes fonctionnels. Restriction Les oprateurs de restriction sont utiliss pour ajouter ou enlever des lments dans une squence dentre.
Loprateur Where Loprateur Where est utilis pour ltrer des lments dune squence.

Prototypes Deux prototypes de loprateur Where seront tudis dans ce livre. Premier prototype
public static IEnumerable<T> Where<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce prototype demande deux paramtres : une squence dentre et un prdicat (dlgu gnrique). Il renvoie un objet numrable dont seuls les lments pour lesquels le prdicat renvoie true sont accessibles.
INFO Comme Where est une mthode dextension, la squence dentre nest pas rellement passe dans le premier argument : tant que Where est appliqu sur un objet du mme type que le premier argument, ce dernier peut tre remplac par le mot-cl this.

66

LINQ to Objects

Partie II

Lorsque vous appelez la mthode Where, un dlgu est pass un prdicat. Cette dernire doit accepter une entre de type T (o T est le type des lments contenus dans la squence dentre) et retourner un boolen. Loprateur Where communique chacun des lments contenus dans la squence dentre au prdicat. Llment nest retourn dans la squence de sortie que dans le cas o le prdicat retourne la valeur true. Second prototype
public static IEnumerable<T> Where<T>( this IEnumerable<T> source, Func<T, int, bool> predicate);

Ce second prototype est semblable au premier si ce nest que le prdicat reoit un argument complmentaire entier. Cet argument correspond lindex de llment dans la squence. Il commence zro et se termine au nombre dlments de la squence moins un. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.1 est un exemple dappel du premier prototype Where.
Listing 4.1 : Un exemple dappel du premier prototype Where.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> sequence = presidents.Where(p => p.StartsWith("J")); foreach (string s in sequence) Console.WriteLine("{0}", s);

Cet exemple applique la mthode Where la squence dentre et dnit une expression lambda. Cette dernire retourne un boolen dont la valeur indique si llment doit ou ne doit pas tre inclus dans la squence de sortie. Dans cet exemple, seuls les lments qui commencent par la lettre "J" seront retourns. Voici les rsultats afchs dans la console lorsque vous appuyez sur Ctrl+F5 :
Jackson Jefferson Johnson

Le Listing 4.2 est un exemple dappel du second prototype Where. Ce code se contente dutiliser lindex i pour ltrer les lments de la squence. Seuls les lments dindice impair seront retourns.

Chapitre 4

Les oprateurs diffrs

67

Listing 4.2 : Un exemple dappel du second prototype Where.


string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> sequence = presidents.Where((p, i) => (i & 1) == 1); foreach (string s in sequence) Console.WriteLine("{0}", s);

Lexcution de ce code produit la sortie suivante dans la console :


Arthur Bush Cleveland Coolidge Fillmore Garfield Harding Hayes Jackson Johnson Lincoln McKinley Nixon Polk Roosevelt Taylor Tyler Washington

Projection Les oprateurs de projection retournent une squence dlments slectionns dans la squence dentre ou instancis partir de portions dlments de la squence dentre. Le type des lments de la squence de sortie peut tre diffrent du type des lments de la squence dentre.
Loprateur Select Loprateur Select est utilis pour crer une squence de sortie S dun type dlment en partant dune squence dentre T dun autre type dlment. Ces deux types ne sont pas forcment identiques.

Prototypes Deux prototypes de loprateur Select seront tudis dans ce livre.

68

LINQ to Objects

Partie II

Premier prototype
public static IEnumerable<S> Select<T, S>( this IEnumerable<T> source, Func<T, S> selector);

Ce prototype admet deux arguments en entre : une squence source et un dlgu. Il retourne un objet dont lnumration produit une squence dlments de type S. Comme signal prcdemment, les types T et S ne sont pas forcment identiques. Pour utiliser ce prototype, vous devez passer un dlgu une mthode de slection via largument selector. Ce dernier doit accepter un lment de type T (o T est le type des lments contenus dans la squence dentre) et retourner un lment de type S. Loprateur Select appelle la mthode selector pour chacun des lments de la squence dentre. La mthode selector choisit une portion de llment pass, cre un nouvel lment, ventuellement dun autre type (y compris le type anonyme) et le retourne. Second prototype
public static IEnumerable<S> Select<T, S>( this IEnumerable<T> source, Func<T, int, S> selector);

Ce second prototype est semblable au premier si ce nest quun argument complmentaire de type entier est pass au dlgu. Cet argument correspond lindex de llment dans la squence (lindex du premier lment est 0). Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.3 est un exemple dappel du premier prototype.
Listing 4.3 : Un exemple dutilisation du premier prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<int> nameLengths = presidents.Select(p => p.Length); foreach (int item in nameLengths) Console.WriteLine(item);

La mthode selector est passe par lintermdiaire dune expression lambda. Cette dernire retourne la longueur des lments de la squence dentre. Remarquez que les types des squences dentre et de sortie diffrent : string pour la premire, integer pour la deuxime.

Chapitre 4

Les oprateurs diffrs

69

Voici le rsultat de ce code lorsque vous appuyez sur Ctrl+F5 :


5 6 8 4 6 9 7 8 10 8 4 8 5 7 8 5 6 7 9 7 7 7 7 8 6 5 6 4 6 9 4 6 6 5 9 10 6

Cet exemple est trs simple, puisquil ne gnre aucune classe. Le Listing 4.4 donne un exemple plus labor du premier prototype de loprateur Select.
Listing 4.4 : Un autre exemple dutilisation du premier prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; var nameObjs = presidents.Select(p => new { p, p.Length }); foreach (var item in nameObjs) Console.WriteLine(item);

Ici, lexpression lambda instancie un nouveau type anonyme. Le compilateur gnre dynamiquement un objet de type anonyme qui contient un string p et un int

70

LINQ to Objects

Partie II

p.Length, et la mthode selector retourne cet objet. tant donn que llment retourn est de type anonyme, il nexiste aucun type pour y faire rfrence. Contrairement lexemple prcdent, o la squence de sortie avait t affecte un IEnumerable<int>, il est impossible daffecter la squence de sortie un IEnumerable dun type connu. Cest la raison pour laquelle le mot-cl var a t utilis.
INFO Les oprateurs de projection dont les mthodes selector instancient des types anonymes doivent affecter leur squence de sortie une variable dclare avec le mot-cl var.

Voici la sortie dans la console lorsque vous appuyez sur Ctrl+F5 :


{ { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p p = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = Adams, Length = 5 } Arthur, Length = 6 } Buchanan, Length = 8 } Bush, Length = 4 } Carter, Length = 6 } Cleveland, Length = 9 } Clinton, Length = 7 } Coolidge, Length = 8 } Eisenhower, Length = 10 } Fillmore, Length = 8 } Ford, Length = 4 } Garfield, Length = 8 } Grant, Length = 5 } Harding, Length = 7 } Harrison, Length = 8 } Hayes, Length = 5 } Hoover, Length = 6 } Jackson, Length = 7 } Jefferson, Length = 9 } Johnson, Length = 7 } Kennedy, Length = 7 } Lincoln, Length = 7 } Madison, Length = 7 } McKinley, Length = 8 } Monroe, Length = 6 } Nixon, Length = 5 } Pierce, Length = 6 } Polk, Length = 4 } Reagan, Length = 6 } Roosevelt, Length = 9 } Taft, Length = 4 } Taylor, Length = 6 } Truman, Length = 6 } Tyler, Length = 5 } Van Buren, Length = 9 } Washington, Length = 10 } Wilson, Length = 6 }

Dans son tat actuel, ce code a un inconvnient : il ne permet pas dagir sur les membres de la classe anonyme gnre dynamiquement. Cependant, grce la fonctionnalit dinitialisation dobjets de C# 3.0, il est possible de spcier les noms des membres de la classe anonyme dans une expression lambda (voir Listing 4.5).

Chapitre 4

Les oprateurs diffrs

71

Listing 4.5 : Un troisime exemple du premier prototype de loprateur Select.


string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; var nameObjs = presidents.Select(p => new { LastName = p, Length = p.Length }); foreach (var item in nameObjs) Console.WriteLine("{0} contient {1} caractres", item.LastName, item.Length);

Comme vous pouvez le voir, le nom des membres a t spci dans lexpression lambda, et on a accd aux membres par leurs noms dans la mthode Console.WriteLine. Voici le rsultat de ce code :
Adams contient 5 caractres Arthur contient 6 caractres Buchanan contient 8 caractres Bush contient 4 caractres Carter contient 6 caractres Cleveland contient 9 caractres Clinton contient 7 caractres Coolidge contient 8 caractres Eisenhower contient 10 caractres Fillmore contient 8 caractres Ford contient 4 caractres Garfield contient 8 caractres Grant contient 5 caractres Harding contient 7 caractres Harrison contient 8 caractres Hayes contient 5 caractres Hoover contient 6 caractres Jackson contient 7 caractres Jefferson contient 9 caractres Johnson contient 7 caractres Kennedy contient 7 caractres Lincoln contient 7 caractres Madcontienton contient 7 caractres McKinley contient 8 caractres Monroe contient 6 caractres Nixon contient 5 caractres Pierce contient 6 caractres Polk contient 4 caractres Reagan contient 6 caractres Roosevelt contient 9 caractres Taft contient 4 caractres Taylor contient 6 caractres Truman contient 6 caractres Tyler contient 5 caractres Van Buren contient 9 caractres Washington contient 10 caractres Wilson contient 6 caractres

Pour illustrer le second prototype, nous allons insrer lindex pass la mthode selector dans la squence de sortie (voir Listing 4.6).

72

LINQ to Objects

Partie II

Listing 4.6 : Un exemple dutilisation du second prototype de loprateur Select.


string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; var nameObjs = presidents.Select((p, i) => new { Index = i, LastName = p }); foreach (var item in nameObjs) Console.WriteLine("{0}. {1}", item.Index + 1, item.LastName);

Pour chaque lment de la squence dentre, cet exemple afche la valeur de lindex augmente de 1, puis le nom de llment. Voici les rsultats afchs dans la console :
1. Adams 2. Arthur 3. Buchanan 4. Bush 5. Carter 34. Tyler 35. Van Buren 36. Washington 37. Wilson

Oprateur SelectMany Loprateur SelectMany est utilis pour crer une ou plusieurs squences partir de la squence passe en entre. Contrairement loprateur Select, qui retourne un lment en sortie pour chaque lment en entre, SelectMany peut retourner zro, un ou plusieurs lments en sortie pour chaque lment en entre.

Prototypes Deux prototypes de loprateur Select seront tudis dans ce livre. Premier prototype
public static IEnumerable<S> SelectMany<T, S>( this IEnumerable<T> source, Func<T, IEnumerable<S>> selector);

Ce prototype admet deux entres : une squence source dlments de type T et un dlgu pour effectuer la slection des donnes. Il retourne un objet dont lnumration passe chaque lment de la squence dentre au dlgu. Lors de lnumration de la mthode selector, zro, un ou plusieurs lments de type S sont retourns dans une squence de sortie intermdiaire. Loprateur SelectMany retourne les diffrentes squences de sortie concatnes.

Chapitre 4

Les oprateurs diffrs

73

Second prototype
public static IEnumerable<S> SelectMany<T, S>( this IEnumerable<T> source, Func<T, int, IEnumerable<S>> selector);

Ce prototype est en tout point semblable au prcdent, si ce nest quun index des lments de la squence dentre est pass la mthode selector (lindex du premier lment est 0). Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.7 donne un exemple dappel du premier prototype.
Listing 4.7 : Un exemple du premier prototype de loprateur SelectMany.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<char> chars = presidents.SelectMany(p => p.ToArray()); foreach (char ch in chars) Console.WriteLine(ch);

Dans cet exemple, la mthode de slection reoit un paramtre string. En lui appliquant la mthode ToArray, on obtient un tableau de chanes qui est transform en une chane de sortie de type char. Pour une unique squence en entre (ici, un string), le slecteur retourne une squence de caractres. Loprateur SelectMany concatne toutes ces squences de caractres dans une seule qui devient la squence de sortie. Voici le texte afch dans la console suite lexcution du code :
A d a m s A r t h u r

74

LINQ to Objects

Partie II

B u c h a n a nB u s h W a s h i n g t o n W i l s o n

Cette requte est simple comprendre, mais pas trs dmonstrative de la faon dont loprateur SelectMany est gnralement utilis. Dans le prochain exemple, nous utiliserons les classes communes Employee et EmployeeOptionEntry pour tre plus proches de la ralit. Loprateur SelectMany va tre appliqu sur un tableau dlments Employee. Pour chacun de ces lments, la mthode de slection (le dlgu) retournera zro, un ou plusieurs lments de la classe anonyme. Ces lments contiendront les champs id et optionsCount du tableau dlments EmployeeOptionEntry de lobjet Employee (voir Listing 4.8).
Listing 4.8 : Un exemple plus complexe du premier prototype de loprateur SelectMany.
Employee[] employees = Employee.GetEmployeesArray(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); var employeeOptions = employees .SelectMany(e => empOptions .Where(eo => eo.id == e.id) .Select(eo => new { id = eo.id, optionsCount = eo.optionsCount })); foreach (var item in employeeOptions) Console.WriteLine(item);

Chaque employ du tableau Employee est pass dans lexpression lambda utilise dans loprateur SelectMany. Par lintermdiaire de loprateur Where, lexpression lambda

Chapitre 4

Les oprateurs diffrs

75

retrouve alors les lments EmployeeOptionEntry dont le champ id correspond au champ id de lemploy actuel. Ce code effectue donc une jointure des tableaux Employee et EmployeeOptionEntry sur le champ id. Loprateur Select de lexpression lambda cre alors un objet anonyme compos des membres id et optionsCount pour chacun des enregistrements slectionns dans le tableau EmployeeOptionEntry. Lexpression lambda retourne donc une squence de zro, un ou plusieurs objets anonymes pour chacun des employs slectionns. Le rsultat nal est une squence de squences concatnes par loprateur SelectMany. Voici le rsultat de ce code, afch dans la console :
{ { { { { { { { { id id id id id id id id id = = = = = = = = = 1, optionsCount = 2, optionsCount = 2, optionsCount = 2, optionsCount = 3, optionsCount = 3, optionsCount = 3, optionsCount = 4, optionsCount = 101, optionsCount 2 } 10000 } 10000 } 10000 } 5000 } 7500 } 7500 } 1500 } = 2 }

Bien quun peu tir par les cheveux, le Listing 4.9 donne un exemple dappel du second prototype de loprateur SelectMany.
Listing 4.9 : Un exemple dappel du second prototype de loprateur SelectMany.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<char> chars = presidents .SelectMany((p, i) => i < 5? p.ToArray(): new char[] { }); foreach (char ch in chars) Console.WriteLine(ch);

Lexpression lambda teste la valeur de lindex. Sil est infrieur 5, le tableau de caractres de la chane passe en entre est retourn. Voici le rsultat afch dans la console :
A d a m s A r t h u r

76

LINQ to Objects

Partie II

B u c h a n a n B u s h C a r t e r

Cette expression lambda nest pas particulirement efcace, en particulier si le nombre dlments en entre est lev. Elle est en effet appele pour chacun des lments passs en entre, y compris pour ceux dont lindex est suprieur 5. Dans ce cas, un tableau vide est retourn. Pour une plus grande efcacit, vous prfrerez loprateur Take (voir la section suivante). Loprateur SelectMany peut galement tre utilis lorsquil sagit de concatner plusieurs squences. Reportez-vous la section relative loprateur Concat, un peu plus loin dans ce chapitre, pour avoir un exemple de concatnation. Partage Les oprateurs de partage retournent une squence qui est un sous-ensemble de la squence dentre.
Oprateur Take Loprateur Take retourne un certain nombre dlments de la squence dentre, partir du premier.

Prototype Un seul prototype de loprateur Take sera tudi dans ce livre :


public static IEnumerable<T> Take<T>( this IEnumerable<T> source, int count);

Loprateur Take admet deux paramtres en entre : une squence source et lentier count, qui indique combien dlments doivent tre retourns. Il renvoie un objet dont lnumration produira les count premiers lments de la squence dentre. Si count est plus grand que le nombre dlments contenus dans la squence dentre, la totalit de la squence dentre est retourne.

Chapitre 4

Les oprateurs diffrs

77

Exceptions Lexception ArgumentNullException est leve si la squence source a pour valeur null. Exemples Le Listing 4.10 donne un exemple dappel de loprateur Take.
Listing 4.10 : Un exemple dappel de lunique prototype Take.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Take(5); foreach (string item in items) Console.WriteLine(item);

Ce code retourne les cinq premiers lments du tableau presidents :


Adams Arthur Buchanan Bush Carter

Dans lexemple prcdent, jai indiqu que le code serait plus efcace si loprateur Take tait utilis pour limiter le nombre dentres soumises lexpression lambda. Le code auquel je faisais rfrence se trouve dans le Listing 4.11.
Listing 4.11 : Un autre exemple dappel du prototype Take.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<char> chars = presidents.Take(5).SelectMany(s => s.ToArray()); foreach (char ch in chars) Console.WriteLine(ch);

La sortie console est identique celle du Listing 4.9 :


A d a m s

78

LINQ to Objects

Partie II

A r t h u r B u c h a n a n B u s h C a r t e r

Contrairement au Listing 4.9, seuls les cinq premiers lments sont passs en entre de loprateur SelectMany. Cette technique est bien plus efcace, en particulier si de nombreux lments ne doivent pas tre passs SelectMany.
Loprateur TakeWhile Loprateur TakeWhile renvoie les lments de la squence dentre, en commenant par le premier, tant quune condition est vrie. Les lments restants sont ignors.

Prototypes Deux prototypes de loprateur TakeWhile seront tudis dans ce livre. Premier prototype
public static IEnumerable<T> TakeWhile<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Dans ce prototype, loprateur TakeWhile admet deux paramtres en entre : une squence source et un prdicat. Il retourne un objet dont lnumration fournit des lments jusqu ce que le prdicat renvoie la valeur false. Les lments suivants ne sont pas traits. Second prototype
public static IEnumerable<T> TakeWhile<T>( this IEnumerable<T> source, Func<T, int, bool> predicate);

Ce second prototype est semblable au premier si ce nest que le prdicat reoit galement un entier qui correspond lindex de llment dans la squence source.

Chapitre 4

Les oprateurs diffrs

79

Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.12 donne un exemple dappel du premier prototype.
Listing 4.12 : Un exemple dappel du premier prototype de loprateur TakeWhile.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.TakeWhile(s => s.Length < 10); foreach (string item in items) Console.WriteLine(item);

Seuls les lments contenant dix caractres au maximum sont retourns :


Adams Arthur Buchanan Bush Carter Cleveland Clinton Coolidge

Lnumration sest arrte sur le nom Eisenhower, long de 10 caractres. Voici maintenant un exemple dappel du second prototype de loprateur TakeWhile.
Listing 4.13 : Un exemple dappel du second prototype TakeWhile.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents .TakeWhile((s, i) => s.Length < 10 && i < 5); foreach (string item in items) Console.WriteLine(item);

80

LINQ to Objects

Partie II

Cet exemple arrte lnumration lorsquun lment en entre a une longueur suprieure 9 caractres ou lorsque la sixime entre est atteinte. Voici le rsultat :
Adams Arthur Buchanan Bush Carter

Ici, lnumration sest arrte lorsque la sixime entre a t atteinte.


Oprateur Skip Loprateur Skip saute un certain nombre dlments dans la squence dentre et retourne les suivants.

Prototype Un seul prototype de loprateur Skip sera tudi dans ce livre :


public static IEnumerable<T> Skip<T>( this IEnumerable<T> source, int count);

Loprateur Skip admet deux paramtres : une squence source et lentier count, qui indique le nombre dlments sauter. Ce prototype renvoie un objet dont lnumration exclut les count premiers lments. Si la valeur de count est suprieure au nombre dlments de la squence dentre, cette dernire ne sera pas numre et la squence de sortie sera vide. Exceptions Lexception ArgumentNullException est leve si la squence dentre a pour valeur null. Exemples Le Listing 4.14 est un exemple dappel du prototype Skip.
Listing 4.14 : Un exemple dutilisation du prototype de loprateur Skip.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Skip(1); foreach (string item in items) Console.WriteLine(item);

Chapitre 4

Les oprateurs diffrs

81

Dans cet exemple, seul le premier lment est ignor. Tous les lments suivants sont donc renvoys par loprateur Skip :
Arthur Buchanan Bush Van Buren Washington Wilson

Oprateur SkipWhile Loprateur SkipWhile ignore les lments de la squence dentre tant quune condition est vrie. Les lments suivants sont alors renvoys dans la squence de sortie.

Prototypes Deux prototypes de loprateur SkipWhile seront tudis dans ce livre. Premier prototype
public static IEnumerable<T> SkipWhile<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce premier prototype admet deux paramtres : une squence source et un prdicat. Il renvoie un objet dont lnumration exclut les lments de la squence dentre tant que le prdicat retourne la valeur true. Ds quune valeur false est retourne, tous les lments suivants sont envoys dans la squence de sortie. Second prototype
public static IEnumerable<T> SkipWhile<T>( this IEnumerable<T> source, Func<T, int, bool> predicate);

Ce second prototype est semblable au premier si ce nest que le prdicat reoit galement un entier qui correspond lindex de llment dans la squence source. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.15 donne un exemple dappel du premier prototype de loprateur SkipWhile.
Listing 4.15 : Un exemple dappel du premier prototype de loprateur SkipWhile.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",

82

LINQ to Objects

Partie II

"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.SkipWhile(s => s.StartsWith("A")); foreach (string item in items) Console.WriteLine(item);

Dans cet exemple, tous les lments qui commencent par la lettre A sont ignors. Les lments suivants sont passs la squence de sortie :
Buchanan Bush Carter Van Buren Washington Wilson

Le Listing 4.16 donne un exemple dutilisation du second prototype de loprateur SkipWhile.


Listing 4.16 : Un exemple dutilisation du second prototype de loprateur SkipWhile.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents .SkipWhile((s, i) => s.Length > 4 && i < 10); foreach (string item in items) Console.WriteLine(item);

Dans cet exemple, tous les lments dont la longueur est infrieure ou gale 4 caractres ou suprieure ou gale 10 caractres sont ignors. Les lments suivants constituent la squence de sortie :
Bush Carter Cleveland Van Buren Washington Wilson

Llment Bush compte 4 caractres. Il a donc mis n au SkipWhile.

Chapitre 4

Les oprateurs diffrs

83

Concatnation Les oprateurs de concatnation accolent plusieurs squences dentre dans la squence de sortie.
Oprateur Concat Loprateur Concat accole deux squences dentre dans la squence de sortie.

Prototype Un seul prototype de loprateur Concat sera tudi dans ce livre :


public static IEnumerable<T> Concat<T>( this IEnumerable<T> first, IEnumerable<T> second);

Deux squences de mme type T sont fournies en entre de ce prototype : first et second. Lnumration de lobjet retourn renvoie tous les lments de la premire squence dentre suivis de tous les lments de la seconde squence dentre. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.17 donne un exemple dutilisation des oprateurs Concat, Take et Skip.
Listing 4.17 : Un exemple dutilisation du prototype de loprateur Concat.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Take(5).Concat(presidents.Skip(5)); foreach (string item in items) Console.WriteLine(item);

Ce code concatne les cinq premiers lments de la squence dentre presidents aux lments de cette mme squence dentre, en excluant les cinq premiers. Le rsultat contient donc tous les lments de la squence dentre :
Adams Arthur Buchanan Bush Carter Cleveland Clinton

84

LINQ to Objects

Partie II

Coolidge Eisenhower Fillmore Ford Garfield Grant Harding Harrison Hayes Hoover Jackson Jefferson Johnson Kennedy Lincoln Madison McKinley Monroe Nixon Pierce Polk Reagan Roosevelt Taft Taylor Truman Tyler Van Buren Washington Wilson

Pour effectuer une concatnation, vous pouvez galement utiliser loprateur SelectMany (voir Listing 4.18).
Listing 4.18 : Un exemple effectuant une concatnation sans loprateur Concat.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = new[] { presidents.Take(5), presidents.Skip(5) } .SelectMany(s => s); foreach (string item in items) Console.WriteLine(item);

Le tableau item a t instanci par lintermdiaire de deux squences : une cre avec loprateur Take et une autre, avec loprateur Skip. Cet exemple est comparable au prcdent mais, ici, on fait appel loprateur SelectMany.

Chapitre 4

Les oprateurs diffrs

85

ASTUCE Si vous devez concatner plusieurs squences, vous utiliserez loprateur SelectMany. Loprateur Concat, quant lui, est limit la concatnation de deux squences.

Voici le rsultat afch dans la console :


Adams Arthur Buchanan Bush Carter Cleveland Clinton Coolidge Eisenhower Fillmore Ford Garfield Grant Harding Harrison Hayes Hoover Jackson Jefferson Johnson Kennedy Lincoln Madison McKinley Monroe Nixon Pierce Polk Reagan Roosevelt Taft Taylor Truman Tyler Van Buren Washington Wilson

Tri Les oprateurs de tri permettent de classer des squences. Les oprateurs OrderBy et OrderByDescending ncessitent tous deux une squence dentre de type IEnumerable<T> et retournent une squence de type IOrderedEnumerable<T>. Il est impossible de passer un IOrderedEnumerable<T> en entre des oprateurs OrderBy et OrderByDescending. Tout chanage est donc impossible. Si vous avez besoin de trier conjointement plusieurs lments, utilisez les oprateurs ThenBy ou ThenByDescending. Ces oprateurs peuvent tre chans car ils admettent et retournent des IOrderedEnumerable<T>.

86

LINQ to Objects

Partie II

titre dexemple, cet appel nest pas valide :


inputSequence.OrderBy(s => s.LastName).OrderBy(s => s.FirstName)

Pour effectuer ce traitement, vous utiliserez la syntaxe suivante :


inputSequence.OrderBy(s => s.LastName).ThenBy(s => s.FirstName)

Loprateur OrderBy Loprateur OrderBy trie une squence dentre en utilisant la mthode keySelector. Cette mthode retourne une valeur cl pour chaque lment en entre et une squence de sortie de type IOrderedEnumerable<T>. Dans cette dernire, les lments seront classs dans un ordre croissant, en se basant sur les valeurs cls retournes.

Le tri effectu par loprateur OrderBy est connu pour tre "instable" : si deux lments ayant la mme valeur cl sont passs OrderBy, leur ordre initial peut aussi bien tre maintenu quinvers. Vous ne devez donc jamais vous er lordre des lments issus de ces oprateurs OrderBy et OrderByDescending pour les champs qui ne sont pas spcis dans la mthode.
ATTENTION Le tri effectu par les oprateurs OrderBy et OrderByDescending est "instable".

Prototypes Deux prototypes de loprateur OrderBy seront tudis dans ce livre. Premier prototype
public static IOrderedEnumerable<T> OrderBy<T, K>( this IEnumerable<T> source, Func<T, K> keySelector) where K: IComparable<K>;

Ce prototype admet deux entres : une squence source et le dlgu keySelector. Lnumration de lobjet retourn passe tous les lments de la squence dentre la mthode KeySelector an dobtenir leurs cls et de procder leur tri. La mthode KeySelector se voit passer un lment de type T. Elle retourne la valeur cl de type K. Les types T et K peuvent tre similaires ou diffrents. En revanche, le type de la valeur retourne par la mthode KeySelector doit implmenter linterface IComparable. Second prototype
public static IOrderedEnumerable<T> OrderBy<T, K>( this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer);

Ce prototype est le mme que le prcdent, si ce nest quun objet comparer complmentaire lui est pass. Si vous utilisez cette version de loprateur OrderBy, le type K nest pas forc dimplmenter linterface IComparable.

Chapitre 4

Les oprateurs diffrs

87

Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le Listing 4.19 est un exemple dutilisation du premier prototype.
Listing 4.19 : Un exemple du premier prototype de loprateur OrderBy.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.OrderBy(s => s.Length); foreach (string item in items) Console.WriteLine(item);

Cet exemple classe les prsidents par la longueur de leurs noms. Voici les rsultats :
Bush Ford Polk Taft Adams Grant Hayes Nixon Tyler Arthur Carter Hoover Monroe Pierce Reagan Taylor Truman Wilson Clinton Harding Jackson Johnson Kennedy Lincoln Madison Buchanan Coolidge Fillmore Garfield Harrison McKinley Cleveland Jefferson Roosevelt Van Buren Eisenhower Washington

88

LINQ to Objects

Partie II

Nous allons maintenant donner un exemple dutilisation du deuxime prototype. Mais, auparavant, prenons quelques instants pour examiner linterface IComparer :
interface IComparer<T> { int Compare(T x, T y); }

Cette interface utilise la mthode Compare. Cette dernire admet deux arguments de type T en entre et retourne une valeur int. Sa valeur est :
m m m

ngative si le premier argument est infrieur au second ; nulle si les deux arguments sont gaux ; positive si le second argument est suprieur au premier.

Remarquez quel point les gnriques de C# 2.0 sont utiles dans cette interface et ce prototype. Pour faire fonctionner cet exemple, une classe spcique qui implmente linterface IComparer a t cre. Cette classe rarrangera les lments par rapport leur ratio nombre de voyelles/nombre de consonnes. Implmentation de linterface IComparer pour illustrer le second prototype OrderBy
public class MyVowelToConsonantRatioComparer: IComparer<string> { public int Compare(string s1, string s2) { int vCount1 = 0; int cCount1 = 0; int vCount2 = 0; int cCount2 = 0; GetVowelConsonantCount(s1, ref vCount1, ref cCount1); GetVowelConsonantCount(s2, ref vCount2, ref cCount2); double dRatio1 = (double)vCount1/(double)cCount1; double dRatio2 = (double)vCount2/(double)cCount2; if(dRatio1 < dRatio2) return(-1); else if (dRatio1 > dRatio2) return(1); else return(0); } // Cette mthode est publique. Le code qui utilise ce comparateur // pourra donc y accder si cela est ncessaire public void GetVowelConsonantCount(string s, ref int vowelCount, ref int consonantCount) { string vowels = "AEIOUY"; // Initialize the counts. vowelCount = 0; consonantCount = 0;

Chapitre 4

Les oprateurs diffrs

89

// Conversion en majuscules pour ne pas tre sensible la casse string sUpper = s.ToUpper(); foreach(char ch in sUpper) { if(vowels.IndexOf(ch) < 0) consonantCount++; else vowelCount++; } return; } }

Cette classe contient deux mthodes : Compare et GetVowelConsonantCount. La mthode Compare est ncessaire pour linterface IComparer. La mthode GetConsonantVowelCount calcule le nombre de voyelles et de consonnes de la chane qui lui est passe. Par son intermdiaire, il est ainsi possible dobtenir les valeurs afcher lors de lnumration de la squence rordonne. La logique utilise lintrieur de la mthode na pas dimportance. Il est en effet peu probable que vous ayez un jour classer des donnes en tenant compte de leur ratio nombre de voyelles/nombre de consonnes, et encore moins de comparer deux chanes selon ce ratio. Ce qui est important, en revanche, cest la technique qui a permis de crer une classe qui implmente linterface IComparer en implmentant la mthode Compare. Pour cela, examinez le bloc if else la n de la mthode Compare. Comme vous le voyez, les valeurs retournes sont -1, 1 ou 0, ce qui assure la compatibilit avec linterface IComparer. Le Listing 4.20 donne un exemple dappel du code.
Listing 4.20 : Un exemple dappel du second prototype OrderBy.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; MyVowelToConsonantRatioComparer myComp = new MyVowelToConsonantRatioComparer(); IEnumerable<string> namesByVToCRatio = presidents .OrderBy((s => s), myComp); foreach (string item in namesByVToCRatio) { int vCount = 0; int cCount = 0; myComp.GetVowelConsonantCount(item, ref vCount, ref cCount); double dRatio = (double)vCount / (double)cCount; Console.WriteLine(item + " - " + dRatio + " - " + vCount + ":" + cCount); }

90

LINQ to Objects

Partie II

Lobjet mycomp a t instanci avant dappeler loprateur OrderBy. Une rfrence est donc cre, et il est possible de lutiliser dans la boucle foreach. Voici les rsultats de ce code :
Grant - 0.25 - 1:4 Bush - 0.333333333333333 - 1:3 Ford - 0.333333333333333 - 1:3 Polk - 0.333333333333333 - 1:3 Taft - 0.333333333333333 - 1:3 Clinton - 0.4 - 2:5 Harding - 0.4 - 2:5 Jackson - 0.4 - 2:5 Johnson - 0.4 - 2:5 Lincoln - 0.4 - 2:5 Washington - 0.428571428571429 - 3:7 Arthur - 0.5 - 2:4 Carter - 0.5 - 2:4 Cleveland - 0.5 - 3:6 Jefferson - 0.5 - 3:6 Truman - 0.5 - 2:4 Van Buren - 0.5 - 3:6 Wilson - 0.5 - 2:4 Buchanan - 0.6 - 3:5 Fillmore - 0.6 - 3:5 Garfield - 0.6 - 3:5 Harrison - 0.6 - 3:5 McKinley - 0.6 - 3:5 Adams - 0.666666666666667 - 2:3 Nixon - 0.666666666666667 - 2:3 Tyler - 0.666666666666667 - 2:3 Kennedy - 0.75 - 3:4 Madison - 0.75 - 3:4 Roosevelt - 0.8 - 4:5 Coolidge - 1 - 4:4 Eisenhower - 1 - 5:5 Hoover - 1 - 3:3 Monroe - 1 - 3:3 Pierce - 1 - 3:3 Reagan - 1 - 3:3 Taylor - 1 - 3:3 Hayes - 1.5 - 3:2

Les prsidents sont classs par ratio voyelle/consonne croissant.


Loprateur OrderByDescending Cet oprateur a les mmes prototypes et comportement que OrderBy, except que les lments sont classs dans un ordre dcroissant.

Prototypes Deux prototypes de loprateur OrderByDescending seront tudis dans ce livre. Premier prototype
public static IOrderedEnumerable<T> OrderByDescending<T, K>( this IEnumerable<T> source, Func<T, K> keySelector) where K: IComparable<K>;

Chapitre 4

Les oprateurs diffrs

91

ATTENTION Le tri effectu par les oprateurs OrderBy et OrderByDescending est "instable".

Second prototype
public static IOrderedEnumerable<T> OrderByDescending<T, K>( this IEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer);

Ce prototype est le mme que le prcdent, si ce nest quun objet comparer complmentaire lui est pass. Si vous utilisez cette version de loprateur OrderByDescending, le type K nest pas forc dimplmenter linterface IComparable. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Dans lexemple du Listing 4.21, nous allons classer les prsidents des tats-Unis en utilisant un ordre inverse alphabtique sur leurs noms.
Listing 4.21 : Un exemple dutilisation du premier prototype dOrderDescending.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.OrderByDescending(s => s); foreach (string item in items) Console.WriteLine(item);

Les prsidents sont bien classs en utilisant un ordre inverse alphabtique sur leurs noms.
Wilson Washington Van Buren Tyler Truman Taylor Taft Roosevelt Reagan Polk Pierce Nixon Monroe McKinley Madison Lincoln

92

LINQ to Objects

Partie II

Kennedy Johnson Jefferson Jackson Hoover Hayes Harrison Harding Grant Garfield Ford Fillmore Eisenhower Coolidge Clinton Cleveland Carter Bush Buchanan Arthur Adams

Nous allons maintenant donner un exemple dappel du second prototype d OrderByDescending. Nous utiliserons le mme code (y compris au niveau du comparateur MyVowelToConsonantRatioComparer) que dans la section relative loprateur OrderBy. Mais, ici, cest loprateur OrderByDescending qui sera appel (voir Listing 4.22).
Listing 4.22 : Un exemple dappel du second prototype dOrderByDescending.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; MyVowelToConsonantRatioComparer myComp = new MyVowelToConsonantRatioComparer(); IEnumerable<string> namesByVToCRatio = presidents .OrderByDescending((s => s), myComp); foreach (string item in namesByVToCRatio) { int vCount = 0; int cCount = 0; myComp.GetVowelConsonantCount(item, ref vCount, ref cCount); double dRatio = (double)vCount / (double)cCount; Console.WriteLine(item + " - " + dRatio + " - " + vCount + ":" + cCount); }

Voici les rsultats de cet exemple :


Hayes - 1.5 - 3:2 Coolidge - 1 - 4:4 Eisenhower - 1 - 5:5

Chapitre 4

Les oprateurs diffrs

93

Hoover - 1 - 3:3 Monroe - 1 - 3:3 Pierce - 1 - 3:3 Reagan - 1 - 3:3 Taylor - 1 - 3:3 Roosevelt - 0.8 - 4:5 Kennedy - 0.75 - 3:4 Madison - 0.75 - 3:4 Adams - 0.666666666666667 - 2:3 Nixon - 0.666666666666667 - 2:3 Tyler - 0.666666666666667 - 2:3 Buchanan - 0.6 - 3:5 Fillmore - 0.6 - 3:5 Garfield - 0.6 - 3:5 Harrison - 0.6 - 3:5 McKinley - 0.6 - 3:5 Arthur - 0.5 - 2:4 Carter - 0.5 - 2:4 Cleveland - 0.5 - 3:6 Jefferson - 0.5 - 3:6 Truman - 0.5 - 2:4 Van Buren - 0.5 - 3:6 Wilson - 0.5 - 2:4 Washington - 0.428571428571429 - 3:7 Clinton - 0.4 - 2:5 Harding - 0.4 - 2:5 Jackson - 0.4 - 2:5 Johnson - 0.4 - 2:5 Lincoln - 0.4 - 2:5 Bush - 0.333333333333333 - 1:3 Ford - 0.333333333333333 - 1:3 Polk - 0.333333333333333 - 1:3 Taft - 0.333333333333333 - 1:3 Grant - 0.25 - 1:4

Ces rsultats sont les mmes que dans lexemple de la section prcdente mais, ici, le classement a t effectu du plus grand au plus petit ratio voyelles/consonnes.
Oprateur ThenBy Loprateur ThenBy trie une squence de type IOrderedEnumerable<T> en se basant sur une mthode keySelector qui lui retourne une valeur cl. Il renvoie une squence de sortie de type IOrderedEnumerable<T>.
INFO Les oprateurs ThenBy et ThenByDescending demandent tous deux un paramtre dont le type est inhabituel : IOrderedEnumerable<T>. Loprateur OrderBy ou OrderByDescending doit tre appel en premier lieu pour crer un objet IOrderedEnumerable.

INFO Contrairement aux oprateurs OrderBy et OrderByDescending, ThenBy et ThenByDescending sont stables. Ils prservent donc lordre original des lments qui possdent la mme cl.

94

LINQ to Objects

Partie II

Prototypes Deux prototypes de loprateur ThenBy seront tudis dans ce livre. Premier prototype
public static IOrderedEnumerable<T> ThenBy<T, K>( this IOrderedEnumerable<T> source, Func<T, K> keySelector) where K: IComparable<K>;

Dans ce prototype, loprateur ThenBy reoit une squence dentre de type IOrderedEnumerable<T> et un dlgu keySelector. Ce dernier se voit passer llment dentre de type T et retourne le champ de type K de cet lment qui sera utilis comme valeur cl. Les types T et K peuvent tre identiques ou diffrents. La valeur retourne par la mthode KeySelector doit implmenter linterface ICompare. Loprateur ThenBy classe la squence dentre par ordre croissant selon la cl retourne par keySelector. Second prototype
public static IOrderedEnumerable<T> ThenBy<T, K>( this IOrderedEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer);

Ce prototype est identique au prcdent, si ce nest quun objet comparer complmentaire lui est pass. Si vous utilisez cette version de loprateur ThenBy, le type K nest pas forc dimplmenter linterface IComparable. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null (voir Listing 4.23). Exemples
Listing 4.23 : Un exemple dappel du premier prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.OrderBy(s => s.Length).ThenBy(s => s); foreach (string item in items) Console.WriteLine(item);

Dans un premier temps, ce code classe les lments (ici, les noms des prsidents des tats-Unis) selon leur longueur. Dans un second temps, les lments sont classs dans

Chapitre 4

Les oprateurs diffrs

95

un ordre alphabtique. Si plusieurs noms ont la mme longueur, ils apparatront donc dans lordre alphabtique.
Bush Ford Polk Taft Adams Grant Hayes Nixon Tyler Arthur Carter Hoover Monroe Pierce Reagan Taylor Truman Wilson Clinton Harding Jackson Johnson Kennedy Lincoln Madison Buchanan Coolidge Fillmore Garfield Harrison McKinley Cleveland Jefferson Roosevelt Van Buren Eisenhower Washington

Pour illustrer le second prototype de loprateur ThenBy, nous allons utiliser le comparateur MyVowelConsonantRatioComparer, introduit quelques pages prcdemment. Pour tre en mesure dappeler loprateur ThenBy, il faut au pralable appeler loprateur OrderBy ou OrderByDescending. Le but de cet exemple est de classer les noms par longueurs croissantes puis, lintrieur de chaque groupe de longueurs, par ratio voyelles/consonnes (voir Listing 4.24).
Listing 4.24 : Un exemple dappel du second prototype de ThenBy.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft",

96

LINQ to Objects

Partie II

"Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; MyVowelToConsonantRatioComparer myComp = new MyVowelToConsonantRatioComparer(); IEnumerable<string> namesByVToCRatio = presidents .OrderBy(n => n.Length) .ThenBy((s => s), myComp); foreach (string item in namesByVToCRatio) { int vCount = 0; int cCount = 0; myComp.GetVowelConsonantCount(item, ref vCount, ref cCount); double dRatio = (double)vCount / (double)cCount; Console.WriteLine(item + " - " + dRatio + " - " + vCount + ":" + cCount); }

Voici le rsultat de ce code :


Bush - 0.333333333333333 - 1:3 Ford - 0.333333333333333 - 1:3 Polk - 0.333333333333333 - 1:3 Taft - 0.333333333333333 - 1:3 Grant - 0.25 - 1:4 Adams - 0.666666666666667 - 2:3 Nixon - 0.666666666666667 - 2:3 Tyler - 0.666666666666667 - 2:3 Hayes - 1.5 - 3:2 Arthur - 0.5 - 2:4 Carter - 0.5 - 2:4 Truman - 0.5 - 2:4 Wilson - 0.5 - 2:4 Hoover - 1 - 3:3 Monroe - 1 - 3:3 Pierce - 1 - 3:3 Reagan - 1 - 3:3 Taylor - 1 - 3:3 Clinton - 0.4 - 2:5 Harding - 0.4 - 2:5 Jackson - 0.4 - 2:5 Johnson - 0.4 - 2:5 Lincoln - 0.4 - 2:5 Kennedy - 0.75 - 3:4 Madison - 0.75 - 3:4 Buchanan - 0.6 - 3:5 Fillmore - 0.6 - 3:5 Garfield - 0.6 - 3:5 Harrison - 0.6 - 3:5 McKinley - 0.6 - 3:5 Coolidge - 1 - 4:4 Cleveland - 0.5 - 3:6 Jefferson - 0.5 - 3:6 Van Buren - 0.5 - 3:6 Roosevelt - 0.8 - 4:5 Washington - 0.428571428571429 - 3:7 Eisenhower - 1 - 5:5

Comme prvu, les noms sont classs par longueurs, puis par ratio voyelles/consonnes.

Chapitre 4

Les oprateurs diffrs

97

Oprateur ThenByDescending Cet oprateur utilise les mmes prototypes et se comporte comme loprateur ThenBy, mais il classe les donnes dans un ordre dcroissant.

Prototypes Deux prototypes de loprateur ThenByDescending seront tudis dans ce livre. Premier prototype
public static IOrderedEnumerable<T> ThenByDescending<T, K>( this IOrderedEnumerable<T> source, Func<T, K> keySelector) where K: IComparable<K>;

Ce prototype se comporte comme le premier prototype de loprateur ThenBy, mais il classe les donnes dans un ordre dcroissant. Second prototype
public static IOrderedEnumerable<T> ThenByDescending<T, K>( this IOrderedEnumerable<T> source, Func<T, K> keySelector, IComparer<K> comparer);

Ce prototype est identique au prcdent, si ce nest quun objet comparer complmentaire lui est pass. Si vous utilisez cette version de loprateur ThenByDescending, le type K nest pas forc dimplmenter linterface IComparable. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Nous allons utiliser le mme exemple que dans la section prcdente, mais ici loprateur ThenByDescending sera utilis la place de ThenBy (voir Listing 4.25).
Listing 4.25 : Un exemple dappel du premier prototype de loprateur ThenByDescending.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.OrderBy(s => s.Length).ThenByDescending(s => s); foreach (string item in items) Console.WriteLine(item);

98

LINQ to Objects

Partie II

Ce code classe les noms des prsidents par longueur croissante puis, lintrieur de chaque groupe, par ordre inverse alphabtique.
Taft Polk Ford Bush Tyler Nixon Hayes Grant Adams Wilson Truman Taylor Reagan Pierce Monroe Hoover Carter Arthur Madison Lincoln Kennedy Johnson Jackson Harding Clinton McKinley Harrison Garfield Fillmore Coolidge Buchanan Van Buren Roosevelt Jefferson Cleveland Washington Eisenhower

Pour illustrer le second prototype de loprateur ThenByDescending, nous utiliserons le mme code que dans lexemple du second prototype de loprateur ThenBy, ceci prs que loprateur ThenByDescending remplacera loprateur ThenBy (voir Listing 4.26).
Listing 4.26 : Un exemple dappel du second prototype de loprateur ThenByDescending.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};

Chapitre 4

Les oprateurs diffrs

99

MyVowelToConsonantRatioComparer myComp = new MyVowelToConsonantRatioComparer(); IEnumerable<string> namesByVToCRatio = presidents .OrderBy(n => n.Length) .ThenByDescending((s => s), myComp); foreach (string item in namesByVToCRatio) { int vCount = 0; int cCount = 0; myComp.GetVowelConsonantCount(item, ref vCount, ref cCount); double dRatio = (double)vCount / (double)cCount; Console.WriteLine(item + " - " + dRatio + " - " + vCount + ":" + cCount); }

Voici les informations afches dans la console suite lexcution de ce code :


Bush - 0.333333333333333 - 1:3 Ford - 0.333333333333333 - 1:3 Polk - 0.333333333333333 - 1:3 Taft - 0.333333333333333 - 1:3 Hayes - 1.5 - 3:2 Adams - 0.666666666666667 - 2:3 Nixon - 0.666666666666667 - 2:3 Tyler - 0.666666666666667 - 2:3 Grant - 0.25 - 1:4 Hoover - 1 - 3:3 Monroe - 1 - 3:3 Pierce - 1 - 3:3 Reagan - 1 - 3:3 Taylor - 1 - 3:3 Arthur - 0.5 - 2:4 Carter - 0.5 - 2:4 Truman - 0.5 - 2:4 Wilson - 0.5 - 2:4 Kennedy - 0.75 - 3:4 Madison - 0.75 - 3:4 Clinton - 0.4 - 2:5 Harding - 0.4 - 2:5 Jackson - 0.4 - 2:5 Johnson - 0.4 - 2:5 Lincoln - 0.4 - 2:5 Coolidge - 1 - 4:4 Buchanan - 0.6 - 3:5 Fillmore - 0.6 - 3:5 Garfield - 0.6 - 3:5 Harrison - 0.6 - 3:5 McKinley - 0.6 - 3:5 Roosevelt - 0.8 - 4:5 Cleveland - 0.5 - 3:6 Jefferson - 0.5 - 3:6 Van Buren - 0.5 - 3:6 Eisenhower - 1 - 5:5 Washington - 0.428571428571429 - 3:7

Comme vous pouvez le voir, les noms sont classs par longueur croissante, puis par ratio voyelles/consonnes dcroissant.

100

LINQ to Objects

Partie II

Oprateur Reverse Cet oprateur renvoie une squence du mme type que celle passe en entre, mais en inversant ses lments.

Prototype Un seul prototype de loprateur Reverse sera tudi dans ce livre :


public static IEnumerable<T> Reverse<T>( this IEnumerable<T> source);

Ce prototype retourne une squence IEnumerable<T> dont lnumration produit lordre inverse des lments de la squence dentre. Exceptions Lexception ArgumentNullException est leve si largument a pour valeur null (voir Listing 4.27). Exemples
Listing 4.27 : Un exemple dappel de loprateur Reverse.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> items = presidents.Reverse(); foreach (string item in items) Console.WriteLine(item);

Ce code afche les informations suivantes dans la fentre Console. Comme on pouvait sy attendre, les noms des prsidents apparaissent dans lordre inverse de ceux passs en entre :
Wilson Washington Van Buren Bush Buchanan Arthur Adams

Oprateurs de jointure Les oprateurs de jointure effectuent un assemblage de plusieurs squences.

Chapitre 4

Les oprateurs diffrs

101

Oprateur Join Loprateur Join effectue une jointure entre deux squences, en se basant sur les cls extraites des diffrents lments des deux squences.

Prototype Un seul prototype de loprateur Join sera abord dans cet ouvrage :
public static IEnumerable<V> Join<T, U, K, V>( this IEnumerable<T> outer, IEnumerable<U> inner, Func<T, K> outerKeySelector, Func<U, K> innerKeySelector, Func<T, U, V> resultSelector);

Le premier lment de la mthode a pour nom outer (extrieur). Comme il sagit dune mthode dextension, on parlera de "squence extrieure" pour faire rfrence la squence sur laquelle loprateur Join est appel. Loprateur Join retourne un objet. Son numration produit, dans un premier temps, une squence inner dlments de type U. Pour ce faire, la mthode innerKeySelector est appele sur chaque lment de la squence inner et un tableau de rfrencement est cr pour mmoriser les couples lment/valeur cl. Dans un second temps, lobjet retourn numre la squence outer dlments de type T. Pour ce faire, la mthode outerKeySelector est appele sur chaque lment de la squence outer an dobtenir sa cl et de retrouver la squence inner correspondante dans le tableau de rfrencement. Pour chaque lment de la paire squence outer/squence inner, lobjet retourn appelle enn la mthode resultSelector, en lui passant les lments outer et inner. Un objet instanci de type V est alors retourn par la mthode resultSelector, puis plac dans la squence de sortie de type V. Lordre des lments de la squence outer est prserv, ainsi que celui des lments inner de chaque squence outer. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Cet exemple utilise les deux classes communes dnies au dbut de ce chapitre : Employee et EmployeeOptionEntry. Le code du Listing 4.28 a t mis en forme un peu diffremment an damliorer la lisibilit des arguments de loprateur Join.
Listing 4.28 : Un exemple dappel de loprateur Join.
Employee[] employees = Employee.GetEmployeesArray(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); var employeeOptions = employees .Join( empOptions, // squence inner

102

LINQ to Objects

Partie II

e => e.id, // outerKeySelector o => o.id, // innerKeySelector (e, o) => new // resultSelector { id = e.id, name = string.Format("{0} {1}", e.firstName, e.lastName), options = o.optionsCount }); foreach (var item in employeeOptions) Console.WriteLine(item);

Ce code effectue une jointure sur deux tableaux de donnes en utilisant deux classes communes. Loprateur Join tant appliqu au tableau employees, ce dernier joue le rle de la squence externe. Quant empOptions, il correspond la squence interne. Voici les rsultats de la jointure :
{ { { { { { { { { id id id id id id id id id = = = = = = = = = 1, name = 2, name = 2, name = 2, name = 3, name = 3, name = 3, name = 4, name = 101, name Joe Rattz, options = 2 } William Gates, options = 10000 } William Gates, options = 10000 } William Gates, options = 10000 } Anders Hejlsberg, options = 5000 } Anders Hejlsberg, options = 7500 } Anders Hejlsberg, options = 7500 } David Lightman, options = 1500 } = Kevin Flynn, options = 2 }

La mthode resultSelector cre une classe anonyme du mme type que la squence de sortie. Il est facile de voir quil sagit dune classe anonyme, car aucun nom de classe nest spci dans linstruction new. Par ailleurs, le mot-cl var est utilis, ce qui conrme nos soupons. Il nest pas possible de le dclarer en tant qu IEnumerable<>, puisque aucun type nomm ne donne les prcisions ncessaires pour le dclarer comme tel.
ASTUCE Lorsque le dernier oprateur appel retourne une squence de type anonyme, vous devez utiliser le mot-cl var pour mmoriser la squence dans un objet.

Loprateur GroupJoin Loprateur GroupJoin effectue une jointure sur deux squences en se basant sur les cls extraites de chacun des lments des deux squences.

Cet oprateur travaille dune manire comparable loprateur Join, ceci prs que loprateur Join ne passe quun seul lment de la squence externe et un lment de la squence interne la mthode resultSelector. Cela signie que, si plusieurs lments de la squence interne correspondent un lment de la squence interne, plusieurs appels resultSelect seront ncessaires. Avec loprateur GroupJoin, tous les lments de la squence interne qui correspondent un lment de la squence externe

Chapitre 4

Les oprateurs diffrs

103

sont passs conjointement sous la forme dune squence resultSelector. Un seul appel cette mthode est donc ncessaire. Prototype Un seul prototype de loprateur GroupJoin sera tudi dans cet ouvrage :
public static IEnumerable<V> GroupJoin<T, U, K, V>( this IEnumerable<T> outer, IEnumerable<U> inner, Func<T, K> outerKeySelector, Func<U, K> innerKeySelector, Func<T, IEnumerable<U>, V> resultSelector);

Le premier lment de la mthode a pour nom outer (extrieur). Comme il sagit dune mthode dextension, on parlera de "squence extrieure" pour faire rfrence la squence sur laquelle loprateur Join est appel. Loprateur GroupJoin retourne un objet. Son numration produit, dans un premier temps, une squence inner dlments de type U. Pour ce faire, la mthode innerKeySelector est appele sur chaque lment de la squence inner et un tableau de rfrencement est cr pour mmoriser les couples lment/valeur cl. Dans un second temps, lobjet retourn numre la squence outer dlments de type T. Pour ce faire, la mthode outerKeySelector est appele sur chaque lment de la squence outer an dobtenir sa cl et de retrouver la squence inner correspondante dans le tableau de rfrencement. Pour chaque lment de la paire squence outer/squence inner, lobjet retourn appelle enn la mthode resultSelector, en lui passant llment outer et la squence des lments inner correspondants. Un objet instanci de type V est alors retourn par la mthode resultSelector, puis plac dans la squence de sortie de type V. Lordre des lments de la squence outer est prserv, ainsi que celui des lments inner de chaque squence outer. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Nous utiliserons les classes Employee et EmployeeOptionEntry dj voques dans la section prcdente. Le code du Listing 4.29 ralise une jointure entre les employs et les options et calcule la somme des options de chacun des employs en utilisant loprateur GroupJoin.
Listing 4.29 : Un exemple dutilisation de loprateur GroupJoin.
Employee[] employees = Employee.GetEmployeesArray(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); var employeeOptions = employees .GroupJoin(

104

LINQ to Objects

Partie II

empOptions, e => e.id, o => o.id, (e, os) => new { id = e.id, name = string.Format("{0} {1}", e.firstName, e.lastName), options = os.Sum(o => o.optionsCount) }); foreach (var item in employeeOptions) Console.WriteLine(item);

Ce code est trs proche du prcdent. Cependant, si vous examinez le deuxime argument pass lexpression lambda (issu de la mthode resultSelector), vous verrez que largument o de lexemple sur loprateur Join est remplac par os. Cette diffrence sexplique par le fait que loprateur Join travaille sur un seul objet option, alors que loprateur GroupJoin travaille sur une squence dobjets option. Loprateur Sum initialise donc le dernier membre de lobjet anonyme instanci avec la somme des objets option. Pour linstant, il vous suft de savoir que cet oprateur est en mesure de calculer la somme des lments (ou dun membre des lments) qui lui sont passs. Pour en savoir plus sur loprateur non diffr Join, reportez-vous au Chapitre 5. Voici le rsultat du code prcdent :
{ { { { { id id id id id = = = = = 1, name = 2, name = 3, name = 4, name = 101, name Joe Rattz, options = 2 } William Gates, options = 30000 } Anders Hejlsberg, options = 20000 } David Lightman, options = 1500 } = Kevin Flynn, options = 2 }

Dans ces rsultats, les valeurs options correspondent la somme de tous les champs option de chaque employ. Ces rsultats sont diffrents de ceux issus de loprateur Join, o une ligne tait cre pour chacune des options de chaque employ. Oprateurs de regroupement Ces oprateurs permettent de regrouper les lments dune squence qui possdent une mme cl.
Oprateur GroupBy Cet oprateur est utilis pour regrouper les lments dune squence dentre.

Prototypes Tous les prototypes de loprateur GroupBy retournent une squence dlments IGrouping<K, T>. Linterface IGrouping<K, T> est dnie comme suit :
public interface IGrouping<K, T>: IEnumerable<T> { K Key { get; } }

Chapitre 4

Les oprateurs diffrs

105

Un IGrouping est donc une squence de type T avec une cl de type K. Quatre prototypes de GroupBy seront tudis dans cet ouvrage. Premier prototype
public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>( this IEnumerable<T> source, Func<T, K> keySelector);

Ce prototype retourne un objet dont lnumration passe en revue les lments de la squence dentre, appelle la mthode keySelector, mmorise chaque lment avec sa cl et produit une squence dinstances IGrouping<K, E> dans laquelle chaque lment IGrouping<K, E> est une squence dlments qui partagent la mme cl. Les cls sont compares par lintermdiaire du comparateur dgalit par dfaut, EqualityComparerDefault. Pour dire les choses autrement, la valeur retourne par la mthode GroupBy est une squence dobjets IGrouping. Chacun dentre eux contient une cl et une squence dlments issus de la squence dentre et partageant la mme cl. Lordre des instances IGrouping est le mme que celui des cls dans la squence dentre. Quant lordre des lments dune squence IGrouping, il est identique celui des lments dans la squence dentre. Deuxime prototype
public static IEnumerable<IGrouping<K, T>> GroupBy<T, K>( this IEnumerable<T> source, Func<T, K> keySelector, IEqualityComparer<K> comparer);

Ce prototype est identique au premier, ceci prs quil est possible de choisir le comparateur utiliser. Troisime prototype
public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elementSelector);

Ce prototype est identique au premier mais, ici, la mthode elementSelector est utilise pour choisir les lments de la squence dentre qui doivent apparatre dans la squence de sortie. Quatrime prototype
public static IEnumerable<IGrouping<K, E>> GroupBy<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elementSelector, IEqualityComparer<K> comparer);

Ce prototype regroupe les possibilits offertes par les deuxime et troisime prototypes : il est donc possible de choisir un comparateur avec largument comparer et de limiter les lments de la squence de sortie avec largument elementSelector.

106

LINQ to Objects

Partie II

Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Le premier exemple (voir Listing 4.30) utilise la classe commune EmployeeOptionEntries. Les employs seront regroups par id et afchs.
Listing 4.30 : Un exemple dutilisation du premier prototype.
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); IEnumerable<IGrouping<int, EmployeeOptionEntry>> outerSequence = empOptions.GroupBy(o => o.id); // Premire numration de la squence extrieure de IGroupings foreach (IGrouping<int, EmployeeOptionEntry> keyGroupSequence in outerSequence) { Console.WriteLine("Enregistrements Option pour lemploy " + keyGroupSequence.Key); // numration des squences IGrouping dlments EmployeeOptionEntry foreach (EmployeeOptionEntry element in keyGroupSequence) Console.WriteLine("id={0}: optionsCount={1}: dateAwarded={2:d}", element.id, element.optionsCount, element.dateAwarded); }

Ce code numre la squence outerSequence. Les lments obtenus sont des objets qui implmentent linterface IGrouping. Ils contiennent une cl et une squence dlments EmployeeOptionEntry qui partagent cette mme cl. Voici les rsultats :
Enregistrements Option pour lemploy 1 id=1: optionsCount=2: dateAwarded=12/31/1999 Enregistrements Option pour lemploy 2 id=2: optionsCount=10000: dateAwarded=6/30/1992 id=2: optionsCount=10000: dateAwarded=1/1/1994 id=2: optionsCount=10000: dateAwarded=4/1/2003 Enregistrements Option pour lemploy 3 id=3: optionsCount=5000: dateAwarded=9/30/1997 id=3: optionsCount=7500: dateAwarded=9/30/1998 id=3: optionsCount=7500: dateAwarded=9/30/1998 Enregistrements Option pour lemploy 4 id=4: optionsCount=1500: dateAwarded=12/31/1997 Enregistrements Option pour lemploy 101 id=101: optionsCount=2: dateAwarded=12/31/1998

Pour illustrer le deuxime prototype de loprateur GroupBy, nous allons supposer que tous les employs dont le champ id est infrieur 100 sont des membres fondateurs de lentreprise. Nous allons lister tous les enregistrements option regroups selon ltat fondateur/non fondateur des employs.

Chapitre 4

Les oprateurs diffrs

107

Pour ce faire, nous devons dnir un comparateur spcique qui de plus doit implmenter linterface IEqualityComparer. Avant de parler du comparateur, jetons un il cette interface :
interface IEqualityComparer<T> { bool Equals(T x, T y); int GetHashCode(T x); }

Cette interface ncessite limplmentation de deux mthodes : Equals et GetHashCode. La mthode Equals reoit deux objets de type T. Elle retourne la valeur true si les deux objets sont considrs comme gaux et la valeur false dans le cas contraire. La mthode GetHashCode reoit un objet de type T et retourne un code (appel hash code ou cl) de type entier pour cet objet. Le hash code est une valeur numrique qui identie (gnralement) de manire unique un objet. Ordinairement calcul partir du contenu de lobjet, il est utilis comme un index qui permettra de retrouver facilement une structure de donnes. Voici la classe qui implmente linterface IEqualityComparer :
public class MyFounderNumberComparer: IEqualityComparer<int> { public bool Equals(int x, int y) { return(isFounder(x) == isFounder(y)); } public int GetHashCode(int i) { int f = 1; int nf = 100; return (isFounder(i)? f.GetHashCode(): nf.GetHashCode()); } public bool isFounder(int id) { return(id < 100); } }

La mthode IsFounder a t ajoute aux mthodes Equals et GetHashCode. Elle dtermine si un employ est un fondateur en se basant sur son champ id. Ceci facilite la comprhension du code. La mthode IsFounder est publique. Il est donc possible de lappeler en dehors de linterface. Nous verrons cela un peu plus loin dans le code de lexemple. Le comparateur dgalit considre que tout entier infrieur 100 reprsente un membre fondateur. Si deux entiers font partie dune de ces deux catgories, ils sont considrs comme gaux. La fonction GetHashCode retourne un entier gal 1 si lemploy est un membre fondateur ou gal 100 dans le cas contraire.
Listing 4.31 : Un exemple dutilisation du deuxime prototype.
MyFounderNumberComparer comp = new MyFounderNumberComparer(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries();

108

LINQ to Objects

Partie II

IEnumerable<IGrouping<int, EmployeeOptionEntry>> opts = empOptions .GroupBy(o => o.id, comp); // numration de la squence dIGrouping foreach (IGrouping<int, EmployeeOptionEntry> keyGroup in opts) { Console.WriteLine("Options pour les " + (comp.isFounder(keyGroup.Key)? "fondateurs": "non fondateurs ")); // numration de la squence dlments EmployeeOptionEntry foreach (EmployeeOptionEntry element in keyGroup) Console.WriteLine("id={0}: optionsCount={1}: dateAwarded={2:d}", element.id, element.optionsCount, element.dateAwarded); }

Dans cet exemple, le comparateur est instanci en dehors de la mthode GroupBy. La mthode IsFounder peut ainsi tre appele dans la boucle dafchage foreach. Voici les rsultats afchs par ce code :
Options pour les fondateurs id=1: optionsCount=2: dateAwarded=12/31/1999 id=2: optionsCount=10000: dateAwarded=6/30/1992 id=2: optionsCount=10000: dateAwarded=1/1/1994 id=3: optionsCount=5000: dateAwarded=9/30/1997 id=2: optionsCount=10000: dateAwarded=4/1/2003 id=3: optionsCount=7500: dateAwarded=9/30/1998 id=3: optionsCount=7500: dateAwarded=9/30/1998 id=4: optionsCount=1500: dateAwarded=12/31/1997 Options pour les non fondateurs id=101: optionsCount=2: dateAwarded=12/31/1998

Comme vous le voyez, les employs dont le champ id est infrieur 100 sont regroups sous le libell "Options pour les fondateurs" et les autres sous le libell "Options pour les non fondateurs". Pour illustrer le troisime prototype, nous allons extraire les dates de dlivrance des options. Le code sera trs proche de celui utilis pour illustrer le premier prototype. Contrairement au Listing 4.30, qui retournait un regroupement dobjets EmployeeOptionEntry, le Listing 4.32 retourne un regroupement de dates.
Listing 4.32 : Un exemple dutilisation du troisime prototype.
EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); IEnumerable<IGrouping<int, DateTime>> opts = empOptions .GroupBy(o => o.id, e => e.dateAwarded); // numration de la squence de IGrouping foreach (IGrouping<int, DateTime> keyGroup in opts) { Console.WriteLine("Enregistrements Option pour lemploy " + keyGroup.Key); // numration des lments DateTime foreach (DateTime date in keyGroup) Console.WriteLine(date.ToShortDateString()); }

Chapitre 4

Les oprateurs diffrs

109

Dans lappel loprateur GroupBy, remarquez que le deuxime argument ne retourne que la date de loption (dateAwarded). Le IGrouping est donc de type DateTime (et non EmployeeOptionEntry). Voici le rsultat de lexcution :
Enregistrements 12/31/1999 Enregistrements 6/30/1992 1/1/1994 4/1/2003 Enregistrements 9/30/1997 9/30/1998 9/30/1998 Enregistrements 12/31/1997 Enregistrements 12/31/1998 Option pour lemploy 1 Option pour lemploy 2

Option pour lemploy 3

Option pour lemploy 4 Option pour lemploy 101

Pour illustrer le quatrime prototype, nous allons utiliser la mthode elementSelector et un objet comparer. Cela revient utiliser une combinaison des exemples du deuxime et du troisime prototypes. Dans le Listing 4.33, nous regroupons les dates des options dans deux groupes : les fondateurs (id < 100) et les non-fondateurs (id > 100).
Listing 4.33 : Un exemple dutilisation du quatrime prototype.
MyFounderNumberComparer comp = new MyFounderNumberComparer(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); IEnumerable<IGrouping<int, DateTime>> opts = empOptions .GroupBy(o => o.id, o => o.dateAwarded, comp); // numration de la squence de IGrouping foreach (IGrouping<int, DateTime> keyGroup in opts) { Console.WriteLine("Enregistrements Option pour les " + (comp.isFounder(keyGroup.Key)? "fondateurs": "non fondateurs")); // numration de la squence des lments EmployeeOptionEntry foreach (DateTime date in keyGroup) Console.WriteLine(date.ToShortDateString()); }

La sortie console nafche que des dates regroupes par fondateurs et non-fondateurs :
Enregistrements Option pour les fondateurs 12/31/1999 6/30/1992 1/1/1994 9/30/1997 4/1/2003 9/30/1998 9/30/1998 12/31/1997 Enregistrements Option pour les non fondateurs 12/31/1998

110

LINQ to Objects

Partie II

Oprateurs dinitialisation Les oprateurs dinitialisation sont utiliss pour obtenir des valeurs calcules partir de squences.
ASTUCE Les prototypes des oprateurs dinitialisation passs en revue dans cet ouvrage ne sont pas adapts aux DataSets. Prfrez-leur les prototypes prsents au Chapitre 10.

Oprateur Distinct Loprateur Distinct supprime les doublons dans la squence dentre.

Prototype Un seul prototype de loprateur Distinct sera tudi dans cet ouvrage :
public static IEnumerable<T> Distinct<T>( this IEnumerable<T> source);

Cet oprateur retourne un objet dont lnumration exclut les doublons de la squence dentre. Le critre dgalit entre deux lments est dtermin avec les mthodes GetHashCode et Equals. Exceptions Lexception ArgumentNullException est leve si la source a pour valeur null. Exemples Cet exemple fonctionne selon les cinq tapes suivantes :
m m m m m

afchage du nombre dlments contenus dans le tableau presidents ; duplication des lments du tableau ; afchage de la squence rsultante ; appel de loprateur Distinct sur la squence concatne ; afchage du nombre dlments en sortie de loprateur.

Si tout fonctionne correctement, le nombre dlments renvoys par loprateur Distinct devrait tre gal au nombre initial dlments du tableau presidents. Pour obtenir le nombre dlments des deux squences, nous utiliserons loprateur de requte standard non diffr Count. Si ncessaire, reportez-vous au chapitre suivant pour avoir de plus amples informations sur cet oprateur.
Listing 4.34 : Un exemple dutilisation de loprateur Distinct.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield",

Chapitre 4

Les oprateurs diffrs

111

"Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; // Affichage du nombre dlments du tableau presidents Console.WriteLine("Nombre de prsidents: " + presidents.Count()); // Duplication des lments du tableau presidents IEnumerable<string> presidentsWithDupes = presidents.Concat(presidents); // Affichage du nombre dlments du tableau presidents Console.WriteLine("Nombre de prsidents aprs la duplication: " + presidentsWithDupes.Count()); // Suppression des doublons et affichage du nombre dlments IEnumerable<string> presidentsDistinct = presidentsWithDupes.Distinct(); Console.WriteLine("Nombre de prsidents distincts: " + presidentsDistinct.Count());

Voici le rsultat de ce code :


Nombre de prsidents: 37 Nombre de prsidents aprs la duplication: 74 Nombre de prsidents distincts: 37

Oprateur Union Loprateur Union retourne la runion de deux squences dentre.

Prototype Nous tudierons un seul prototype de loprateur Union dans cet ouvrage :
public static IEnumerable<T> Union<T>( this IEnumerable<T> first, IEnumerable<T> second);

Ce prototype fournit un objet dont lnumration retourne les lments de la premire squence, privs de leurs doublons, suivis des lments de la seconde squence, galement privs de leurs doublons. Lgalit des lments est dtermine par les mthodes HashCode et Equals. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Pour montrer la diffrence entre les oprateurs Union et Concat (voir Listing 4.35), nous allons crer les squences first et second partir du tableau presidents. Ces deux squences auront en commun le cinquime lment du tableau presidents. Nous afcherons le nombre dlments du tableau presidents, des squences premier et second et des squences premier et second soumises aux oprateurs Concat et Union.

112

LINQ to Objects

Partie II

Listing 4.35 : Un exemple dutilisation de loprateur Union.


string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> first = presidents.Take(5); IEnumerable<string> second = presidents.Skip(4); // Seul le cinquime lment du tableau presidents // est commun aux squences premier et second IEnumerable<string> concat = first.Concat<string>(second); IEnumerable<string> union = first.Union<string>(second); Console.WriteLine("Nombre presidents.Count()); Console.WriteLine("Nombre Console.WriteLine("Nombre Console.WriteLine("Nombre concat.Count()); Console.WriteLine("Nombre union.Count()); dlments du tableau presidents: " + dlments de la premire squence: " + first.Count()); dlments de la deuxime squence: " + second.Count()); dlments aprs concatnation des deux squences: " + dlments aprs union des deux squences: " +

Ce code afche le texte ci-aprs dans la fentre Console :


Nombre Nombre Nombre Nombre Nombre dlments dlments dlments dlments dlments du tableau presidents: 37 de la premire squence: 5 de la deuxime squence: 33 aprs concatnation des deux squences: 38 aprs union des deux squences: 37

Comme on pouvait sy attendre :


m

La squence issue de loprateur Concat a un lment de plus que le tableau presidents. La squence issue de loprateur Union a le mme nombre dlments que le tableau presidents.

Oprateur Intersect Loprateur Intersect retourne lintersection des deux squences passes en entre.

Prototype Nous tudierons un seul prototype de loprateur Intersect dans cet ouvrage :
public static IEnumerable<T> Intersect<T>( this IEnumerable<T> first, IEnumerable<T> second);

Chapitre 4

Les oprateurs diffrs

113

Cet oprateur retourne un objet dont lnumration est obtenue : 1. en dressant la liste des singletons de la premire squence ; 2. en numrant les lments de la deuxime squence et en marquant ceux qui se trouvent dans la liste de la premire tape ; 3. en numrant les lments marqus dans lordre o ils ont t collects ltape 2. Lgalit des lments est dtermine laide des mthodes GetHashCode et Equals. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Nous allons utiliser les oprateurs Take et Skip pour gnrer deux squences qui possdent un seul lment en commun. Lorsque nous appliquerons loprateur Intersect ces deux squences, seul cet lment sera retourn.
Listing 4.36 : Un exemple dutilisation de loprateur Intersect.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; IEnumerable<string> first = presidents.Take(5); IEnumerable<string> second = presidents.Skip(4); // Seul le cinquime lment du tableau presidents // est commun aux squences premier et second IEnumerable<string> intersect = first.Intersect(second); Console.WriteLine("Nombre presidents.Count()); Console.WriteLine("Nombre Console.WriteLine("Nombre second.Count()); Console.WriteLine("Nombre intersect.Count()); dlments dans le tableau presidents: " + dlments dans la premire squence: " + first.Count()); dlments dans la deuxime squence: " + dlments aprs intersection des deux squences: " +

// Affichage de la squence rsultant de loprateur Intersect foreach (string name in intersect) Console.WriteLine(name);

Voici le rsultat de lexcution de ce code :


Nombre Nombre Nombre Nombre Carter dlments dlments dlments dlments dans le tableau presidents: 37 dans la premire squence: 5 dans la deuxime squence: 33 aprs intersection des deux squences: 1

114

LINQ to Objects

Partie II

Oprateur Except Cet oprateur retourne une squence qui contient tous les lments de la premire squence qui napparaissent pas dans la seconde.

Prototype Nous tudierons un seul prototype de loprateur Except dans cet ouvrage :
public static IEnumerable<T> Except<T>( this IEnumerable<T> first, IEnumerable<T> second);

Cet oprateur retourne un objet dont lnumration effectue les actions suivantes : 1. numration des lments de la premire squence en liminant les doublons. 2. numration des lments de la deuxime squence en ne conservant que les lments qui nont pas t retenus la premire tape. 3. Cration dune collection de sortie qui contient les lments retenus la deuxime tape. Lgalit des lments est dtermine laide des mthodes GetHashCode et Equals. Exceptions Lexception ArgumentNullException est leve si un des arguments a pour valeur null. Exemples Une fois encore, nous utiliserons le tableau presidents. Supposons que vous effectuiez un traitement sur les lments du tableau presidents et que les lments obtenus soient placs dans une squence. Si de nouveaux lments sont ajouts cette squence, il sera inutile dappliquer le traitement aux lments qui lont dj subi. Pour ne slectionner que les nouveaux lments, il sufra de transmettre lancienne liste et la nouvelle liste loprateur Except. Vous pourrez alors appliquer le traitement aux nouveaux venus. Dans cet exemple, nous allons supposer que les quatre premiers lments ont dj subi le traitement. Pour obtenir la liste des autres lments, il suft dutiliser loprateur Except (voir Listing 4.37).
Listing 4.37 : Un exemple dutilisation de loprateur Except.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};

Chapitre 4

Les oprateurs diffrs

115

// Dfinition de la squence processed IEnumerable<string> processed = presidents.Take(4); IEnumerable<string> exceptions = presidents.Except(processed); foreach (string name in exceptions) Console.WriteLine(name);

Comme on pouvait sy attendre, les noms des prsidents afchs dans la console commencent au cinquime :
Carter Cleveland Clinton Coolidge Eisenhower Fillmore Ford Garfield Grant Harding Harrison Hayes Hoover Jackson Jefferson Johnson Kennedy Lincoln Madison McKinley Monroe Nixon Pierce Polk Reagan Roosevelt Taft Taylor Truman Tyler Van Buren Washington Wilson

Oprateurs de conversion Les oprateurs de conversion reprsentent une faon simple et pratique de convertir des squences en des collections dun autre type.
Oprateur Cast Loprateur Cast convertit tous les lments de la squence dentre dans le type spcifi et les place dans la squence de sortie.

Prototype Nous tudierons un seul prototype de loprateur Cast dans cet ouvrage :
public static IEnumerable<T> Cast<T>( this IEnumerable source);

116

LINQ to Objects

Partie II

Contrairement la majorit des oprateurs de requte standard diffrs, le premier argument de loprateur Cast est de type IEnumerable, et non IEnumerable<T>. Ceci sexplique par le fait que loprateur Cast a t dni pour tre appel sur des classes qui implmentent linterface IEnumerable. En particulier les collections C# hrites, dnies avant la sortie de C# 2.0 et des gnriques. Vous pouvez utiliser loprateur Cast sur toute collection C# hrite, condition quelle implmente linterface IEnumerable. Une squence IEnumerable<T> sera alors cre. tant donn que la plupart des oprateurs de requte standard ne travaillent quavec des squences de type IEnumerable<T>, vous devrez utiliser loprateur Cast ou OfType (voir la section suivante) pour obtenir un type IEnumerable<T> compatible. Ayez bien cela en tte si vous prvoyez dappliquer des oprateurs de requte standard sur des collections hrites. Loprateur Cast retourne un objet dont lnumration transforme les lments de la squence dentre pour quils soient du type T. Si un lment ne peut pas tre converti, une exception est leve. Il est donc important de nutiliser cet oprateur que lorsque lon est sr que tous les lments de la squence dentre peuvent tre convertis.
ASTUCE Lorsque vous appliquez une requte LINQ une collection hrite, noubliez pas dutiliser un oprateur Cast ou OfType pour convertir la collection hrite en une squence IEnumerable<T> compatible avec les oprateurs de requte standard.

Exceptions Lexception ArgumentNullException est leve si largument source a pour valeur null. Lexception InvalidCastException est leve si un des lments de la squence dentre ne peut pas tre converti dans le type T. Exemples Dans cet exemple, nous utiliserons la mthode GetEmployeesArrayList de la classe commune Employee pour obtenir un objet ArrayList hrit (non gnrique). Cet objet sera alors converti en un IEnumerable<T> avec loprateur Cast (voir Listing 4.38).
Listing 4.38 : Ce code convertit un ArrayList en un IEnumerable<T> qui peut tre utilis avec les oprateurs de requte standard.
ArrayList employees = Employee.GetEmployeesArrayList(); Console.WriteLine("Le type de lobjet employees est " + employees.GetType()); var seq = employees.Cast<Employee>(); Console.WriteLine("Le type de lobjet seq est " + seq.GetType()); var emps = seq.OrderBy(e => e.lastName); foreach (Employee emp in emps) Console.WriteLine("{0} {1}", emp.firstName, emp.lastName);

Chapitre 4

Les oprateurs diffrs

117

La premire ligne utilise la mthode GetEmployeesArrayList pour obtenir un ArrayList dobjets Employee. Le type de lobjet ainsi obtenu est afch par la deuxime ligne. Dans la troisime ligne, lobjet employees est converti en une squence IEnumerable<T> en appelant loprateur Cast. Le type de lobjet obtenu est afch dans la quatrime ligne. Les autres lignes numrent lobjet IEnumerable<T> an de prouver que la conversion a russi. Voici le rsultat de lexcution de ce code :
Le type de lobjet employees est System.Collections.ArrayList Le type de lobjet seq est System.Linq.Enumerable+<CastIterator>d__b0`1[LINQChapter4.Employee] Kevin Flynn William Gates Anders Hejlsberg David Lightman Joe Rattz

Le type de lobjet employees est clairement identiable. Il en va autrement de celui de lobjet seq. Ce que nous pouvons dire, cest quil est diffrent du prcdent et quil ressemble une squence. Nous pouvons galement remarquer le mot CastIterator dans lintitul de son type. Vous rappelez-vous de ce qui a t dit propos des oprateurs diffrs : ces oprateurs retournent non pas une squence en sortie, mais un objet dont lnumration fournit les lments de la squence de sortie. Lobjet seq est prcisment de ce type.
ATTENTION Loprateur Cast essaye de convertir tous les lments de la squence dentre dans le type spci. Si un de ces lments ne peut pas tre converti, une exception InvalidCastException est leve. Si une telle situation est possible, prfrez loprateur OfType loprateur Cast.

Oprateur OfType Cet oprateur change le type des lments de la squence dentre qui le permettent et les place dans la squence de sortie.

Prototype Nous tudierons un seul prototype de loprateur OfType dans cet ouvrage :
public static IEnumerable<T> OfType<T>( this IEnumerable source);

Le premier argument de loprateur OfType est de type IEnumerable, et non IEnumerable<T>. Tout comme Cast, OfType est destin tre appel sur des classes qui implmentent linterface IEnumerable. En particulier les collections C# hrites, dnies avant la sortie de C# 2.0 et des gnriques.

118

LINQ to Objects

Partie II

Vous pouvez utiliser loprateur OfType sur toute collection C# hrite, condition quelle implmente linterface IEnumerable. Une squence IEnumerable<T> sera alors cre. tant donn que la plupart des oprateurs de requte standard ne travaillent quavec des squences de type IEnumerable<T>, vous devrez utiliser loprateur OfType ou Cast (voir la section prcdente) pour obtenir un type IEnumerable<T> compatible. Ayez bien cela en tte si vous prvoyez dappliquer des oprateurs de requte standard sur des collections hrites. Loprateur OfType retourne un objet dont lnumration transforme les lments de la squence dentre pour quils soient du type T (seuls les lments qui supportent la conversion sont convertis). Exceptions Lexception ArgumentNullException est leve si largument source a pour valeur null. Exemples Dans lexemple du Listing 4.39, nous crons un ArrayList contenant des objets issus des classes communes Employee et EmployeeOptionEntry. Appliqu cet objet, loprateur Cast ne parvient pas effectuer la conversion de type. Quelques lignes plus bas, loprateur OfType, appliqu ce mme objet, passe haut la main la conversion.
Listing 4.39 : Un exemple dappel des oprateurs Cast et OfType.
ArrayList al = new ArrayList(); al.Add(new Employee { id = 1, firstName = "Joe", lastName = "Rattz" }); al.Add(new Employee { id = 2, firstName = "William", lastName = "Gates" }); al.Add(new EmployeeOptionEntry { id = 1, optionsCount = 0 }); al.Add(new EmployeeOptionEntry { id = 2, optionsCount = 99999999999 }); al.Add(new Employee { id = 3, firstName = "Anders", lastName = "Hejlsberg" }); al.Add(new EmployeeOptionEntry { id = 3, optionsCount = 848475745 }); var items = al.Cast<Employee>(); Console.WriteLine("Tentative dnumration de la squence issue de loprateur Cast ..."); try { foreach (Employee item in items) Console.WriteLine("{0} {1} {2}", item.id, item.firstName, item.lastName); } catch (Exception ex) { Console.WriteLine("{0}{1}", ex.Message, System.Environment.NewLine); } Console.WriteLine("Tentative dnumration de la squence issue de loprateur OfType ..."); var items2 = al.OfType<Employee>(); foreach (Employee item in items2) Console.WriteLine("{0} {1} {2}", item.id, item.firstName, item.lastName);

Le premier bloc dinstructions cre et remplit lobjet ArrayList al. Loprateur Cast est alors appliqu cet objet. Le bloc dinstructions suivant tente dnumrer les lments de la squence issue de loprateur Cast (sans ces instructions, lerreur de

Chapitre 4

Les oprateurs diffrs

119

conversion naurait pas t identie). Lnumration est protge par une structure try/catch. Ainsi, un message est afch lorsquune erreur de conversion est dtecte. Le code se poursuit par lapplication de loprateur OfType sur la squence al. Ici, aucune erreur de conversion ntant possible, les lments de la squence retourne par OfType sont simplement numrs (la structure try/catch ne devrait pas tre retire dun code dont la porte dpasse le cadre pdagogique). Voici les rsultats de ce code :
Tentative dnumration de la squence issue de loprateur Cast ... 1 Joe Rattz 2 William Gates Unable to cast object of type LINQChapter4.EmployeeOptionEntry to type LINQChapter4.Employee. Tentative dnumration de la squence issue de loprateur OfType ... 1 Joe Rattz 2 William Gates 3 Anders Hejlsberg

Il na pas t possible dnumrer tous les rsultats de la squence retourne par loprateur Cast sans quune exception ne soit gnre. En revanche, tous les rsultats de la squence retourne par loprateur OfType ont pu tre numrs, et seuls les lments de type employee ont t inclus dans la squence de sortie.
ASTUCE Si vous voulez convertir une collection non gnrique (une collection hrite, par exemple) en une squence IEnumerable<T>, utilisez loprateur OfType et non loprateur Cast si les donnes convertir peuvent tre de plusieurs types diffrents.

Oprateur AsEnumerable Loprateur AsEnumerable retourne la squence dentre IEnumerable<T> en tant quIEnumerable<T>.

Prototype Un seul prototype de loprateur AsEnumerable sera tudi dans cet ouvrage :
public static IEnumerable<T> AsEnumerable<T>( this IEnumerable<T> source);

Un rapide coup dil ce prototype montre quAsEnumerable utilise la squence dentre IEnumerable<T> source et la retourne type en IEnumerable<T>. Cela peut sembler quelque peu trange. En effet, quel est lintrt de transformer un IEnumerable<T> en un autre IEnumerable<T> ? Les oprateurs de requte standard sont dnis pour oprer sur des squences LINQ to Objects "normales", cest--dire qui implmentent linterface IEnumerable<T>.

120

LINQ to Objects

Partie II

Dautres types de collections, par exemple celles qui accdent des bases de donnes, peuvent utiliser des squences et des oprateurs qui leur sont propres. Gnralement, lorsque vous appliquez un oprateur de requte sur ces types de collections, cet oprateur est spcique la collection. En utilisant loprateur AsEnumerable, vous allez pouvoir convertir une squence dentre en une squence IEnumerable<T> "normale", directement utilisable dans un oprateur de requte standard. titre dexemple, lorsque nous nous intresserons LINQ to SQL un peu plus loin dans ce livre, vous verrez que cette partie de LINQ utilise des squences de type IQueryable<T> et implmente ses propres oprateurs. Ces derniers sont spciques aux squences IQueryable<T>. Lorsque vous appelez loprateur Where sur une squence IQueryable<T>, cest la mthode Where de LINQ to SQL qui est invoque, et non loprateur de requte standard Where de LINQ to Objects ! Si vous essayez dinvoquer un oprateur de requte standard sur un objet IQueryable, une exception sera gnre, moins quun oprateur LINQ to SQL de mme nom nexiste. Loprateur AsEnumerable permet de convertir une squence IQueryable<T> en une squence IEnumerable<T>, permettant ainsi lutilisation des oprateurs de requte standard. AsEnumerable se rvle trs pratique si vous devez contrler dans quelle API un oprateur doit tre appel. Exceptions Aucune exception nest gnre par cet oprateur. Exemples Pour mieux comprendre comment fonctionne cet oprateur, nous allons raisonner sur un cas pratique. Nous utiliserons lexemple LINQ to SQL donn au Chapitre 1. Voici le code utilis :
using using using using System; System.Linq; System.Data.Linq; nwind;

Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var custs = from c in db.Customers where c.City == "Rio de Janeiro" select c; foreach (var cust in custs) Console.WriteLine("{0}", cust.CompanyName);

Et voici les rsultats de cet exemple :


Hanari Carnes Que Delcia Ricardo Adocicados

Chapitre 4

Les oprateurs diffrs

121

Pour que ce code soit en mesure de fonctionner, vous devez ajouter votre projet :
m m m

lassembly System.Data.Linq.dll ; une directive using qui pointe sur lespace de noms nwind ; les classes dentits gnres, qui seront tudies dans les chapitres relatifs LINQ to SQL.

Supposons que vous deviez inverser lordre des enregistrements issus de la base de donnes. Vous utiliserez loprateur Reverse, abord un peu plus loin dans ce chapitre. Le Listing 4.40 reprsente le code prcdent, modi pour appeler loprateur Reverse.
Listing 4.40 : Appel de loprateur Reverse.
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var custs = (from c in db.Customers where c.City == "Rio de Janeiro" select c) .Reverse(); foreach (var cust in custs) Console.WriteLine("{0}", cust.CompanyName);

Comme vous pouvez le voir, lunique modication a consist appeler la mthode Reverse. Voici les rsultats renvoys dans la console :
Exception non gre: System.NotSupportedException: Loprateur Reverse nest pas support.

Que sest-il pass ? tant donn quil nexiste aucune mthode Reverse pour linterface IQueryable<T>, une exception a t gnre. Cest l quintervient la mthode AsEnumerable. Grce elle, la squence IQueryable<T> va tre convertie en une squence IEnumerable<T>, et il sera possible de lui appliquer la mthode Reverse. Voici dans le Listing 4.41 le code modi.
Listing 4.41 : Appel de loprateur AsEnumerable avant loprateur Reverse.
Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); var custs = (from c in db.Customers where c.City == "Rio de Janeiro" select c) .AsEnumerable() .Reverse(); foreach (var cust in custs) Console.WriteLine("{0}", cust.CompanyName);

122

LINQ to Objects

Partie II

La mthode AsEnumerable est appele avant loprateur Reverse. Cest donc loprateur Reverse de LINQ to Objects qui va tre invoqu. Voici les rsultats afchs dans la console :
Ricardo Adocicados Que Delcia Hanari Carnes

Ces rsultats sont bien afchs dans lordre inverse de la squence originale. Loprateur Reverse a donc bien fonctionn. Oprateurs ddis aux lments Ces oprateurs permettent dextraire des lments dans la squence dentre.
Oprateur DefaultIfEmpty Loprateur DefaultIfEmpty retourne une squence qui contient un lment par dfaut si la squence dentre est vide.

Prototypes Deux prototypes de loprateur DefaultIfEmpty seront tudis dans cet ouvrage. Premier prototype
public static IEnumerable<T> DefaultIfEmpty<T>( this IEnumerable<T> source);

Ce prototype retourne un objet dont lnumration renvoie chacun des lments de la squence dentre. Si cette dernire est vide, une squence de type default(T) contenant un seul lment est retourne. Pour les rfrences et les types nullables, la valeur par dfaut est null. Contrairement aux autres oprateurs ddis aux lments, DefaultIfEmpty retourne une squence de type IEnumerable<T> et non de type T. Il existe dautres oprateurs de type, mais nous ne les tudierons pas dans ce chapitre, car ils ne sont pas diffrs. Le second prototype permet de spcier la valeur par dfaut. Second prototype
public static IEnumerable<T> DefaultIfEmpty<T>( this IEnumerable<T> source, T defaultValue);

Cet oprateur est utile aux oprateurs qui gnrent des exceptions lorsque la squence dentre est vide. Il permet galement loprateur GroupJoin de gnrer des jointures externes gauche (left outer join).

Chapitre 4

Les oprateurs diffrs

123

Exceptions Une exception ArgumentNullException est leve si largument source a pour valeur null. Exemples Dans ce premier exemple, nous allons rechercher le nom "Jones" dans le tableau presidents (voir Listing 4.42). Un message indiquera si ce nom a t ou na pas t trouv.
Listing 4.42 : Premier exemple du prototype DefaultIfEmpty, sans loprateur DefaultIfEmpty.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string jones = presidents.Where(n => n.Equals("Jones")).First(); if (jones!= null) Console.WriteLine("Jones was found"); else Console.WriteLine("Jones was not found");

Voici les rsultats afchs dans la console :


Exception non gre : System.InvalidOperationException : La squence ne contient aucun lment

Le nom "Jones" nayant pas t trouv, une squence vide est passe loprateur First. Ce dernier napprciant pas les squences vides, il a gnr une exception. Nous allons maintenant ajouter un appel loprateur DefaultIfEmpty entre les oprateurs Where et First. Ainsi, cest non pas une squence vide, mais une squence contenant un lment null qui sera passe loprateur First (voir Listing 4.43).
Listing 4.43 : Second exemple du premier prototype, cette fois en utilisant DefaultIfEmpty.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string jones = presidents.Where(n => n.Equals("Jones")).DefaultIfEmpty().First(); if (jones!= null) Console.WriteLine("Jones was found."); else Console.WriteLine("Jones was not found.");

124

LINQ to Objects

Partie II

Voici le rsultat :
Jones na pas t trouv.

Voici maintenant un exemple pour le second prototype (voir Listing 4.44). Ici, nous pouvons choisir la valeur retourne lorsque la squence dentre est vide.
Listing 4.44 : Un exemple du second prototype de loprateur DefaultIfEmpty.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.Where(n => n.Equals("Jones")).DefaultIfEmpty("Missing").First(); Console.WriteLine(name);

Voici le rsultat :
Absent

Nous allons maintenant raliser une jointure externe gauche en utilisant les oprateurs GroupJoin et DefaultIfEmpty. Nous travaillerons avec deux classes communes, Employee et EmployeeOptionEntry. Dans le Listing 4.45, loprateur DefaultIfEmpty nest pas utilis.
Listing 4.45 : Un exemple sans loprateur DefaultIfEmpty.
ArrayList employeesAL = Employee.GetEmployeesArrayList(); // Ajout dun nouvel employ sans enregistrement EmployeeOptionEntry correspondant employeesAL.Add(new Employee { id = 102, firstName = "Michael", lastName = "Bolton" }); Employee[] employees = employeesAL.Cast<Employee>().ToArray(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); var employeeOptions = employees .GroupJoin( empOptions, e => e.id, o => o.id, (e, os) => os .Select(o => new { id = e.id, name = string.Format("{0} {1}", e.firstName, e.lastName), options = o!= null? o.optionsCount: 0 })) .SelectMany(r => r); foreach (var item in employeeOptions) Console.WriteLine(item);

Chapitre 4

Les oprateurs diffrs

125

Quelques prcisions propos de cet exemple :


m

Le code est trs proche de celui qui a t utilis pour illustrer loprateur GroupJoin. tant donn que chaque employ de la classe commune employee a une correspondance dans la classe commune EmployeeOptionEntry, nous allons ajouter un nouvel employ, Michael Bolton, lobjet ArrayList des employs, de telle sorte quaucun objet EmployeeOptionEntry ne lui corresponde. Loprateur DefaultIfEmpty ne sera pas appel dans cet exemple.

Voici les rsultats de la requte :


{ { { { { { { { { id id id id id id id id id = = = = = = = = = 1, name = 2, name = 2, name = 2, name = 3, name = 3, name = 3, name = 4, name = 101, name Joe Rattz, options = 2 } William Gates, options = 10000 } William Gates, options = 10000 } William Gates, options = 10000 } Anders Hejlsberg, options = 5000 } Anders Hejlsberg, options = 7500 } Anders Hejlsberg, options = 7500 } David Lightman, options = 1500 } = Kevin Flynn, options = 2 }

Comme aucun objet ne correspond lemploy Michael Bolton dans le tableau EmployeeOptionArray, aucune information concernant cet employ nest afche dans la console. Nous allons maintenant utiliser loprateur DefaultIfEmpty pour crer un enregistrement par dfaut pour cet employ (voir Listing 4.46).
Listing 4.46 : Un exemple dutilisation de loprateur DefaultIfEmpty.
ArrayList employeesAL = Employee.GetEmployeesArrayList(); // Ajout dun nouvel employ sans enregistrement EmployeeOptionEntry correspondant employeesAL.Add(new Employee { id = 102, firstName = "Michael", lastName = "Bolton" }); Employee[] employees = employeesAL.Cast<Employee>().ToArray(); EmployeeOptionEntry[] empOptions = EmployeeOptionEntry.GetEmployeeOptionEntries(); var employeeOptions = employees .GroupJoin( empOptions, e => e.id, o => o.id, (e, os) => os .DefaultIfEmpty() .Select(o => new { id = e.id,

126

LINQ to Objects

Partie II

name = string.Format("{0} {1}", e.firstName, e.lastName), options = o!= null? o.optionsCount: 0 })) .SelectMany(r => r); foreach (var item in employeeOptions) Console.WriteLine(item);

Le premier bloc de code ajoute lemploy Michael Bolton sans lui associer un objet EmployeeOptionEntry. Le deuxime bloc de code effectue une requte sur les donnes en faisant appel loprateur DefaultIfEmpty. Voici les rsultats :
{ { { { { { { { { { id id id id id id id id id id = = = = = = = = = = 1, name = 2, name = 2, name = 2, name = 3, name = 3, name = 3, name = 4, name = 101, name 102, name Joe Rattz, options = 2 } William Gates, options = 10000 } William Gates, options = 10000 } William Gates, options = 10000 } Anders Hejlsberg, options = 5000 } Anders Hejlsberg, options = 7500 } Anders Hejlsberg, options = 7500 } David Lightman, options = 1500 } = Kevin Flynn, options = 2 } = Michael Bolton, options = 0 }

Loprateur DefaultIfEmpty a bien ajout un objet EmployeeOptionEntry pour lemploy Michael Bolton. Oprateurs de gnration Ces oprateurs sont utiliss pour gnrer des squences.
Oprateur Range Loprateur Range gnre une squence dentiers.

Prototype Un seul prototype de loprateur Range sera tudi dans cet ouvrage :
public static IEnumerable<int> Range( int start, int count);

Ce prototype gnre une squence de count entiers partir de start. Loprateur Range nest pas une mthode dextension. Il ntend pas le type IEnumerable<T>.
INFO Loprateur Range nest pas une mthode dextension. Cest une mthode statique appele dans lassembly System.Linq.Enumerable.

Chapitre 4

Les oprateurs diffrs

127

Exceptions Une exception ArgumentOutOfRangeException est leve si count est infrieur zro ou si start+count-1 est suprieur int.MaxValue. Exemples
Listing 4.47 : Un exemple dappel de loprateur Range.
IEnumerable<int> ints = Enumerable.Range(1, 10); foreach(int i in ints) Console.WriteLine(i);

Je tiens rappeler que loprateur Range nest pas appliqu une squence : il sagit dune mthode statique de la classe System.Linq.Enumerable. Voici les rsultats afchs dans la console :
1 2 3 4 5 6 7 8 9 10

Oprateur Repeat Loprateur Repeat gnre une squence en rptant plusieurs fois un mme lment.

Prototype Un seul prototype de loprateur Repeat sera tudi dans cet ouvrage :
public static IEnumerable<T> Repeat<T>( T element, int count);

Ce prototype retourne un objet dont lnumration produit count lments T. Loprateur Repeat nest pas une mthode dextension. Il ntend pas le type IEnumerable<T>.
INFO Loprateur Repeat nest pas une mthode dextension. Cest une mthode statique appele dans lassembly System.Linq.Enumerable.

Exceptions Une exception ArgumentOutOfRangeException est leve si count est infrieur zro.

128

LINQ to Objects

Partie II

Exemples
Listing 4.48 : Gnration dune squence de dix lments Integer initialiss la valeur 2.
IEnumerable<int> ints = Enumerable.Repeat(2, 10); foreach(int i in ints) Console.WriteLine(i);

Voici les rsultats afchs dans la console :


2 2 2 2 2 2 2 2 2 2

Oprateur Empty Loprateur Empty gnre une squence vide de type T.

Prototype Un seul prototype de loprateur Empty sera tudi dans cet ouvrage :
public static IEnumerable<T> Empty<T>();

Ce prototype renvoie un objet dont lnumration produit 0 lment de type T. Loprateur Empty nest pas une mthode dextension. Il ntend pas le type IEnumerable<T>.
INFO Loprateur Empty nest pas une mthode dextension. Cest une mthode statique appele dans lassembly System.Linq.Enumerable.

Exceptions Aucune. Exemples Cet exemple gnre une squence de type String par lintermdiaire de loprateur Empty. La squence gnre ainsi que son nombre dlments sont ensuite afchs dans la console.
Listing 4.49 : Gnration dune squence vide de String.
IEnumerable<string> strings = Enumerable.Empty<string>(); foreach(string s in strings)

Chapitre 4

Les oprateurs diffrs

129

Console.WriteLine(s); Console.WriteLine(strings.Count());

Voici le rsultat afch dans la console :


0

Comme vous le voyez, la boucle foreach ne produit aucun rsultat. Ceci est normal, puisquil ny a aucun lment afcher.

Rsum
Ce chapitre a illustr la plupart des prototypes des oprateurs diffrs, du plus simple au plus complexe. En isolant les oprateurs de requte standard diffrs de leurs acolytes non diffrs, jai mis laccent sur limpact que pouvait avoir lexcution non instantane dune requte. Au chapitre suivant, vous dcouvrirez les oprateurs de requte standard non diffrs. Ce sera le dernier chapitre ddi LINQ to Objects.

5
Les oprateurs non diffrs
Au chapitre prcdent, nous nous sommes intresss aux oprateurs de requte diffrs. Ces oprateurs sont faciles identier, car ils retournent un IEnumerable<T> ou un OrderedSequence<T>. Nous allons maintenant nous intresser aux oprateurs de requte standard non diffrs. Ces oprateurs sont faciles reconnatre, car le rsultat retourn a un type diffrent de IEnumerable<T> et OrderedSequence<T>. Pour pouvoir excuter les exemples de ce chapitre, assurez-vous que vous avez rfrenc les espaces de noms (directive using), les assemblies et les codes communs ncessaires.

Espaces de noms rfrencs


Les exemples de ce chapitre vont utiliser les espaces de noms System.Linq, System.Collections et System.Collections.Generic. Si elles ne sont pas dj prsentes, vous devez donc ajouter les directives using suivantes dans votre code :
using System.Linq; using System.Collections; using System.Collections.Generic;

Si vous parcourez le code source mis disposition sur le site www.pearson.fr, vous verrez que jai galement ajout une directive using sur lespace de noms System.Diagnostic. Cette directive nest pas ncessaire si vous saisissez directement les exemples de ce chapitre. Elle nest l que pour les besoins propres du code source.

Classes communes
Pour fonctionner entirement, certains exemples de ce chapitre ncessitent des classes additionnelles. Cette section dcrit les quatre classes qui seront utilises par certains exemples de ce chapitre.

132

LINQ to Objects

Partie II

La classe Employee permet de travailler sur les employs dune entreprise. Elle contient des mthodes statiques qui retournent un tableau demploys de type ArrayList.
public class Employee { public int id; public string firstName; public string lastName; public static ArrayList GetEmployeesArrayList() { ArrayList al = new ArrayList(); al.Add(new Employee al.Add(new Employee al.Add(new Employee al.Add(new Employee al.Add(new Employee return (al); } public static Employee[] GetEmployeesArray() { return ((Employee[])GetEmployeesArrayList().ToArray()); } } { { { { { id id id id id = = = = = 1, firstName = 2, firstName = 3, firstName = 4, firstName = 101, firstName "Joe", lastName = "Rattz" }); "William", lastName = "Gates" }); "Anders", lastName = "Hejlsberg" }); "David", lastName = "Lightman" }); = "Kevin", lastName = "Flynn" });

La classe EmployeeOptionEntry reprsente le montant des stock-options des employs. Elle contient une mthode statique qui retourne un tableau de stock-options.
public class EmployeeOptionEntry { public int id; public long optionsCount; public DateTime dateAwarded; public static EmployeeOptionEntry[] GetEmployeeOptionEntries() { EmployeeOptionEntry[] empOptions = new EmployeeOptionEntry[] { new EmployeeOptionEntry { id = 1, optionsCount = 2, dateAwarded = DateTime.Parse("1999/12/31") }, new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("1992/06/30") }, new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("1994/01/01") }, new EmployeeOptionEntry { id = 3, optionsCount = 5000, dateAwarded = DateTime.Parse("1997/09/30") }, new EmployeeOptionEntry { id = 2, optionsCount = 10000, dateAwarded = DateTime.Parse("2003/04/01") }, new EmployeeOptionEntry { id = 3, optionsCount = 7500, dateAwarded = DateTime.Parse("1998/09/30") },

Chapitre 5

Les oprateurs non diffrs

133

new EmployeeOptionEntry { id = 3, optionsCount = 7500, dateAwarded = DateTime.Parse("1998/09/30") }, new EmployeeOptionEntry { id = 4, optionsCount = 1500, dateAwarded = DateTime.Parse("1997/12/31") }, new EmployeeOptionEntry { id = 101, optionsCount = 2, dateAwarded = DateTime.Parse("1998/12/31") } }; return (empOptions); } }

Plusieurs oprateurs utilisent des classes qui implmentent linterface IEqualityComparer<T>. Ceci an de tester lgalit entre deux lments. Cette interface est utile lorsque le terme "galit" doit tre pris au sens large. Par exemple, deux chanes peuvent tre considres gales, mme si leur casse diffre. Linterface IEqualityComparer<T> ayant t aborde en dtail au chapitre prcdent, nous ny reviendrons pas. Dans les exemples de ce chapitre, nous aurons besoin dune classe permettant de comparer plusieurs nombres stocks dans des chanes de caractres. Ainsi, par exemple, les chanes "17" et "00017" seront considres comme gales. La classe MyStringifieldNumberComparer se chargera de ce type de comparaison.
public class MyStringifiedNumberComparer : IEqualityComparer<string> { public bool Equals(string x, string y) { return(Int32.Parse(x) == Int32.Parse(y)); } public int GetHashCode(string obj) { return Int32.Parse(obj).ToString().GetHashCode(); } }

Cette implmentation de linterface IEqualityComparer ne fonctionne que sur des variables de type string. La technique utilise consiste convertir les valeurs string en int32. Ainsi, par exemple, la valeur "002" sera convertie en un entier de valeur 2, et les ventuels zros en tte de la chane naffecteront pas la conversion. Dans plusieurs exemples de ce chapitre, nous aurons besoin dune classe dans laquelle le champ cl des enregistrements nest pas forcment unique. La classe Actor a t cre dans ce but (le champ birthYear sera utilis comme cl).
public class Actor { public int birthYear; public string firstName; public string lastName;

134

LINQ to Objects

Partie II

public static Actor[] GetActors() { Actor[] actors = new Actor[] { new Actor { birthYear = 1964, new Actor { birthYear = 1968, new Actor { birthYear = 1960, new Actor { birthYear = 1964, }; return (actors); } }

firstName firstName firstName firstName

= = = =

"Keanu", lastName = "Reeves" }, "Owen", lastName = "Wilson" }, "James", lastName = "Spader" }, "Sandra", lastName = "Bullock" },

Les oprateurs non diffrs, par groupes fonctionnels


Dans cette section, nous avons organis les diffrents oprateurs de requte standard non diffrs par grands groupes fonctionnels. Oprateurs de conversion Les oprateurs de conversion sont utiliss pour convertir des squences dans des collections dun autre type.
Loprateur ToArray Loprateur ToArray cre un tableau de type T partir dune squence dentre de type T.

Prototype Un seul prototype de loprateur ToArray sera tudi dans ce livre :


public static T[] ToArray<T>( this IEnumerable<T> source);

Ce prototype admet un seul paramtre : une squence source dlments de type T. Il renvoie un tableau dlments de type T. Exceptions Lexception ArgumentNullExpression est leve si largument a pour valeur null. Exemples Nous allons crer une squence IEnumerable<T> en appliquant loprateur OfType un tableau. Une fois la squence obtenue, nous la passerons loprateur ToArray pour placer les diffrents lments dans un tableau (voir Listing 5.1).
Listing 5.1 : Un exemple dappel loprateur ToArray.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson",

Chapitre 5

Les oprateurs non diffrs

135

"Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string[] names = presidents.OfType<string>().ToArray(); foreach (string name in names) Console.WriteLine(name);

Dans un premier temps, le tableau presidents est converti en une squence IEnumerable<string> avec loprateur OfType. Dans un second temps, cette squence est convertie en un tableau en utilisant loprateur ToArray. Le tableau est immdiatement initialis, car ToArray est un oprateur non diffr. Voici le rsultat afch dans la console :
Adams Arthur Buchanan Bush Carter Cleveland Clinton Coolidge Eisenhower Fillmore Ford Garfield Grant Harding Harrison Hayes Hoover Jackson Jefferson Johnson Kennedy Lincoln Madison McKinley Monroe Nixon Pierce Polk Reagan Roosevelt Taft Taylor Truman Tyler Van Buren Washington Wilson

Vous aurez certainement remarqu que ce code est redondant. En effet, le tableau presidents est dj une squence, puisque dans C# 3.0 les tableaux implmentent linterface IEnumerable<T>. Lappel loprateur ToArray aurait donc pu tre vit.

136

LINQ to Objects

Partie II

Mais alors quauriez-vous pens de ce code qui se serait content de convertir un tableau en un tableau ? Loprateur ToArray a deux avantages : il permet de mmoriser une squence jusqu son numration et de sassurer que plusieurs numrations du tableau travailleront sur les mmes donnes.
Oprateur ToList Loprateur ToList cre une liste dlments de type T partir dune squence dentre de type T.

Prototype Un seul prototype de loprateur ToList sera tudi dans ce livre :


public static List<T> ToList<T>( this IEnumerable<T> source);

Cet oprateur admet un argument : une squence dentre source de type T. Il renvoie une liste dlments de type T. Exceptions Lexception ArgumentNullExpression est leve si largument source a pour valeur null. Exemples
Listing 5.2 : Un appel loprateur ToList.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; List<string> names = presidents.ToList(); foreach (string name in names) Console.WriteLine(name);

Ce code utilise les mmes donnes que lexemple prcdent. Mais, ici, loprateur OfType nest pas appel pour crer une squence intermdiaire de type IEnumerable<T> : le tableau presidents est directement converti en une liste de type List<string>. Voici les rsultats afchs dans la console :
Adams Arthur

Chapitre 5

Les oprateurs non diffrs

137

Buchanan Bush Carter Cleveland Clinton Coolidge Eisenhower Fillmore Ford Garfield Grant Harding Harrison Hayes Hoover Jackson Jefferson Johnson Kennedy Lincoln Madison McKinley Monroe Nixon Pierce Polk Reagan Roosevelt Taft Taylor Truman Tyler Van Buren Washington Wilson

Tout comme ToArray, ToList a deux avantages : il permet de mmoriser une squence jusqu son numration et de sassurer que plusieurs numrations travailleront sur les mmes donnes.
Oprateur ToDictionary Cet oprateur admet au minimum deux paramtres en entre : une squence dentre de type T et une cl de type K. Il cre un dictionnaire de type <K, T>.

Si largument facultatif elementSelector est spci dans le prototype, le dictionnaire cr est de type <K,E>. Les valeurs stockes sont de type E, diffrent du type dentre T.
INFO Si la classe C# Dictionary ne vous est pas familire, sachez quelle permet de mmoriser des couples lment/cl (o cl est unique pour chaque lment). Pour retrouver un lment dans la liste, il suft dindexer le tableau en utilisant la cl.

138

LINQ to Objects

Partie II

Prototypes Quatre prototypes de loprateur ToDictionary seront tudis dans ce livre. Premier prototype
public static Dictionary<K, T> ToDictionary<T, K>( this IEnumerable<T> source, Func<T, K> keySelector);

Ce prototype cre un dictionnaire de type <K, T> en numrant la squence dentre source. Le dlgu keySelector est appel pour obtenir une valeur cl pour chaque lment (cest cette valeur qui sera inscrite dans le dictionnaire). Les lments stocks dans le dictionnaire sont de mme type que ceux de la squence dentre. Aucun comparateur ntant spci dans le prototype, cest le comparateur par dfaut, EqualityComparer<K>.Default, qui sera utilis. Le deuxime prototype est semblable au premier, mais il permet de spcier le comparateur utiliser. Deuxime prototype
public static Dictionary<K, T> ToDictionary<T, K>( this IEnumerable<T> source, Func<T, K> keySelector, IEqualityComparer<K> comparer);

Vous utiliserez ce prototype si le comparateur par dfaut, EqualityComparer<K>.Default, ne convient pas. Dans ce cas, le comparateur IEqualityComparer sera utilis pour tout ajout ou lecture dlment dans le dictionnaire. La classe StringComparer implmente plusieurs classes de comparaison. Lune dentre elles, par exemple, ignore la casse des chanes compares. Ainsi, les cls "Joe" et "joe" seront considres comme gales. Le troisime prototype est semblable au premier, mais il ajoute un slectionneur dlment. Par son intermdiaire, les valeurs stockes dans le dictionnaire peuvent tre dun autre type que celles de la squence dentre. Troisime prototype
public static Dictionary<K, E> ToDictionary<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elementSelector);

Largument elementSelector fait rfrence un dlgu qui retourne un fragment de llment soumis, ou un objet dun tout autre type. Cest cet lment qui sera stock dans le dictionnaire. Le quatrime prototype cumule les avantages des deux prcdents. Par son intermdiaire, vous pouvez spcier un elementSelector et un comparateur. Quatrime prototype
public static Dictionary<K, E> ToDictionary<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector,

Chapitre 5

Les oprateurs non diffrs

139

Func<T, E> elementSelector, IEqualityComparer<K> comparer);

Exceptions Lexception ArgumentNullExpression est leve si largument source, keySelector ou elementSelector, a pour valeur null ou si la cl retourne par keySelector a pour valeur null. Lexception ArgumentException est leve si un slecteur retourne la mme cl pour deux lments. Exemples Dans cet exemple, nous utiliserons la classe commune Employee. Nous allons crer un dictionnaire de type Dictionary<int, Employee>. La cl int reprsentera lidentiant id de lemploy et lobjet Employee, llment stock dans le dictionnaire.
Listing 5.3 : Un exemple dutilisation du premier prototype de loprateur ToDictionary.
Dictionary<int, Employee> eDictionary = Employee.GetEmployeesArray().ToDictionary(k => k.id); Employee e = eDictionary[2]; Console.WriteLine("Employ dont le champ id vaut 2 : {0} {1}", e.firstName, e.lastName);

Le champ id est utilis comme cl. Le premier argument de Dictionary est donc de type int. Ce prototype tant limit lenregistrement intgral des donnes qui lui sont passes, le deuxime argument est de type Employee. En fournissant lidentiant dun employ, le prototype Dictionary<int, Employee> donne donc accs aux donnes correspondantes. Voici le rsultat afch dans la console :
Employ dont le champ id vaut 2 : William Gates

Pour illustrer le deuxime prototype, nous avons besoin dune situation dans laquelle lutilisation dun comparateur personnalis se justie. Supposons que la cl soit une valeur numrique stocke dans une chane. Les valeurs "1", "01", "001", etc. ne sont pas identiques, mme si elles reprsentent le mme nombre. Nous devons donc utiliser un comparateur qui autorise ce type de "largesse dcriture". Nous allons lgrement modier la classe commune Employee pour quelle admette une cl de type string. Cette modication va donner naissance la classe Employee2. La classe utilise par le deuxime prototype de loprateur ToDictionary
public class Employee2 { public string id; public string firstName; public string lastName; public static ArrayList GetEmployeesArrayList()

140

LINQ to Objects

Partie II

{ ArrayList al = new ArrayList(); al.Add(new Employee2 { id = "1", firstName = "Joe", lastName = "Rattz" }); al.Add(new Employee2 { id = "2", firstName = "William", lastName = "Gates" }); al.Add(new Employee2 { id = "3", firstName = "Anders", lastName = "Hejlsberg" }); al.Add(new Employee2 { id = "4", firstName = "David", lastName = "Lightman" }); al.Add(new Employee2 { id = "101", firstName = "Kevin", lastName = "Flynn" }); return (al); } public static Employee2[] GetEmployeesArray() { return ((Employee2[])GetEmployeesArrayList().ToArray(typeof(Employee2))); } }

Le type de la cl a t modi dans un but purement dmonstratif, an dtayer le fonctionnement du comparateur MyStringifieldNumberComparer. Ce dernier considrera comme gales deux cls qui, littralement, ne le sont pas. Voyons maintenant comment utiliser la classe Employee2 (voir Listing 5.4).
Listing 5.4 : Un exemple dutilisation du deuxime prototype de loprateur ToDictionary.
Dictionary<string, Employee2> eDictionary = Employee2.GetEmployeesArray() .ToDictionary(k => k.id, new MyStringifiedNumberComparer()); Employee2 e = eDictionary["2"]; Console.WriteLine("Employ dont le champ id vaut \"2\" : {0} {1}", e.firstName, e.lastName); e = eDictionary["000002"]; Console.WriteLine("Employ dont le champ id vaut \"000002\" : {0} {1}", e.firstName, e.lastName);

Dans cet exemple, nous tentons daccder llment du dictionnaire dont la cl a pour valeur "2", puis "000002". Si la classe de comparaison fonctionne, ces deux cls devraient pointer vers le mme employ. Voici les rsultats :
Employ dont le champ id vaut "2" : William Gates Employ dont le champ id vaut "000002" : William Gates

Les deux cls ayant une mme valeur numrique, elles renvoient vers la mme entre dans le dictionnaire. Le troisime prototype permet de stocker dans le dictionnaire un lment dun autre type que celui de la squence dentre. Pour illustrer son fonctionnement, nous allons travailler avec la classe Employee (voir Listing 5.5).
Listing 5.5 : Un exemple dutilisation du troisime prototype de loprateur ToDictionary.
Dictionary<int, string> eDictionary = Employee.GetEmployeesArray() .ToDictionary(k => k.id,

Chapitre 5

Les oprateurs non diffrs

141

i => string.Format("{0} {1}", // elementSelector i.firstName, i.lastName)); string name = eDictionary[2]; Console.WriteLine("Employ dont le champ id vaut 2 : {0}", name);

Dans cet exemple, une expression lambda concatne les champs firstName et lastName et les stocke dans une chane. La squence dentre est de type Employee, mais cest un type string qui est stock dans le dictionnaire. Voici le rsultat :
Employ dont le champ id vaut 2 : William Gates

Pour illustrer le quatrime prototype, nous allons utiliser la classe Employee2 et la classe commune MyStringfieldNumberComparer (voir Listing 5.6).
Listing 5.6 : Un exemple dutilisation du quatrime prototype de loprateur ToDictionary.
Dictionary<string, string> eDictionary = Employee2.GetEmployeesArray() .ToDictionary(k => k.id, // slection de la cl i => string.Format("{0} {1}", // slection de llment i.firstName, i.lastName), new MyStringifiedNumberComparer()); // comparateur string name = eDictionary["2"]; Console.WriteLine("Employ dont le champ id vaut \"2\" : {0}", name); name = eDictionary["000002"]; Console.WriteLine("Employ dont le champ id vaut \"000002\" : {0}", name);

Dans ce code :
m m

le slecteur elementSelector stocke des valeurs chanes dans le dictionnaire ; le comparateur MyStringifiedNumberComparer est utilis pour trouver un lment dans le dictionnaire.

Les deux derniers blocs recherchent lemploy dont lidentiant vaut " 2", puis "000002". Les chanes renvoyes sont identiques puisque le comparateur considre ces deux chanes comme gales. Voici le rsultat :
Employ dont le champ id vaut 2 : William Gates Employ dont le champ id vaut 000002 : William Gates

Oprateur ToLookup Cet oprateur admet au minimum deux paramtres en entre : une squence dentre de type T et une cl de type K. Il cre un objet Lookup de type <K, T>.

Si largument facultatif elementSelector est spci dans le prototype, lobjet Lookup cr est de type <K, E>. Les valeurs stockes sont de type E, diffrent du type dentre T. Tous les prototypes de loprateur ToLookup crent un objet Lookup qui implmente linterface ILookup. Nous leur ferons souvent rfrence en utilisant le simple mot "Lookup".

142

LINQ to Objects

Partie II

INFO Si la classe C# Lookup ne vous est pas familire, sachez quelle permet de mmoriser des couples lment/cl (o cl nest pas forcment unique pour chaque lment). Pour retrouver le ou les lments qui correspondent une cl, il suft dindexer le tableau en utilisant cette cl.

Prototypes Quatre prototypes de loprateur ToLookup seront tudis dans ce livre. Premier prototype
public static ILookup<K, T> ToLookup<T, K>( this IEnumerable<T> source, Func<T, K> keySelector);

Ce prototype cre un Lookup de type <K, T> en numrant la squence dentre, source. Le dlgu keySelector est appel pour extraire la valeur cl de chaque lment (cest cette valeur qui sera inscrite dans le Lookup). Les lments stocks dans le Lookup sont de mme type que ceux de la squence dentre. Aucun comparateur ntant spci dans le prototype, cest le comparateur par dfaut, EqualityComparer<K>.Default, qui sera utilis. Le deuxime prototype est semblable au premier, mais il permet de spcier le comparateur utiliser. Deuxime prototype
public static ILookup<K, T> ToLookup<T, K>( this IEnumerable<T> source, Func<T, K> keySelector, IEqualityComparer<K> comparer);

Vous utiliserez ce prototype si le comparateur par dfaut, EqualityComparer<K>.Default, ne convient pas. Dans ce cas, le comparateur IEqualityComparer sera utilis pour tout ajout ou lecture dlment dans le Lookup. La classe StringComparer implmente plusieurs classes de comparaison. Lune dentre elles, par exemple, ignore la casse des chanes compares. Ainsi, les cls "Joe" et "joe" seront considres comme gales. Le troisime prototype est semblable au premier, mais il ajoute un slectionneur dlment. Par son intermdiaire, les valeurs stockes dans le dictionnaire peuvent tre dun autre type que celles de la squence dentre. Troisime prototype
public static ILookup<K, E> ToLookup<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elementSelector);

Chapitre 5

Les oprateurs non diffrs

143

Largument elementSelector fait rfrence un dlgu qui retourne un fragment de llment soumis, ou un objet dun tout autre type. Cest cet lment qui sera stock dans le dictionnaire. Le quatrime prototype cumule les avantages des deux prcdents. Par son intermdiaire, vous pouvez spcier un elementSelector et un comparateur. Quatrime prototype
public static ILookup<K, E> ToLookup<T, K, E>( this IEnumerable<T> source, Func<T, K> keySelector, Func<T, E> elementSelector, IEqualityComparer<K> comparer);

Exceptions Lexception ArgumentNullExpression est leve si largument source, keySelector ou elementSelector, a pour valeur null ou si la cl retourne par keySelector a pour valeur null. Lexception ArgumentException est leve si un slecteur retourne la mme cl pour deux lments. Exemples Pour illustrer le premier prototype de loprateur ToLookup, nous avons besoin dune classe dont les lments contiennent des membres qui peuvent tre utiliss comme cls, mais qui ne sont pas forcment uniques. Nous utiliserons pour cela la classe Actor (voir Listing 5.7).
Listing 5.7 : Un exemple dappel du premier prototype de loprateur ToLookup.
ILookup<int, Actor> lookup = Actor.GetActors().ToLookup(k => k.birthYear); // Recherche dun acteur n en 1964 IEnumerable<Actor> actors = lookup[1964]; foreach (var actor in actors) Console.WriteLine("{0} {1}", actor.firstName, actor.lastName);

La premire instruction cre un Lookup en utilisant le membre Actor.birthYear comme cl. La deuxime instruction indexe le Lookup en utilisant la cl. Il ne reste plus qu numrer lobjet actors pour afcher le ou les rsultats :
Keanu Reeves Sandra Bullock

Pour illustrer le deuxime prototype, nous allons lgrement modier la classe Actor. Son membre birthYear, initialement de type int, sera de type string dans la classe modie.

144

LINQ to Objects

Partie II

La classe utilise par le deuxime prototype de loprateur ToLookup


public class Actor2 { public string birthYear; public string firstName; public string lastName; public static Actor2[] GetActors() { Actor2[] actors = new Actor2[] { new Actor2 { birthYear = "1964", firstName = "Keanu", lastName = "Reeves" }, new Actor2 { birthYear = "1968", firstName = "Owen", lastName = "Wilson" }, new Actor2 { birthYear = "1960", firstName = "James", lastName = "Spader" }, // Une date exprime sur 5 chiffres new Actor2 { birthYear = "01964", firstName = "Sandra", lastName = "Bullock" }, }; return(actors); } }

Le membre birthYear est maintenant une chane de caractres. Il ne reste plus qu appeler loprateur ToLookup (voir Listing 5.8).
Listing 5.8 : Un exemple dutilisation du deuxime prototype de loprateur ToLookup.
ILookup<string, Actor2> lookup = Actor2.GetActors() .ToLookup(k => k.birthYear, new MyStringifiedNumberComparer()); // Recherche dun acteur n en 1964 IEnumerable<Actor2> actors = lookup["0001964"]; foreach (var actor in actors) Console.WriteLine("{0} {1}", actor.firstName, actor.lastName);

La mthode de comparaison est la mme que celle qui avait t utilise pour illustrer loprateur Dictionary. En effet, lventuel ou les ventuels "0" en tte de cl ntant pas signicatifs, il est ncessaire de tester lgalit "au sens large". Voici les rsultats :
Keanu Reeves Sandra Bullock

La recherche dlments dont la cl vaut "0001964" retourne les acteurs Keanu Reeves et Sandra Bullock, dont les cls respectives valent "1964" et "01964". Lobjet de comparaison a donc bien fonctionn. Pour illustrer le troisime prototype, nous ferons appel la classe Actor, qui avait dj t utilise dans lexemple du premier prototype (voir Listing 5.9).
Listing 5.9 : Un exemple dutilisation du troisime prototype de loprateur ToLookup.
ILookup<int, string> lookup = Actor.GetActors() .ToLookup(k => k.birthYear, a => string.Format("{0} {1}", a.firstName, a.lastName)); // Recherche dun acteur n en 1964 IEnumerable<string> actors = lookup[1964];

Chapitre 5

Les oprateurs non diffrs

145

foreach (var actor in actors) Console.WriteLine("{0}", actor);

Dans cet exemple, largument elementSelector est une expression lambda qui concatne les champs firstName et lastName. Voici le rsultat :
Keanu Reeves Sandra Bullock

En utilisant cette troisime variante de loprateur ToLookup, le type de donnes mmorises dans lobjet Lookup (string) est diffrent de celui des lments passs en entre (Actor). Pour illustrer le quatrime prototype, nous allons utiliser la classe Actor2 et la classe commune MyStringfieldNumberComparer (voir Listing 5.10).
Listing 5.10 : Un exemple dappel du quatrime prototype de loprateur ToLookup.
ILookup<string, string> lookup = Actor2.GetActors() .ToLookup(k => k.birthYear, a => string.Format("{0} {1}", a.firstName, a.lastName), new MyStringifiedNumberComparer()); // Recherche dun acteur n en 1964 IEnumerable<string> actors = lookup["0001964"]; foreach (var actor in actors) Console.WriteLine("{0}", actor);

Voici le rsultat :
Keanu Reeves Sandra Bullock

Cet exemple recherche des lments dont la cl vaut "0001964". Les acteurs Keanu Reeves et Sandra Bullock, dont les cls respectives valent "1964" et "01964", correspondent au critre. La comparaison a donc bien fonctionn. Par ailleurs, seules les chanes ncessaires la requte (firstName et lastName) sont stockes dans le Lookup. Oprateurs dgalit Les oprateurs de cette catgorie sont utiliss pour tester lgalit entre deux squences.
Oprateur SequenceEqual Loprateur SequenceEqual dtermine si deux squences dentre sont gales.

Prototypes Deux prototypes de loprateur SequenceEqual seront tudis dans ce livre. Premier prototype
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second);

146

LINQ to Objects

Partie II

Cet oprateur numre les deux squences en parallle et compare leurs lments en utilisant la mthode System.Object.Equals. Si tous les lments sont gaux et si les deux squences ont le mme nombre dlments, loprateur retourne la valeur true. Dans le cas contraire, il retourne la valeur false. Second prototype
public static bool SequenceEqual<T>( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Exemples
Listing 5.11 : Un exemple dutilisation du premier prototype de loprateur SequenceEqual.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool eq = presidents.SequenceEqual(presidents); Console.WriteLine(eq);

Voici le rsultat :
True

Ceci vous semble un peu trop simple ? Nous allons lgrement compliquer les choses dans le Listing 5.12.
Listing 5.12 : Un autre exemple dutilisation du premier prototype de loprateur SequenceEqual.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool eq = presidents.SequenceEqual(presidents.Take(presidents.Count())); Console.WriteLine(eq);

Chapitre 5

Les oprateurs non diffrs

147

Si lcriture vous semble plus complexe, un rapide examen va vous persuader du contraire. Loprateur Take limite la comparaison tous les lments du tableau presidents (presidents.Count()). Ce code est donc strictement quivalent au prcdent et, bien entendu, il produit le mme rsultat :
True

Nous allons maintenant comparer le tableau presidents avec ses presidents.Count() 1 premiers lments (voir Listing 5.13).
Listing 5.13 : Un autre exemple dutilisation du premier prototype de loprateur SequenceEqual.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool eq = presidents.SequenceEqual(presidents.Take(presidents.Count() - 1)); Console.WriteLine(eq);

Voici le rsultat :
False

Les deux squences nayant pas le mme nombre dlments, il est tout fait normal que la valeur False soit retourne. Au chapitre prcdent, lors de ltude des oprateurs Take et Skip, il a t dit que, si ces oprateurs taient utiliss correctement, ils permettaient de retrouver la squence originale. Nous allons maintenant le prouver en leur adjoignant les oprateurs Concat et SequenceEqual (voir Listing 5.14).
Listing 5.14 : Un exemple plus complexe du premier prototype de loprateur SequenceEqual.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool eq = presidents.SequenceEqual(presidents.Take(5).Concat(presidents.Skip(5))); Console.WriteLine(eq);

Dans cet exemple, Take(5) extrait les cinq premiers lments de la squence originale. Ces lments sont alors concatns la squence originale (Concat) en sautant les cinq

148

LINQ to Objects

Partie II

premiers lments (Skip(5)). La squence obtenue est compare la squence originale (presidents.SequenceEqual()). Comme il se doit, la valeur True est retourne par loprateur SequenceEqual :
True

Pour illustrer le second prototype, nous allons utiliser deux tableaux de string dont chaque lment est un nombre exprim sous la forme dune chane. Les lments des deux tableaux seront dnis de telle sorte quils soient gaux, aprs conversion en entiers. Pour effectuer la comparaison, nous utiliserons la classe MyStringifieldNumberComparer (voir Listing 5.15).
Listing 5.15 : Un exemple du second prototype de loprateur SequenceEqual.
string[] stringifiedNums1 = { "001", "49", "017", "0080", "00027", "2" }; string[] stringifiedNums2 = { "1", "0049", "17", "080", "27", "02" }; bool eq = stringifiedNums1.SequenceEqual(stringifiedNums2, new MyStringifiedNumberComparer()); Console.WriteLine(eq);

En examinant rapidement les deux tableaux, vous pouvez voir que leurs lments sont gaux, aprs conversion en entiers. Voici le rsultat :
True

Oprateurs agissant au niveau des lments Cette catgorie doprateurs vous permet dobtenir des lments partir de la squence dentre.
Oprateur First Selon le prototype utilis, loprateur First retourne le premier lment de la squence dentre ou de la squence correspondant un prdicat.

Prototypes Deux prototypes de loprateur First seront tudis dans ce livre. Premier prototype
public static T First<T>( this IEnumerable<T> source);

Ce prototype retourne le premier lment de la squence dentre source.

Chapitre 5

Les oprateurs non diffrs

149

Second prototype
public static T First<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce prototype retourne le premier lment de la squence dentre pour lequel le prdicat vaut true. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception InvalidOperationException est leve si le prdicat ne retourne la valeur true pour aucun des lments de la squence dentre. Exemples
Listing 5.16 : Un exemple dutilisation du premier prototype de loprateur First.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.First(); Console.WriteLine(name);

Voici le rsultat :
Adams

Vous vous demandez peut-tre si les oprateurs First et Take(1) sont diffrents ? Eh bien, oui ! Loprateur Take retourne une squence dlments (y compris dans le cas o cette squence ne contient quun seul lment). En revanche, loprateur First retourne un lment ou gnre une exception si aucun lment ne peut tre retourn.
Listing 5.17 : Un exemple dutilisation du deuxime prototype de loprateur First.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.First(p => p.StartsWith("H")); Console.WriteLine(name);

150

LINQ to Objects

Partie II

Ce code devrait retourner le premier lment de la squence dentre qui commence par la lettre "H". Voici le rsultat :
Harding

Si aucun lment ne peut tre renvoy par loprateur First, une exception InvalidOperationException est leve. Pour viter ce problme, utilisez loprateur FirstOrDefault.
Oprateur FirstOrDefault Loprateur FirstOrDefault est semblable loprateur First, except en ce qui concerne son comportement lorsque aucun lment nest trouv.

Prototypes Deux prototypes de loprateur FirstOrDefault seront tudis dans ce livre. Premier prototype
public static T FirstOrDefault<T>( this IEnumerable<T> source);

Ce prototype retourne le premier lment de la squence dentre source. Si la squence dentre est vide, lobjet default(T) est retourn (la valeur par dfaut des types rfrence et nullable est null). Second prototype
public static T FirstOrDefault<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce prototype retourne le premier lment de la squence dentre pour lequel le prdicat vaut true. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Exemples
Listing 5.18 : Appel du premier prototype de loprateur FirstOrDefault. Llment recherch nest pas trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"};

Chapitre 5

Les oprateurs non diffrs

151

string name = presidents.Take(0).FirstOrDefault(); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
NULL

Listing 5.19 : Appel du premier prototype de loprateur FirstOrDefault. Llment recherch est trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.FirstOrDefault(); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
Adams

Pour illustrer le second prototype de loprateur FirstOrDefault, nous allons rechercher le premier lment qui commence par la lettre "B".
Listing 5.20 : Appel du second prototype. Llment recherch est trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.FirstOrDefault(p => p.StartsWith("B")); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
Buchanan

Listing 5.21 : Appel du second prototype. Llment recherch nest pas trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.FirstOrDefault(p => p.StartsWith("Z")); Console.WriteLine(name == null ? "NULL" : name);

152

LINQ to Objects

Partie II

Aucune rponse ntant trouve, voici le rsultat :


NULL

Oprateur Last Selon le prototype utilis, loprateur Last retourne le dernier lment de la squence dentre ou de la squence correspondant un prdicat.

Prototypes Deux prototypes de loprateur Last seront tudis dans ce livre. Premier prototype
public static T Last<T>( this IEnumerable<T> source);

Ce prototype retourne le dernier lment de la squence dentre source. Second prototype


public static T Last<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce prototype retourne le dernier lment de la squence dentre pour lequel le prdicat vaut true. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception InvalidOperationException est leve si le prdicat ne retourne la valeur true pour aucun des lments de la squence dentre. Exemples
Listing 5.22 : Un exemple dutilisation du premier prototype de loprateur Last.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.Last(); Console.WriteLine(name);

Voici le rsultat :
Wilson

Chapitre 5

Les oprateurs non diffrs

153

Listing 5.23 : Un exemple dutilisation du second prototype de loprateur Last.


string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.Last(p => p.StartsWith("H")); Console.WriteLine(name);

Ce code devrait retourner le dernier lment de la squence dentre qui commence par la lettre "H". Voici le rsultat :
Hoover

Si aucun lment ne peut tre renvoy par loprateur Last, une exception InvalidOperationException est leve. Pour viter ce problme, utilisez loprateur LastOrDefault.
Oprateur LastOrDefault Loprateur LastOrDefault est semblable loprateur Last, except en ce qui concerne son comportement lorsque aucun lment nest trouv.

Prototypes Deux prototypes de loprateur LastOrDefault seront tudis dans ce livre. Premier prototype
public static T LastOrDefault<T>( this IEnumerable<T> source);

Ce prototype retourne le dernier lment de la squence dentre source. Si la squence dentre est vide, lobjet default(T) est retourn (la valeur par dfaut des types rfrence et nullable est null). Second prototype
public static T LastOrDefault<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce prototype retourne le dernier lment de la squence dentre pour lequel le prdicat vaut true. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null.

154

LINQ to Objects

Partie II

Exemples
Listing 5.24 : Appel du premier prototype de loprateur LastOrDefault. Llment recherch nest pas trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.Take(0).LastOrDefault(); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
NULL

Listing 5.25 : Appel du premier prototype de loprateur LastOrDefault. Llment recherch est trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.LastOrDefault(); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
Wilson

Pour illustrer le second prototype de loprateur LastOrDefault, nous allons rechercher le dernier lment qui commence par la lettre "B".
Listing 5.26 : Appel du second prototype. Llment recherch est trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.LastOrDefault(p => p.StartsWith("B")); Console.WriteLine(name == null ? "NULL" : name);

Voici le rsultat :
Bush

Chapitre 5

Les oprateurs non diffrs

155

Listing 5.27 : Appel du second prototype. Llment recherch nest pas trouv.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string name = presidents.LastOrDefault(p => p.StartsWith("Z")); Console.WriteLine(name == null ? "NULL" : name);

Aucune rponse ntant trouve, voici le rsultat :


NULL

Oprateur Single Selon le prototype utilis, loprateur Single retourne le seul lment de la squence dentre, ou le seul lment de la squence dentre correspondant un prdicat.

Prototypes Deux prototypes de loprateur Single seront tudis dans ce livre. Premier prototype
public static T Single<T>( this IEnumerable<T> source);

Ce prototype numre la squence dentre source et renvoie lunique lment trouv. Second prototype
public static T Single<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Ce second prototype retourne lunique lment pour lequel le prdicat a pour valeur true. Lexception InvalidOperationException est leve si le prdicat ne retourne la valeur true pour aucun ou pour plusieurs des lments de la squence dentre. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception InvalidOperationException est leve si le prdicat ne retourne la valeur true pour aucun ou pour plusieurs des lments de la squence dentre, ou si la squence dentre est vide. Exemples
Listing 5.28 : Un exemple dutilisation du premier prototype de loprateur Single sur la classe commune Employee.
Employee emp = Employee.GetEmployeesArray() .Where(e => e.id == 3).Single(); Console.WriteLine("{0} {1}", emp.firstName, emp.lastName);

156

LINQ to Objects

Partie II

La requte retourne un seul et unique lment. Dans cet exemple, tout se passe bien, car un seul employ a un identiant gal 3 (Where(e => e.id == 3)). Voici le rsultat :
Anders Hejlsberg

Listing 5.29 : Un exemple dappel du second prototype de loprateur Single.


Employee emp = Employee.GetEmployeesArray() .Single(e => e.id == 3); Console.WriteLine("{0} {1}", emp.firstName, emp.lastName);

Ce code est quivalent au prcdent. Mais, ici, au lieu dinvoquer loprateur Where pour sassurer de lunicit de la rponse, un prdicat est pass en argument de loprateur Single. Voici le rsultat :
Anders Hejlsberg

Oprateur SingleOrDefault Loprateur SingleOrDefault est semblable loprateur Single, except en ce qui concerne son comportement lorsque aucun lment nest trouv.

Prototypes Deux prototypes de loprateur SingleOrDefault seront tudis dans ce livre. Premier prototype
public static T SingleOrDefault<T>( this IEnumerable<T> source);

Ce prototype numre la squence dentre source et renvoie lunique lment trouv. Si la squence est vide, lobjet default(T) est retourn (la valeur par dfaut des types rfrence et nullable est null). Si plusieurs lments sont trouvs, une exception InvalidOperationException est leve. Le second prototype de loprateur SingleOrDefault permet de spcier un prdicat pour indiquer quel lment doit tre retourn. Second prototype
public static T SingleOrDefault<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception InvalidOperationException est leve si le prdicat retourne la valeur true pour plusieurs des lments de la squence dentre.

Chapitre 5

Les oprateurs non diffrs

157

Exemples Le Listing 5.30 illustre le fonctionnement du premier prototype dans le cas o aucun lment nest trouv dans la squence dentre. Pour ce faire, nous utilisons loprateur Where en spciant une cl inexistante.
Listing 5.30 : Un exemple dutilisation du premier prototype de loprateur SingleOrDefault. Ici, aucun lment nest trouv.
Employee emp = Employee.GetEmployeesArray() .Where(e => e.id == 5).SingleOrDefault(); Console.WriteLine(emp == null ? "NULL" : string.Format("{0} {1}", emp.firstName, emp.lastName));

Ce code effectue une requte sur un employ dont lidentiant vaut 5, en sachant pertinemment quun tel identiant nexiste pas. Une squence vide est donc retourne. Voici le rsultat :
NULL

Le Listing 5.31 illustre le fonctionnement du premier prototype dans le cas o un lment est trouv dans la squence dentre. Pour ce faire, nous utilisons loprateur Where en spciant une cl existante et unique.
Listing 5.31 : Un exemple dutilisation du premier prototype de loprateur SingleOrDefault. Ici, un lment est trouv.
Employee emp = Employee.GetEmployeesArray() .Where(e => e.id == 4).SingleOrDefault(); Console.WriteLine(emp == null ? "NULL" : string.Format("{0} {1}", emp.firstName, emp.lastName));

Lidentiant spci dans loprateur Where existe et est unique. Voici le rsultat :
David Lightman

Pour illustrer le fonctionnement du second prototype, nous allons cette fois passer un prdicat loprateur SingleOrDefault en choisissant un identiant qui existe.
Listing 5.32 : Un exemple dutilisation du second prototype de loprateur SingleOrDefault. Ici, un lment est trouv.
Employee emp = Employee.GetEmployeesArray() .SingleOrDefault(e => e.id == 4); Console.WriteLine(emp == null ? "NULL" : string.Format("{0} {1}", emp.firstName, emp.lastName));

158

LINQ to Objects

Partie II

Ce code est quivalent au prcdent. Mais, ici, au lieu dinvoquer loprateur Where pour ltrer les donnes, un prdicat est pass comme argument de loprateur SingleOrDefault. Voici le rsultat :
David Lightman

Nous allons maintenant essayer un prdicat qui ne trouve aucune correspondance dans les donnes (voir Listing 5.33).
Listing 5.33 : Un exemple dutilisation du second prototype de loprateur SingleOrDefault. Ici, aucun lment nest trouv.
Employee emp = Employee.GetEmployeesArray() .SingleOrDefault(e => e.id == 5); Console.WriteLine(emp == null ? "NULL" : string.Format("{0} {1}", emp.firstName, emp.lastName));

Aucune rponse ntant trouve, voici le rsultat (remarquez quaucune exception na t gnre) :
NULL

Oprateur ElementAt Loprateur ElementAt retourne llment de la squence dentre dont lindex est spci.

Prototype Un seul prototype de loprateur ElementAt sera tudi dans ce livre :


public static T ElementAt<T>( this IEnumerable<T> source, int index);

Si la squence implmente IList<T>, linterface IList est utilise pour retrouver llment index. Dans le cas contraire, la squence est numre jusqu ce que llment index soit atteint. Une exception ArgumentOutOfRangeExeption est leve si lindex est ngatif ou suprieur au nombre dlments dans la squence.
INFO Dans le langage C#, le premier lment dun index a pour valeur zro et le dernier, le nombre dlments de la squence moins un.

Exceptions Lexception ArgumentNullExpression est leve si largument source a pour valeur null. Lexception ArgumentOutOfRangeExeption est leve si lindex est ngatif ou suprieur au nombre dlments dans la squence.

Chapitre 5

Les oprateurs non diffrs

159

Exemples
Listing 5.34 : Exemple dappel de loprateur ElementAt.
Employee emp = Employee.GetEmployeesArray() .ElementAt(3); Console.WriteLine("{0} {1}", emp.firstName, emp.lastName);

Llment de rang 3 (cest--dire le quatrime lment) a t demand. Voici le rsultat :


David Lightman

Oprateur ElementAtOrDefault Loprateur ElementAtOrDefault retourne llment de la squence dentre dont lindex est spci.

Prototype Un seul prototype de loprateur ElementAtOrDefault sera tudi dans ce livre :


public static T ElementAtOrDefault<T>( this IEnumerable<T> source, int index);

Si la squence implmente IList<T>, linterface IList est utilise pour retrouver llment index. Dans le cas contraire, la squence est numre jusqu ce que llment index soit atteint. Si lindex est ngatif, suprieur ou gal au nombre dlments dans la squence, lobjet default(T) est retourn (la valeur par dfaut des types rfrence et nullable est null). Cette seule caractristique diffrencie les oprateurs ElementAtOrDefault et ElementAt.
INFO Dans le langage C#, le premier lment dun index a pour valeur zro et le dernier, le nombre dlments de la squence moins un.

Exceptions Lexception ArgumentNullExpression est leve si largument source a pour valeur null. Exemples
Listing 5.35 : Exemple dappel de loprateur ElementAt avec un index valide.
Employee emp = Employee.GetEmployeesArray() .ElementAtOrDefault(3); Console.WriteLine(emp == null ? "NULL" : string.Format("{0} {1}", emp.firstName, emp.lastName));

160

LINQ to Objects

Partie II

Voici le rsultat :
David Lightman

Llment dont lindex vaut 3 est bien retourn par la requte. Nous allons maintenant transmettre un index invalide cette mme requte (voir Listing 5.36).
Listing 5.36 : Exemple dappel de loprateur ElementAt avec un index invalide.
Employee emp = Employee.GetEmployeesArray() .ElementAtOrDefault(5); Console.WriteLine(emp == null ? "NULL" :

tant donn que lindex 5 ne correspond aucun lment, voici le rsultat retourn :
NULL

Quanticateurs Les quanticateurs permettent de tester lexistence dune valeur dans une squence dentre.
Oprateur Any Loprateur Any retourne la valeur true si au moins un lment de la squence dentre vrie une condition.

Prototypes Deux prototypes de loprateur Any seront tudis dans ce livre. Premier prototype
public static bool Any<T>( this IEnumerable<T> source);

Ce prototype retourne la valeur true si la squence dentre contient au moins un lment. Second prototype
public static bool Any<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Le second prototype numre la squence dentre source. Il retourne la valeur true si le prdicat retourne la valeur true sur au moins un lment de la squence. Lnumration stoppe ds que cette condition est atteinte. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null.

Chapitre 5

Les oprateurs non diffrs

161

Exemples
Listing 5.37 : Exemple dappel du premier prototype avec une squence dentre vide.
bool any = Enumerable.Empty<string>().Any(); Console.WriteLine(any);

Voici le rsultat :
False

Listing 5.38 : Exemple dappel du premier prototype avec une squence dentre non vide.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool any = presidents.Any(); Console.WriteLine(any);

Voici le rsultat :
True

Listing 5.39 : Exemple dappel du second prototype. Ici, aucun lment ne correspond au prdicat.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool any = presidents.Any(s => s.StartsWith("Z")); Console.WriteLine(any);

Le prdicat limite la requte aux lments dont le nom commence par la lettre " Z". Comme aucun lment ne correspond, la valeur False est retourne :
False

Listing 5.40 : Exemple dappel du second prototype. Ici, au moins un lment correspond au prdicat.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool any = presidents.Any(s => s.StartsWith("A")); Console.WriteLine(any);

162

LINQ to Objects

Partie II

Au moins un lment du tableau presidents correspondant au prdicat, la valeur True est retourne :
True

Oprateur All Loprateur All retourne la valeur true si tous les lments de la squence dentre vrient une condition.

Prototype Un seul prototype de loprateur All sera tudi dans ce livre :


public static bool All<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Loprateur All numre la squence dentre. Il retourne la valeur true si le prdicat est vri sur tous les lments de la squence. Si le prdicat retourne la valeur false pour un lment, lnumration cesse immdiatement. Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Exemples
Listing 5.41 : Exemple dappel du prototype de loprateur All. Ici, le prdicat ne retourne pas la valeur True pour tous les lments.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool all = presidents.All(s => s.Length > 5); Console.WriteLine(all);

Tous les lments du tableau presidents nayant pas une longueur suprieure 5 caractres, le prdicat nest pas toujours vri. Le rsultat est sans appel :
False

Listing 5.42 : Exemple dappel du prototype de loprateur All. Ici, le prdicat retourne la valeur True pour tous les lments.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley",

Chapitre 5

Les oprateurs non diffrs

163

"Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool all = presidents.All(s => s.Length > 3); Console.WriteLine(all);

Les noms des prsidents comprenant un minimum de 4 caractres, le prdicat est vri pour tous les lments du tableau. Voici le rsultat :
True

Oprateur Contains Loprateur Contains retourne la valeur true si un des lments de la squence dentre vrie la condition.

Prototypes Deux prototypes de loprateur All seront tudis dans ce livre. Premier prototype
public static bool Contains<T>( this IEnumerable<T> source, T value);

Dans un premier temps, ce prototype teste si la squence dentre implmente linterface ICollection<T>. Dans lafrmative, la mthode Contains de cette interface est appele. Dans le cas contraire, la squence dentre est numre pour voir si un de ses lments vrie la condition. Ds quune telle situation est atteinte, lnumration prend n. La valeur spcie est compare aux lments de la squence dentre en utilisant la classe de comparaison par dfaut : EqualityComparer<K>.Default. Second prototype Le second prototype est en tout point comparable au premier, si ce nest quil permet de spcier un objet IEqualityComparer<T>. Dans ce cas, cest ce comparateur qui est utilis pour comparer les lments de la squence dentre :
public static bool Contains<T>( this IEnumerable<T> source, T value, IEqualityComparer<T> comparer);

Exceptions Lexception ArgumentNullExpression est leve si largument source a pour valeur null. Exemples
Listing 5.43 : Exemple dappel du premier prototype. La valeur spcifie ne se trouve pas dans la squence.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland",

164

LINQ to Objects

Partie II

"Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool contains = presidents.Contains("Rattz"); Console.WriteLine(contains);

Aucun lment contenant la valeur "Rattz" dans le tableau. Le rsultat est donc le suivant :
False

Listing 5.44 : Exemple dappel du premier prototype. La valeur spcifie se trouve dans la squence.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; bool contains = presidents.Contains("Hayes"); Console.WriteLine(contains);

Un des lments du tableau contenant la valeur "Hayes", le rsultat est le suivant :


True

Pour illustrer le second prototype, nous allons utiliser la classe commune MyStringifieldNumberComparer (voir Listing 5.45). La requte recherchera un nombre stock au format chane et prcd de plusieurs zros. Le comparateur ne prenant pas en considration les zros de tte, ce nombre sera retrouv dans le tableau.
Listing 5.45 : Exemple dappel du second prototype. La valeur spcifie est trouve dans la squence.
string[] stringifiedNums = { "001", "49", "017", "0080", "00027", "2" }; bool contains = stringifiedNums.Contains("0000002", new MyStringifiedNumberComparer()); Console.WriteLine(contains);

Le comparateur convertit la chane recherche en un nombre. Les zros de tte disparaissent et la valeur est trouve dans la squence. La variable contains devrait donc avoir pour valeur true. Voici le rsultat :
True

Chapitre 5

Les oprateurs non diffrs

165

Nous allons maintenant rechercher un lment inexistant dans la squence dentre (voir Listing 5.46).
Listing 5.46 : Exemple dappel du second prototype. La valeur spcifie nest pas trouve dans la squence.
string[] stringifiedNums = { "001", "49", "017", "0080", "00027", "2" }; bool contains = stringifiedNums.Contains("000271", new MyStringifiedNumberComparer()); Console.WriteLine(contains);

Llment "000271" ntant pas trouv dans la squence dentre, voici le rsultat :
False

Fonctions de comptage Les oprateurs de ce groupe effectuent des comptes (nombre dlments, somme, minimum, maximum) sur les lments de la squence dentre.
Oprateur Count Loprateur Count retourne le nombre dlments de la squence dentre.

Prototypes Deux prototypes de loprateur Count seront tudis dans ce livre. Premier prototype
public static int Count<T>( this IEnumerable<T> source);

Ce prototype teste si la squence dentre implmente linterface ICollection<T>. Dans lafrmative, il obtient le nombre dlments de la squence en utilisant la fonction de comptage de cette interface. Dans la ngative, le nombre dlments est obtenu en numrant la squence dentre. Le second prototype renvoie le nombre dlments de la squence dentre pour lesquels le prdicat retourne la valeur true. Second prototype
public static int Count<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null.

166

LINQ to Objects

Partie II

Lexception OverflowException est leve si le nombre dlments est suprieur la valeur maximale autorise par int.MaxValue. Exemples Lexemple du Listing 5.47 compte le nombre de prsidents stocks dans la squence dentre.
Listing 5.47 : Exemple dappel du premier prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; int count = presidents.Count(); Console.WriteLine(count);

Voici le rsultat :
37

Lexemple du Listing 5.48 compte le nombre de prsidents stocks dans la squence dentre dont le nom commence par la lettre "J".
Listing 5.48 : Exemple dappel du second prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; int count = presidents.Count(s => s.StartsWith("J")); Console.WriteLine(count);

Voici le rsultat :
3

Si le nombre dlments dpasse la capacit de int.MaxValue, vous utiliserez loprateur LongCount.


Oprateur LongCount Loprateur Count retourne le nombre dlments de la squence dentre au format long.

Chapitre 5

Les oprateurs non diffrs

167

Prototypes Deux prototypes de loprateur LongCount seront tudis dans ce livre. Premier prototype
public static long LongCount<T>( this IEnumerable<T> source);

Le premier prototype numre la squence dentre et retourne le nombre dlments compts. Le second prototype renvoie le nombre dlments de la squence dentre pour lesquels le prdicat retourne la valeur true. Second prototype
public static long LongCount<T>( this IEnumerable<T> source, Func<T, bool> predicate);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Exemples Dans lexemple du Listing 5.49, nous utilisons un oprateur de requte standard pour gnrer une squence pour laquelle loprateur Count produirait une exception de type OverflowException. Au chapitre prcdent, nous avons tudi loprateur Range, qui permettait de crer une squence en spciant son nombre dlments sous la forme dun int. Nous allons concatner deux de ces squences pour dpasser les capacits du type int, et cela va donc ncessiter lutilisation de loprateur LongCount.
Listing 5.49 : Exemple dappel du premier prototype.
long count = Enumerable.Range(0, int.MaxValue). Concat(Enumerable.Range(0, int.MaxValue)).LongCount(); Console.WriteLine(count);

Loprateur Range est appel deux reprises pour gnrer deux squences contenant chacune le nombre maximal dlments du type int. Ces deux squences sont alors concatnes laide de loprateur Concat.
ATTENTION Lexcution de cet exemple est assez longue. Sur ma machine, un Pentium 4 dot de 1 Go de RAM, il a fallu attendre deux minutes et demie !

Ne soyez pas surpris si cet exemple est trs long sexcuter : il gnre en effet deux squences de 2 147 483 647 lments !

168

LINQ to Objects

Partie II

Voici le rsultat :
4294967294

Si vous essayez dexcuter cet exemple en utilisant loprateur Count, une exception OverflowException sera leve. Pour illustrer le second prototype, nous reprendrons le mme code que dans lexemple prcdent, mais nous limiterons lnumration aux entiers suprieurs 1 et infrieurs 4. Seuls les lments 2 et 3 seront donc slectionns. tant donn que le code dnit deux squences, lnumration devrait donc compter quatre lments (voir Listing 5.50).
Listing 5.50 : Exemple dappel du second prototype.
long count = Enumerable.Range(0, int.MaxValue). Concat(Enumerable.Range(0, int.MaxValue)).LongCount(n => n > 1 && n < 4); Console.WriteLine(count);

lexception du prdicat, ce code est trs proche du prcdent. Il est galement trs long excuter, et mme plus long que celui de lexemple prcdent. Voici le rsultat afch dans la console :
4

Oprateur Sum Loprateur Sum retourne la somme des valeurs numriques contenues dans les lments de la squence dentre.

Prototypes Deux prototypes de loprateur Sum seront tudis dans ce livre. Premier prototype
public static Numeric Sum( this IEnumerable<Numeric> source);

Numeric doit tre choisi parmi les types suivants : int, long, double, dcimal ou un de leurs quivalents nullables, int?, long?, double? ou decimal?.

Le premier prototype retourne la somme de tous les lments de la squence dentre source. Si la squence dentre est vide, la valeur retourne est 0. Les valeurs null des types nullables ne sont pas incluses dans la somme. Le second prototype est semblable au premier, mais les valeurs additionnes sont slectionnes par lintermdiaire dun dlgu.

Chapitre 5

Les oprateurs non diffrs

169

Second prototype
public static Numeric Sum<T>( this IEnumerable<T> source, Func<T, Numeric> selector);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Si la somme des lments dpasse la capacit du type Numeric :
m

une valeur infini ou +infini est retourne si Numeric est de type decimal ou decimal? ; une exception OverflowException est leve si Numeric est dun autre type.

Exemples Lexemple du Listing 5.51 gnre une squence dentiers avec loprateur Range et calcule leur somme en utilisant le premier prototype de loprateur Sum.
Listing 5.51 : Exemple dappel du premier prototype.
IEnumerable<int> ints = Enumerable.Range(1, 10); foreach (int i in ints) Console.WriteLine(i); Console.WriteLine("--"); int sum = ints.Sum(); Console.WriteLine(sum);

Voici les rsultats :


1 2 3 4 5 6 7 8 9 10 -55

Le Listing 5.52 illustre le second prototype. Ici, le calcul porte sur la somme des options des employs de la classe commune EmployeeOptionEntry.
Listing 5.52 : Exemple dappel du second prototype.
IEnumerable<EmployeeOptionEntry> options = EmployeeOptionEntry.GetEmployeeOptionEntries(); long optionsSum = options.Sum(o => o.optionsCount); Console.WriteLine("Somme des options des employs : {0}", optionsSum);

170

LINQ to Objects

Partie II

Plutt que calculer la somme de tous les membres des lments, nous utilisons ici le slecteur du second prototype pour limiter la somme au membre OptionsCount. Voici le rsultat :
Somme des options des employs : 51504

Oprateur Min Loprateur Min retourne la plus petite valeur de la squence dentre.

Prototypes Quatre prototypes de loprateur Min seront tudis dans ce livre. Premier prototype
public static Numeric Min( this IEnumerable<Numeric> source);

Numeric doit tre choisi parmi les types suivants : int, long, double, dcimal ou un de leurs quivalents nullables, int?, long?, double? ou decimal?.

Le premier prototype retourne la plus petite valeur de la squence dentre. Si les lments implmentent linterface IComparable<T>, cette interface est utilise pour comparer les lments. Dans le cas contraire, cest linterface non gnrique IComparable qui est utilise. La valeur null est retourne si la squence est vide ou uniquement compose de valeurs null. Le deuxime prototype de loprateur Min se comporte comme le premier, mais il sapplique aux types non numriques. Deuxime prototype
public static T Min<T>( this IEnumerable<T> source);

Le troisime prototype est ddi aux types numriques. Il implmente une mthode de slection qui permet de limiter la comparaison un seul membre de chaque lment. Troisime prototype
public static Numeric Min<T>( this IEnumerable<T> source, Func<T, Numeric> selector);

Le quatrime prototype est ddi aux types non numriques. Tout comme le prcdent, il implmente une mthode de slection qui permet de limiter la comparaison un seul membre de chaque lment. Quatrime prototype
public static S Min<T, S>( this IEnumerable<T> source, Func<T, S> selector);

Chapitre 5

Les oprateurs non diffrs

171

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Si le type T est nullable (int?, long?, double? ou decimal?), les premier et troisime prototypes retournent la valeur null si la source est vide. Si le type T nest pas nullable (int, long, double ou decimal), les premier et troisime prototypes lvent une exception InvalidOperationException si la squence source est vide. Exemples Dans lexemple du Listing 5.53, la plus petite valeur stocke dans un tableau dentiers est retourne par le premier prototype de loprateur Min.
Listing 5.53 : Exemple dappel du premier prototype.
int[] myInts = new int[] { 974, 2, 7, 1374, 27, 54 }; int minInt = myInts.Min(); Console.WriteLine(minInt);

Voici le rsultat retourn :


2

Pour illustrer le deuxime prototype, nous appliquerons loprateur Min sur le tableau presidents. La valeur retourne sera la "plus petite", alphabtiquement parlant.
Listing 5.54 : Exemple dappel du deuxime prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string minName = presidents.Min(); Console.WriteLine(minName);

Voici le rsultat :
Adams

Le rsultat est le mme que celui qui aurait t renvoy par loprateur First. Mais ceci est un cas particulier : si les lments du tableau presidents avaient t classs dans un autre ordre ou de faon alatoire, le rsultat de la fonction Min resterait "Adams". Pour illustrer le troisime prototype, nous rechercherons la date de naissance la plus ancienne dans la classe Actor (voir Listing 5.55).

172

LINQ to Objects

Partie II

Listing 5.55 : Exemple dappel du troisime prototype.


int oldestActorAge = Actor.GetActors().Min(a => a.birthYear); Console.WriteLine(oldestActorAge);

Voici le rsultat :
1960

Pour illustrer le quatrime prototype, nous allons rechercher le "premier" nom dacteur (alphabtiquement parlant) dans la classe Actor (voir Listing 5.56).
Listing 5.56 : Exemple dappel du quatrime prototype.
string firstAlphabetically = Actor.GetActors().Min(a => a.lastName); Console.WriteLine(firstAlphabetically);

Voici le rsultat :
Bullock

Oprateur Max Loprateur Max retourne la plus grande valeur de la squence dentre.

Prototypes Quatre prototypes de loprateur Max seront tudis dans ce livre. Premier prototype
public static Numeric Max( this IEnumerable<Numeric> source);

Numeric doit tre choisi parmi les types suivants : int, long, double, dcimal ou un de leurs quivalents nullables, int?, long?, double? ou decimal?.

Le premier prototype retourne la plus grande valeur de la squence dentre. Si les lments implmentent linterface IComparable<T>, cette interface est utilise pour comparer les lments. Dans le cas contraire, cest linterface non gnrique IComparable qui est utilise. La valeur null est retourne si la squence est vide ou uniquement compose de valeurs null. Le deuxime prototype de loprateur Max se comporte comme le premier, mais il sapplique aux types non numriques. Deuxime prototype
public static T Max<T>( this IEnumerable<T> source);

Le troisime prototype est ddi aux types numriques. Il implmente une mthode de slection qui permet de limiter la comparaison un seul membre de chaque lment.

Chapitre 5

Les oprateurs non diffrs

173

Troisime prototype
public static Numeric Max<T>( this IEnumerable<T> source, Func<T, Numeric> selector);

Le quatrime prototype est ddi aux types non numriques. Tout comme le prcdent, il implmente une mthode de slection qui permet de limiter la comparaison un seul membre de chaque lment. Quatrime prototype
public static S Max<T, S>( this IEnumerable<T> source, Func<T, S> selector);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Si le type T est nullable (int?, long?, double? ou decimal?), les premier et troisime prototypes retournent la valeur null si la source est vide. Si le type T nest pas nullable (int, long, double ou decimal), les premier et troisime prototypes lvent une exception InvalidOperationException si la squence source est vide. Exemples Dans lexemple du Listing 5.57, la plus grande valeur stocke dans un tableau dentiers est retourne par le premier prototype de loprateur Max.
Listing 5.57 : Exemple dappel du premier prototype.
int[] myInts = new int[] { 974, 2, 7, 1374, 27, 54 }; int minInt = myInts.Max(); Console.WriteLine(minInt);

Voici le rsultat retourn :


1374

Pour illustrer le deuxime prototype (voir Listing 5.58), nous appliquerons loprateur Max sur le tableau presidents. La valeur retourne sera la "plus grande", alphabtiquement parlant.
Listing 5.58 : Exemple dappel du deuxime prototype.
string[] presidents = { "Adams", "Arthur", "Buchanan", "Bush", "Carter", "Cleveland", "Clinton", "Coolidge", "Eisenhower", "Fillmore", "Ford", "Garfield", "Grant", "Harding", "Harrison", "Hayes", "Hoover", "Jackson", "Jefferson", "Johnson", "Kennedy", "Lincoln", "Madison", "McKinley", "Monroe", "Nixon", "Pierce", "Polk", "Reagan", "Roosevelt", "Taft", "Taylor", "Truman", "Tyler", "Van Buren", "Washington", "Wilson"}; string minName = presidents.Max(); Console.WriteLine(minName);

174

LINQ to Objects

Partie II

Voici le rsultat :
Wilson

Le rsultat est le mme que celui qui aurait t renvoy par loprateur Last. Mais ceci est un cas particulier : si les lments du tableau presidents avaient t classs dans un autre ordre ou de faon alatoire, le rsultat de la fonction Max resterait "Wilson". Pour illustrer le troisime prototype, nous rechercherons la date de naissance la plus rcente dans la classe Actor (voir Listing 5.59).
Listing 5.59 : Exemple dappel du troisime prototype.
int oldestActorAge = Actor.GetActors().Max(a => a.birthYear); Console.WriteLine(oldestActorAge);

Voici le rsultat :
1968

Pour illustrer le quatrime prototype, nous allons rechercher le "dernier" nom dacteur (alphabtiquement parlant) dans la classe Actor (voir Listing 5.60).
Listing 5.60 : Exemple dappel du quatrime prototype.
string firstAlphabetically = Actor.GetActors().Max(a => a.lastName); Console.WriteLine(firstAlphabetically);

Voici le rsultat :
Wilson

Oprateur Average Loprateur Average retourne la moyenne des valeurs numriques contenues dans la squence dentre.

Prototypes Deux prototypes de loprateur Average seront tudis dans ce livre. Premier prototype
public static Result Average( this IEnumerable<Numeric> source);

Numeric doit tre choisi parmi les types suivants : int, long, double, dcimal ou un de leurs quivalents nullables, int?, long?, double? ou decimal?. Si Numeric est de type int ou long, Result sera de type double. Si Numeric est de type int? ou long?, Result sera de type double?. Dans tous les autres cas, Result sera du mme type que Numeric.

Chapitre 5

Les oprateurs non diffrs

175

Le premier prototype numre la squence dentre source et calcule la moyenne des lments de type Numeric. Le second prototype numre la squence dentre source et calcule la moyenne des lments de type Numeric dsigns par la mthode selector. Second prototype
public static Result Average<T>( this IEnumerable<T> source, Func<T, Numeric> selector);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception OverflowException est leve si la somme des valeurs dont on calcule la moyenne dpasse la capacit du type long lorsque Numeric a un type int, int?, long ou long?. Exemples An dillustrer le premier prototype, nous allons utiliser loprateur Range pour crer une squence dentiers dont nous calculerons la moyenne (voir Listing 5.61).
Listing 5.61 : Exemple dappel du premier prototype.
IEnumerable<int> intSequence = Enumerable.Range(1, 10); Console.WriteLine("Squence dentiers :"); foreach (int i in intSequence) Console.WriteLine(i); double average = intSequence.Average(); Console.WriteLine("Moyenne : {0}", average);

Voici les rsultats :


Squence dentiers : 1 2 3 4 5 6 7 8 9 10 Moyenne : 5.5

Pour illustrer le second prototype, nous travaillerons avec la classe EmployeeOptionEntry (voir Listing 5.62).

176

LINQ to Objects

Partie II

Listing 5.62 : Exemple dappel du second prototype.


IEnumerable<EmployeeOptionEntry> options = EmployeeOptionEntry.GetEmployeeOptionEntries(); Console.WriteLine("Identifiants et options des employs :"); foreach (EmployeeOptionEntry eo in options) Console.WriteLine("Identifiant employ : {0}, Options: {1}", eo.id, eo.optionsCount); // Calcul de la moyenne des options double optionAverage = options.Average(o => o.optionsCount); Console.WriteLine("La moyenne des options des employs est : {0}", optionAverage);

Dans un premier temps, lobjet options est dni et initialis avec la mthode GetOptionEntries(). Les identiants et options des employs sont ensuite afchs laide dune boucle foreach. Enn, la moyenne des options est calcule avec le second prototype de loprateur Average, en ne travaillant que sur le membre optionsCount des lments. Voici les rsultats :
Identifiants et options des employs : Identifiant employ : 1, Options : 2 Identifiant employ : 2, Options : 10000 Identifiant employ : 2, Options : 10000 Identifiant employ : 3, Options : 5000 Identifiant employ : 2, Options : 10000 Identifiant employ : 3, Options : 7500 Identifiant employ : 3, Options : 7500 Identifiant employ : 4, Options : 1500 Identifiant employ : 101, Options : 2 La moyenne des options des employs est : 5722.66666666667

Oprateur Aggregate Loprateur Aggregate excute une fonction spcie par lutilisateur sur chacun des lments de la squence dentre. Il passe la valeur retourne par la fonction au rang prcdent et retourne la valeur calcule pour le dernier lment.

Prototypes Deux prototypes de loprateur Average seront tudis dans ce livre. Premier prototype
public static T Aggregate<T>( this IEnumerable<T> source, Func<T, T, T> func);

Dans cette version du prototype, loprateur Aggregate numre les lments de la squence dentre source. Le dlgu func est appel sur chaque lment. Deux arguments lui sont passs : la valeur retourne par la fonction llment prcdent et llment lui-mme. La valeur retourne par func est mmorise dans une mmoire interne, an dtre passe au prochain lment. Cest le premier lment qui est pass lors de la premire invocation de la mthode func. Le second prototype est identique au premier mais, ici, la valeur passer lors de la premire invocation de la mthode func est spcie.

Chapitre 5

Les oprateurs non diffrs

177

Second prototype
public static U Aggregate<T, U>( this IEnumerable<T> source, U seed, Func<U, T, U> func);

Exceptions Lexception ArgumentNullExpression est leve si un des arguments a pour valeur null. Lexception InvalidOperationException est leve dans le premier prototype si la squence dentre est vide. Exemples Le Listing 5.63 illustre le premier prototype. Ici, nous calculons la valeur 5! (factorielle 5). Ce rsultat est obtenu en multipliant entre eux tous les entiers positifs infrieurs ou gaux 5. La valeur 5! est donc gale 1 2 3 4 5.
Listing 5.63 : Exemple dappel du premier prototype.
int N = 5; IEnumerable<int> intSequence = Enumerable.Range(1, N); // Liste des lments de la squence foreach (int item in intSequence) Console.WriteLine(item); // Calcul et affichage de la factorielle // av == valeur de lagrgat, e == lment int agg = intSequence.Aggregate((av, e) => av * e); Console.WriteLine("{0}! = {1}", N, agg);

Ce code gnre une squence dentiers compris entre 1 et 5 en utilisant loprateur Range. Aprs avoir afch ces lments, loprateur Aggregate est appel, en lui fournissant une expression lambda qui multiplie lagrgat par llment. Voici les rsultats :
1 2 3 4 5 5! = 120

ATTENTION Lorsque vous utilisez le premier prototype de loprateur Aggregate, vous devez faire attention ce que le premier lment ne soit pas trait deux reprises par la mthode func. Dans lexemple prcdent, les paramtres 1 et 1 sont transmis en entre de la mthode func. Cela naffecte en rien le rsultat nal, puisque les valeurs sont multiplies entre elles. Le rsultat aurait en revanche t fauss si les valeurs avaient t additionnes.

178

LINQ to Objects

Partie II

Pour illustrer le second prototype, nous allons utiliser un oprateur Sum "fait maison" (voir Listing 5.64).
Listing 5.64 : Exemple dappel du second prototype.
IEnumerable<int> intSequence = Enumerable.Range(1, 10); // Affichage des lments de la squence foreach (int item in intSequence) Console.WriteLine(item); Console.WriteLine("--"); // Calcul et affichage de la somme int sum = intSequence.Aggregate(0, (s, i) => s + i); Console.WriteLine(sum);

La valeur "0" a t dnie comme premier argument de loprateur Aggregate an que le premier appel de la mthode func naltre pas le rsultat nal. Voici les rsultats afchs dans la console :
1 2 3 4 5 6 7 8 9 10 -55

Comme vous pouvez le voir, le rsultat est le mme que celui obtenu pour illustrer loprateur Sum, dans le Listing 5.51.

Rsum
Ce chapitre et le prcdent vous semblent peut-tre quelque peu indigestes. Ils contiennent cependant les bases de LINQ. Jespre avoir couvert tous les oprateurs qui vous seront utiles. Pour que LINQ rvle toute sa puissance, vous devez bien comprendre ces oprateurs et savoir comment les utiliser. Il nest pas ncessaire de retenir le dtail de chaque oprateur. Sachez juste quils existent et quels services ils peuvent vous rendre. En se fondant sur ce qui a t vu jusquici propos de LINQ to Objects et des oprateurs de requte standard, vous avez pu voir quel point LINQ sest rvl puissant et pratique pour interroger des donnes de tout type stockes dans des collections en mmoire. En utilisant les quelque 50 oprateurs de LINQ to Objects, vos requtes seront plus cohrentes, plus ables et plus rapides crire.

Chapitre 5

Les oprateurs non diffrs

179

Je ninsisterai jamais assez sur le fait que la plupart des oprateurs de requte standard travaillent sur des collections qui implmentent linterface IEnumerable<T>. Aucune des collections C# hrites (celles de lespace de noms System.Collection) nimplmentent cette interface ; elles sont donc exclues. Je sais pourtant que certains lecteurs essayeront (sans succs !) dappliquer des requtes LINQ des ArrayList provenant de code hrit. Si vous vous trouvez dans une telle situation, jetez un il aux oprateurs Cast et OfType. Au chapitre suivant, nous allons nous intresser la gnration et linterrogation de squences XML. Cette partie de LINQ a pour nom "LINQ to XML".

III
LINQ to XML

6
Introduction LINQ to XML
Ce chapitre aborde la facette LINQ to XML du langage LINQ. En prambule, le Listing 6.1 montre comment crer une hirarchie XML en utilisant lAPI Microsoft DOM (Document Object Model) W3C DOM XML. Il nest pas ncessaire daller bien loin dans le code pour se rendre compte quel point le processus est douloureux !
Listing 6.1 : Un exemple XML basique.
using System.Xml; // Dclaration de variables XmlElement xmlBookParticipant; XmlAttribute xmlParticipantType; XmlElement xmlFirstName; XmlElement xmlLastName; // Instanciation dun objet XmlDocument XmlDocument xmlDoc = new XmlDocument(); // Cration de llment parent et ajout au document XmlElement xmlBookParticipants = xmlDoc.CreateElement("BookParticipants"); xmlDoc.AppendChild(xmlBookParticipants); // Cration dun participant et ajout la liste des participants xmlBookParticipant = xmlDoc.CreateElement("BookParticipant"); xmlParticipantType = xmlDoc.CreateAttribute("type"); xmlParticipantType.InnerText = "Author"; xmlBookParticipant.Attributes.Append(xmlParticipantType); xmlFirstName = xmlDoc.CreateElement("FirstName"); xmlFirstName.InnerText = "Joe"; xmlBookParticipant.AppendChild(xmlFirstName); xmlLastName = xmlDoc.CreateElement("LastName"); xmlLastName.InnerText = "Rattz"; xmlBookParticipant.AppendChild(xmlLastName); xmlBookParticipants.AppendChild(xmlBookParticipant); // Cration dun participant autre et ajout la liste des participants xmlBookParticipant = xmlDoc.CreateElement("BookParticipant"); xmlParticipantType = xmlDoc.CreateAttribute("type"); xmlParticipantType.InnerText = "Editor";

184

LINQ to XML

Partie III

xmlBookParticipant.Attributes.Append(xmlParticipantType); xmlFirstName = xmlDoc.CreateElement("FirstName"); xmlFirstName.InnerText = "Ewan"; xmlBookParticipant.AppendChild(xmlFirstName); xmlLastName = xmlDoc.CreateElement("LastName"); xmlLastName.InnerText = "Buckingham"; xmlBookParticipant.AppendChild(xmlLastName); xmlBookParticipants.AppendChild(xmlBookParticipant); // Recherche des auteurs et affichage de leur nom XmlNodeList authorsList = xmlDoc.SelectNodes("BookParticipants/BookParticipant[@type=\"Author\"]"); foreach (XmlNode node in authorsList) { XmlNode firstName = node.SelectSingleNode("FirstName"); XmlNode lastName = node.SelectSingleNode("LastName"); Console.WriteLine("{0} {1}", firstName, lastName); }

Ce code construit la hirarchie XML et afche le nom de chaque participant. La structure XML dsire
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Lcriture, la comprhension et la maintenance de ce code sont un vrai cauchemar ! Par ailleurs, il ne suft pas dobserver son contenu pour en dduire la structure XML gnre. Si la mthode DOM est si lourde, cest en partie parce quil nest pas possible de crer un lment, de linitialiser et de lattacher la hirarchie en utilisant une seule et mme instruction. Au lieu de cela, trois tapes sont ncessaires : chaque lment doit tre cr, son membre InnerText, initialis la valeur souhaite puis llment ajout un nud de larborescence. Cette technique gnre beaucoup de code. Sans compter quil faut galement crer un document XML : sans lui, impossible de crer un simple lment ! Observez le listing et son rsultat. Ne trouvez-vous pas la quantit de code disproportionne ? Appuyez sur Ctrl+F5 pour excuter ce programme. Voici le rsultat afch dans la console :
System.Xml.XmlElement System.Xml.XmlElement

Les noms et prnoms des participants napparaissent pas. Nous allons tenter de modier la ligne Console.WriteLine pour obtenir les donnes souhaites :
Console.WriteLine("{0} {1}", firstName.ToString(), lastName.ToString());

Chapitre 6

Introduction LINQ to XML

185

Un nouveau Ctrl+F5 produit le mme rsultat :


System.Xml.XmlElement System.Xml.XmlElement

La tentative a chou !

Introduction
Microsoft aurait pu se contenter de fournir une API LINQ de requtage XML. Heureusement, les dveloppeurs sont alls beaucoup plus loin. Aprs plusieurs annes dutilisation de lAPI W3C DOM XML, il est apparu clairement quune amlioration simposait. Plutt quutiliser lartillerie DOM, navez-vous jamais cr directement des lments XML en passant par des chanes ? Ou t tent de le faire ? Plusieurs dciences de lAPI W3C DOM XML ont t examines et un nouveau modle dobjet a t cr. Il en a rsult une mthode bien plus simple et lgante pour crer des arbres XML : la "construction fonctionnelle". Et, croyez-moi, cette technique vaut son pesant dor ! Bien entendu, la nouvelle API se devait de supporter les requtes LINQ, sans quoi elle naurait pas pu faire partie du langage LINQ. Par lintermdiaire de mthodes dextension, des oprateurs de requtes spciques XML ont ainsi t implments. En combinant ces oprateurs et les oprateurs de requte standard de LINQ to Objects (voir Chapitre 2), vous aurez votre disposition tout ce quil faut pour manipuler des donnes XML de faon lgante et efcace.

Se passer de lAPI W3C DOM XML


Nous allons raisonner sur un cas pratique : le projet sur lequel jai personnellement travaill dans la division IT dune grande entreprise. Jai d mettre au point une classe permettant de pister toutes les actions des utilisateurs dans une application ASP.NET. Cest ainsi quest ne la classe logging. Cette classe avait deux buts : identier tout utilisateur qui abuserait du systme et tre prvenu par e-mail si une exception avait t leve. Ce second point se justiait par le fait que les utilisateurs qui avaient provoqu une exception ntaient jamais en mesure de mindiquer clairement dans quelles conditions stait produit lincident. Je voulais donc un procd capable de traquer les moindres mouvements des utilisateurs ct serveur. Toutes les actions entreprises par lutilisateur (une demande de facture ou la soumission dune commande, par exemple) devaient tre considres comme un vnement. Chaque vnement tait mmoris dans les champs dune base de donnes : rfrences de lutilisateur, date, heure, type de lvnement, etc. Malheureusement, ces informations ne me permettaient pas de connatre le dtail de chaque

186

LINQ to XML

Partie III

action. Par exemple, pour une commande, jaurais voulu connatre son numro et les diffrents articles commands. En fait, javais besoin de toutes les informations qui me permettraient de ritrer la situation qui avait dclench une exception. Chaque vnement manipulait des donnes diffrentes, mais je ne voulais pas que ces donnes soient stockes dans des tableaux diffrents. La solution XML simposait delle-mme. Par exemple, pour une demande de facture, les donnes XML pouvaient avoir lallure suivante :
<StartDate>10/2/2006</StartDate> <EndDate>10/9/2006</EndDate> <IncludePaid>False</IncludePaid>

Et, pour une commande :


<PartId>4754611903</PartId> <Quantity>12</Quantity> <DistributionCenter>Atlanta<DistributionCenter> <ShippingCode>USPS First Class<ShippingCode>

tant donn que les donnes taient lies au type des vnements, il tait impossible de les valider. Lutilisation de lAPI XML DOM tait donc avantageuse. Ce gestionnaire dvnements est devenu un outil trs utile. Il a permis didentier et de rsoudre plus facilement les bugs. Il est assez amusant dappeler un client et de linformer que lerreur survenue sur la commande 32728 quil a passe la veille est dsormais rpare. Le trouble qui rsulte lorsque le client prend conscience quil est possible de connatre le dtail de ses actions est une vraie rcompense en soi. Si vous connaissez dj le XML, vous avez certainement remarqu que ces donnes nont aucun nud parent. Cela constitue un problme si vous utilisez lAPI W3C DOM. Mais, dans mon cas, jai utilis lAPI String.Format XML, qui vous est peut-tre galement familire. Voici le code utilis :
string xmlData = string.Format( "<StartDate>{0}</StartDate><EndDate>{1}</EndDate><IncPaid>{2}</IncPaid>", Date.ToShortDateString(), endDate.ToShortDateString(), includePaid.ToString());

Je sais que ce nest pas la meilleure des faons de dnir des donnes XML et quil est facile de se tromper dans son criture. Pour faciliter la saisie, jai donc cr une mthode laquelle je passe en paramtres une liste dlments et les donnes correspondantes :
string xmlData = XMLHelper( "StartDate", startDate.ToShortDateString(), "EndDate", endDate.ToShortDateString(), "IncPaid", includePaid.ToString());

La mthode XMLHelper cre galement un nud parent. Les amliorations ne sont pas agrantes. Comme vous pouvez le voir, je nai rien fait pour encoder mes donnes dans cet appel.

Chapitre 6

Introduction LINQ to XML

187

Bien que lutilisation de la mthode String.Format (ou une autre technique externe lAPI XML DOM) ne soit pas une trs bonne alternative, DOM se rvle trop complexe lorsquil sagit de manipuler quelques lignes de XML. Si vous pensez que mon approche est un peu trop personnelle, sachez que, rcemment, lors dun sminaire Microsoft, lintervenant a prsent un code qui construisait une structure XML en concatnant plusieurs chanes !

Rsum
La plupart des dveloppeurs associent LINQ au requtage de donnes, et en particulier de donnes provenant de bases de donnes. En tournant les pages de cet ouvrage, vous verrez que LINQ to XML apporte galement une vraie rponse quant la manipulation et linterrogation de donnes XML. Dans ce chapitre, je vous ai montr quel point il tait douloureux de manipuler du XML via lAPI W3C DOM XML. Vous avez galement vu quil tait possible de se passer de cette API. Au chapitre suivant, nous nous intresserons lAPI LINQ to XML. Par son intermdiaire, vous apprendrez crer des hirarchies XML en quelques lignes. titre indicatif, si la hirarchie cre dans le Listing 6.1 demandait 29 lignes de code, elle sera rduite 10 lignes seulement en passant par LINQ to XML. Aprs avoir lu les deux prochains chapitres, vous serez certainement convaincu de lavance rvolutionnaire de LINQ, tant au niveau de la manipulation du XML qu celui de linterrogation des bases de donnes.

7
LAPI LINQ to XML
Au chapitre prcdent, vous avez vu quel point il tait difcile de crer un document XML en utilisant lAPI W3C DOM XML. Vous avez galement appris vous passer de cette API pour allger le code. En outre, vous avez pu constater que LINQ sait faire autre chose quinterroger des collections : il peut galement manipuler des hirarchies XML, travers lAPI LINQ to XML. Dans ce chapitre, vous allez dcouvrir comment utiliser LINQ to XML pour crer, parcourir, manipuler et interroger des documents XML, et effectuer des recherches dans des objets XML. Pour illustrer ce chapitre, nous utiliserons une application console. An de pouvoir tirer parti de LINQ to XML, vous devez y ajouter une rfrence vers lassembly System.Xml.Linq, si celle-ci nest pas dj prsente.

Espaces de noms rfrencs


Les exemples de ce chapitre vont utiliser les espaces de noms System.Linq, System.Xml.Linq et System.Collections.Generic. Si elles ne sont pas dj prsentes, vous devez donc ajouter les directives using suivantes dans votre code :
using System.Linq; using System.Xml.Linq; using System.Collections.Generic;

Si vous parcourez le code source (www.pearson.fr), vous verrez quune directive using a galement t ajoute sur lespace de noms System.Diagnostic. Cette directive nest pas ncessaire si vous saisissez directement les exemples de ce chapitre. Elle nest l que pour les besoins propres au code source.

190

LINQ to XML

Partie III

Amliorations de lAPI
Aprs avoir expriment lAPI Microsoft W3C XML DOM pendant plusieurs annes, des points ngatifs et des faiblesses se sont peu peu dessins. Pour y remdier, les points suivants ont t examins par les quipes de dveloppement de Microsoft :
m m m m

construction darbres XML ; solutions "centres-document" ; espaces de noms et prxes ; extraction de valeurs de nuds.

Non contents de grossir et parfois dobscurcir le code, ces points sont une vritable gne lorsque lon travaille avec des donnes XML. Il tait donc important de les examiner de prs an que LINQ to XML fonctionne dune manire irrprochable. Un exemple : supposons que vous vouliez utiliser une projection, an quune requte LINQ retourne du code XML. LAPI XML existante ne permettant pas dinstancier un nouvel lment avec une dclaration new, il fallait corriger cette limitation pour que LINQ to XML manipule des donnes XML aussi simplement que possible. Dans les pages suivantes, nous allons passer en revue chacune de ces problmatiques et voir comment LINQ to XML les solutionne. La construction fonctionnelle simplie la cration darbres XML Si vous vous reportez au Listing 6.1, au chapitre prcdent, vous verrez quil est trs difcile den tirer un schma XML. Vous constaterez galement que le code est trs "verbeux". Aprs avoir instanci un nouveau document XML, plusieurs nuds doivent tre dnis. titre dexemple, pour ajouter un lment il est ncessaire de le dnir, de linitialiser et de le lier avec un lment parent. Ces tapes doivent tre rptes autant de fois que ncessaire pour dnir toute la structure XML. Un tel procd rend difcilement perceptible le schma XML et fait exagrment grossir le code. Cette API nest malheureusement pas capable de crer un lment (ou un autre type de nud) en le positionnant dans larbre XML et de linitialiser par la mme occasion. Cette technique est toujours utilisable dans lAPI LINQ to XML, mais une autre, bien plus efcace, connue sous le nom de "construction fonctionnelle", a fait son apparition. Cette technique permet de dnir le schma XML pendant les phases de construction et dinitialisation des objets XML. Pour ce faire, la nouvelle API fournit des constructeurs dobjets XML qui acceptent un ou plusieurs objets, accompagns de leurs valeurs. Le type de lobjet ou des objets tant spci, il dtermine leur point dappartenance. Voici le modle gnral :
XMLOBJECT o = new XMLOBJECT(OBJECTNAME, XMLOBJECT1, XMLOBJECT2, ... XMLOBJECTN);

Chapitre 7

LAPI LINQ to XML

191

INFO Le code prcdent na quun objet purement dmonstratif. Aucune des classes rfrences dans les arguments nexiste rellement. Elles ne sont l que pour matrialiser des classes XML purement abstraites.

Si vous utilisez la classe LINQ to XML XAttribute pour ajouter un attribut XML un lment de type XElement, lattribut devient un attribut de llment. Par exemple, dans le code prcdent, si lattribut XMLOBJECT1 est ajout llment XMLOBJECT o, si o est un XElement et XMLOBJECT1, un XAttribute, XMLOBJECT1 devient un attribut du XElement o. Si vous ajoutez un XElement un XElement, llment ajout devient un enfant de llment auquel il est ajout. Par exemple, si XMLOBJECT1 et o sont deux lments, XMLOBJECT1 devient lenfant de llment o. Lorsquun XMLOBJECT est instanci, son contenu peut tre dni par un ou plusieurs XMLOBJECT. Comme vous le verrez un peu plus loin, dans la section "Cration de textes avec XText", il est galement possible de spcier son contenu en ajoutant une chane. Cette dernire sera automatiquement convertie en un XMLOBJECT. Le Listing 7.1 donne un exemple de cration dun schma XML.
Listing 7.1 : Utilisation de la construction fonctionnelle pour dfinir un schma XML.
XElement xBookParticipant = new XElement("BookParticipant", new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")); Console.WriteLine(xBookParticipant.ToString());

Deux objets XElement ont t passs lors de la construction de llment BookParticipant. Chacun deux est donc un enfant de BookParticipant. Notez galement que, lors de la construction des XElement FirstName et LastName, une valeur texte (et non deux objets enfants) a t passe. Voici les rsultats de ce code :
<BookParticipant> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant>

Le schma XML apparat clairement dans le code. Remarquez galement quel point le code est concis. Le Listing 7.2 reprsente le code LINQ to XML quivalent au Listing 6.1.
Listing 7.2 : Dfinition de larbre du Listing 6.1, avec un code bien moins important.
XElement xBookParticipants = new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"),

192

LINQ to XML

Partie III

new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))); Console.WriteLine(xBookParticipants.ToString());

Ce code est bien plus concis et facile maintenir que celui du Listing 6.1. Par ailleurs, la structure des donnes peut tre facilement devine par simple lecture du code. Voici le rsultat :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

La nouvelle API a galement un autre avantage : les donnes cres sont formates comme un arbre XML traditionnel. Il en va tout autrement de larbre cr par le code du Listing 6.1 :
<BookParticipants><BookParticipant type="Author"><FirstName>Joe</FirstName>

Au chapitre suivant, quand nous nous intresserons aux requtes LINQ qui produisent des sorties XML, vous verrez quel point la construction fonctionnelle est importante. Llment, point central dun objet XML Avec lAPI W3C DOM, il tait impossible de dnir un lment XML XmlElement sans le rattacher un document XML XmlDocument. Si vous essayez dinstancier un XmlElement avec cette instruction :
XmlElement xmlBookParticipant = new XmlElement("BookParticipant");

vous obtenez lerreur de compilation ci-aprs :


System.Xml.XmlElement ne contient pas un constructeur qui accepte des arguments 1

Avec lAPI W3C DOM, la seule faon de crer un XmlElement consiste appeler la mthode CreateElement dun objet XmlDocument :
XmlDocument xmlDoc = new XmlDocument(); XmlElement xmlBookParticipant = xmlDoc.CreateElement("BookParticipant");

Ce code fonctionne la perfection, mais il nest pas toujours pratique de devoir crer un document XML avant de pouvoir dnir un lment XML. La nouvelle API LINQ to XML permet dinstancier un lment sans le rattacher ncessairement un document XML.

Chapitre 7

LAPI LINQ to XML

193

XElement xeBookParticipant = new XElement("BookParticipant");

Les lments XML ne sont pas les seuls nuds affects par cette restriction de lAPI X3C DOM. Les attributs, les commentaires, les sections CData, les instructions de calcul et les rfrences dentits doivent tous tre rattachs un document XML. Avec LINQ to XML, tous ces objets pourront tre instancis la vole, sans quun document XML nait t dni au pralable. Bien entendu, rien ne vous empche de crer un document XML avec la nouvelle API. titre dexemple, le Listing 7.3 cre un document XML, y ajoute llment BookParticipants et insre un lment BookParticipant dans ce dernier.
Listing 7.3 : Cration dun document XML et de sa structure avec lAPI LINQ to XML.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(xDocument.ToString());

Voici le rsultat afch dans la console suite lappui sur Ctrl+F5 :


<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Le code XML issu du Listing 7.3 est trs proche de celui en sortie du Listing 6.1, ceci prs quun seul participant a t ajout au document. La construction fonctionnelle le rend cependant bien plus lisible, et il suft dobserver le code pour en dduire le schma correspondant. tant donn quil nest plus ncessaire de dnir un document XML, il est encore possible de simplier le code (voir Listing 7.4).
Listing 7.4 : Le mme exemple que le prcdent, mais sans la dfinition du document XML.
XElement xElement = new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz"))); Console.WriteLine(xElement.ToString());

Lexcution de ce code produit le mme rsultat que prcdemment :


<BookParticipants> <BookParticipant type="Author">

194

LINQ to XML

Partie III

<FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

L ne sarrtent pas les prouesses de LINQ to XML. Par son intermdiaire, vous pouvez galement lire et crire des donnes XML dans un chier. Noms, espaces de noms et prxes Les termes "noms", "espaces de noms" et "prxes despaces de noms" sont souvent abscons, sinon difciles apprhender pour le programmeur XML. Pour viter toute confusion, sachez que les prxes des espaces de noms sont grs lextrieur de lAPI. Ils ne font que sajouter aux espaces de noms et nont aucune existence lintrieur de lAPI. Les espaces de noms sont utiliss pour identier de manire unique le schma XML dune portion darbre XML. Une URI peut donc tre utilise pour chaque espace de noms, puisquil est unique au sein dune socit. Dans plusieurs exemples, nous utiliserons larbre XML suivant :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Les codes crits pour traiter ces donnes XML sattendront ce que le nud BookParticipants contienne plusieurs nuds BookParticipant, chacun dentre eux ayant un attribut type et des nuds FirstName et LastName. Que se passerait-il si ce code devait galement traiter des donnes XML issues dune autre source, contenant un nud BookParticipants, mais dont le schma diffre du prcdent ? Eh bien, un espace de noms informerait le code sur la structure du schma, et le traitement serait alors appropri. Dans XML, chaque lment a besoin dun nom. Lorsquun lment est cr, si son nom est spci dans le constructeur, son type string est implicitement converti en un objet XName. Ce dernier consiste en un espace de noms XNameSpace, lobjet et son nom local (cest--dire le nom que vous avez choisi). titre dexemple, llment BookParticipants pourrait tre cr comme suit :
XElement xBookParticipants = new XElement("BookParticipants");

Lorsque llment est cr, un objet XName contenant un espace de noms non rfrenc et le nom local BookParticipants est dni. Si vous utilisez le dbogueur sur cette ligne de code et que vous examiniez la variable xBookParticipants dans la fentre Espion Express, vous verrez que son membre Name est initialis {BookParticipants}. Dveloppez le membre Name. Vous verrez quil contient le membre LocalName initialis BookParticipants, et un membre NameSpace vide. Ici, lespace de noms na pas t dni.

Chapitre 7

LAPI LINQ to XML

195

Pour spcier un espace de noms, il vous suft de crer un objet XNameSpace et de lutiliser comme prxe du nom local choisi :
XNamespace nameSpace = "http://www.linqdev.com"; XElement xBookParticipants = new XElement(nameSpace + "BookParticipants");

Maintenant, lorsque vous visualisez llment xBookParticipants dans la fentre Espion Express du dbogueur, le nom a pour valeur {{http://www.linqdev.com}BookParticipants}. Dveloppez le membre Name. Le membre LocalName a toujours pour valeur BookParticipants mais, maintenant, le membre Namespace est initialis {http://www.linqdev.com}. Il nest pas obligatoire dutiliser un objet NameSpace pour spcier lespace de noms. Vous auriez tout aussi bien pu le spcier dans limplmentation du XElement :
XElement xBookParticipants = new XElement("{http://www.linqdev.com}" +"BookParticipants");

Des accolades entourent lespace de noms, an dindiquer au constructeur XElement quil sagit dun espace de noms et pas du nom de llment. Si vous examinez nouveau le membre Name dans la fentre Espion Express du dbogueur, vous verrez que le membre Name et ses enfants LocalName et NameSpace sont tous initialiss comme auparavant, lorsque llment avait t cr avec un objet XNamespace. Ayez bien en tte quil ne suft pas de dnir lURI de votre socit ou de votre domaine pour garantir lunicit dun espace de noms. Cela garantit simplement que vous nentrerez pas en conit avec dautres socits qui utilisent galement les rgles inhrentes aux espaces de noms. Notez cependant qu lintrieur de votre socit des conits entre dpartements pourraient se produire si la seule URI constitue lespace de noms. Cest ce point prcis que vous devrez faire intervenir votre connaissance des divisions, dpartements et autres sous-structures de votre socit. Lidal serait que lespace de noms stende sur tous les niveaux dont vous avez le contrle. Supposons par exemple que vous travailliez chez LINQDev.com et que vous deviez crer un schma relatif aux retraites pour le dpartement des ressources humaines. Lespace de noms pourrait tre le suivant :
XNamespace nameSpace = "http://www.linqdev.com/ressourceshumaimes/retraites";

Pour terminer cette discussion sur le fonctionnement des espaces de noms, nous allons modier le Listing 7.2 en y incluant un espace de noms (voir Listing 7.5).
Listing 7.5 : Une version modifie du Listing 7.2 incluant un espace de noms.
XNamespace nameSpace = "http://www.linqdev.com"; XElement xBookParticipants = new XElement(nameSpace + "BookParticipants", new XElement(nameSpace + "BookParticipant", new XAttribute("type", "Author"), new XElement(nameSpace + "FirstName", "Joe"), new XElement(nameSpace + "LastName", "Rattz")), new XElement(nameSpace + "BookParticipant", new XAttribute("type", "Editor"),

196

LINQ to XML

Partie III

new XElement(nameSpace + "FirstName", "Ewan"), new XElement(nameSpace + "LastName", "Buckingham"))); Console.WriteLine(xBookParticipants.ToString());

Appuyez sur Ctrl+F5 pour excuter ce code. Voici les rsultats afchs dans la console :
<BookParticipants xmlns="http://www.linqdev.com"> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Si un programme lit ce schma, il saura quil a t mis par LINQDev.com. Pour isoler le prxe de lespace de noms, vous utiliserez lobjet XAttribute, comme dans le Listing 7.6.
Listing 7.6 : Dfinition dun prfixe dans un espace de noms.
XNamespace nameSpace = "http://www.linqdev.com"; XElement xBookParticipants = new XElement(nameSpace + "BookParticipants", new XAttribute(XNamespace.Xmlns + "linqdev", nameSpace), new XElement(nameSpace + "BookParticipant")); Console.WriteLine(xBookParticipants.ToString());

Le prxe utilis dans ce code est "linqdev". Un objet XAttribute est utilis pour inclure ce prxe dans le schma. Voici les rsultats afchs dans la console :
<linqdev:BookParticipants xmlns:linqdev="http://www.linqdev.com"> <linqdev:BookParticipant /> </linqdev:BookParticipants>

Extraction de valeurs de nuds Si vous avez parcouru le chapitre prcdent, vous avez certainement t tonn par les rsultats du Listing 6.1. Lobtention des valeurs issues dun nud est un vrai cassette ! Nayant pas travaill sur du code XML DOM depuis un moment, jai invitablement t confront une erreur, en oubliant quune tape supplmentaire tait ncessaire pour extraire les donnes. LAPI LINQ to XML solutionne ce problme dune manire lgante. Tout dabord, lappel de la mthode ToString sur un lment produit la chane XML elle-mme, et non le type de lobjet, comme le fait lAPI W3C DOM. Ceci est trs pratique lorsque vous voulez obtenir une portion de XML partir dun certain point dans larbre, et elle a bien plus de sens que de fournir le type de lobjet.

Chapitre 7

LAPI LINQ to XML

197

Listing 7.7 : La mthode ToString applique un lment produit larbre XML correspondant.
XElement name = new XElement("Name", "Joe"); Console.WriteLine(name.ToString());

Voici le rsultat obtenu par un appui sur Ctrl+F5 :


<Name>Joe</Name>

Quel changement ! Attendez un peu, la suite est encore plus tonnante. Bien entendu, les nuds enfants sont inclus dans la sortie mais, tant donn quaucune surcharge de la mthode WriteLine na t dnie pour traiter les XElement, la mthode ToString est automatiquement appele, comme dans le Listing 7.8.
Listing 7.8 : Appel implicite de la mthode ToString dans un Console.WriteLine pour obtenir larbre XML.
XElement name = new XElement("Person", new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")); Console.WriteLine(name);

Voici les rsultats afchs dans la console :


<Person> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </Person>

Encore plus important : si vous utilisez un oprateur de casting sur un nud pour le convertir dans un type compatible avec son contenu, vous obtenez la valeur du nud. Le Listing 7.9 donne un exemple dans lequel la valeur du nud Name est convertie en une chane de caractres.
Listing 7.9 : Le casting dun lment produit la donne qui y est stocke.
XElement name = new XElement("Name", "Joe"); Console.WriteLine(name); Console.WriteLine((string)name);

Voici les rsultats de ce code :


<Name>Joe</Name> Joe

String nest pas le seul oprateur de casting. Les oprateurs suivants sont galement votre disposition : int, int?, uint, uint?, long, long?, ulong, ulong?, bool, bool?, float, float?, double, double?, decimal, decimal?, TimeSpan, TimeSpan?, DateTime, DateTime?, GUID et GUID?.

198

LINQ to XML

Partie III

Le Listing 7.10 donne un exemple des valeurs stockes dans plusieurs nuds.
Listing 7.10 : Valeurs stockes dans diffrents nuds et rcupres par casting.
XElement count = new XElement("Count", 12); Console.WriteLine(count); Console.WriteLine((int)count); XElement smoker = new XElement("Smoker", false); Console.WriteLine(smoker); Console.WriteLine((bool)smoker); XElement pi = new XElement("Pi", 3.1415926535); Console.WriteLine(pi); Console.WriteLine((double)pi);

Voici les rsultats :


<Count>12</Count> 12 <Smoker>false</Smoker> False <Pi>3.1415926535</Pi> 3.1415926535

Cette approche est simple et intuitive. En utilisant lAPI LINQ to XML, les difcults rencontres dans le Listing 6.1 feront tout jamais partie du pass ! Dans les exemples tudis jusquici, les lments ont t convertis dans leurs types dorigine. Ceci nest pas une obligation : il suft que la conversion soit possible. Le Listing 7.11 donne un exemple de casting dune chane de caractres en boolen.
Listing 7.11 : Casting dun nud en utilisant un type diffrent du type dorigine.
XElement smoker = new XElement("Smoker", "true"); Console.WriteLine(smoker); Console.WriteLine((bool)smoker);

tant donn que llment a pour valeur la chane "true" et que cette chane peut tre convertie en une valeur boolenne, le code sexcute sans encombre. Voici les rsultats :
<Smoker>true</Smoker> True

Ce code ne laisse pas apparatre le nom de la mthode utilise pour effectuer le casting. Le Listing 7.12 va vous montrer quil sagit de la mthode System.Xml.XmlConvert.
Listing 7.12 : Le casting boolen utilise la classe System.Xml.XmlConvert.
try { XElement smoker = new XElement("Smoker", "Tue"); Console.WriteLine(smoker); Console.WriteLine((bool)smoker); }

Chapitre 7

LAPI LINQ to XML

199

catch (Exception ex) { Console.WriteLine(ex); }

La valeur affecte llment Smoker a t intentionnellement mal orthographie an dobtenir le nom de la mthode utilise pour effectuer le casting. Un appui sur Ctrl+F5 afche les informations suivantes dans la console :
<Smoker>Tue</Smoker> System.FormatException: The string tue is not a valid Boolean value. at System.Xml.XmlConvert.ToBoolean(String s) ...

Comme vous pouvez le voir, le casting a provoqu une exception lors de lappel la mthode System.Xml.XmlConvert.ToBoolean.

Le modle dobjet LINQ to XML


LAPI LINQ to XML vient avec un nouveau modle dobjet contenant plusieurs classes issues de lespace de noms System.Xml.Linq. Lune dentre elles est la classe statique qui hberge les mthodes dextension (Extensions). Deux autres sont ddies aux comparateurs (XNodeDocumentOrderComparer et XNodeEqualityComparer). Les autres classes sont utilises pour construire les arbres XML (voir Figure 7.1).
Figure 7.1 : Le modle dobjet LINQ to XML.
XDocument XElement XCData

XComment

XContainer

XDocumentType

XProcessingInstruction

XText

XAttribute

XNode

XDeclaration

XName

XNamespace

XObject

XStreamingElement

Quelques remarques intressantes : 1. Les classes XObject, XContainer et XNode sont abstraites. Elles ne peuvent donc pas tre construites. 2. Les attributs XAttribute ne sont pas drivs de nuds XNode. En fait, il sagit dun tout autre type de classe, constitu de paires nom/valeur. 3. Les lments XStreamingElement nhritent pas de XElement. 4. Les classes XDocument et XElement sont les seules avoir des nuds enfants drivs de XNode. Vous utiliserez ces classes pour construire vos arbres XML. LAPI LINQ to XML tant centre sur les lments, la classe XElement vous sera particulirement utile.

Derived

200

LINQ to XML

Partie III

Excution diffre des requtes, suppression de nuds et bogue dHalloween


Lexcution de toutes requtes LINQ est diffre. Il peut parfois en dcouler des effets secondaires indsirables. Le "bogue dHalloween" doit son nom la premire quipe qui en a dbattu ouvertement. Ces spcialistes dHalloween ont discut des problmes qui dcoulent du changement manuel dun index dans une boucle. Cette situation a t dtecte pour la premire fois par des ingnieurs bases de donnes alors quils mettaient au point un processus doptimisation. Une de leurs requtes de test a modi la valeur dune cellule utilise comme index par le processus doptimisation. Cela a engendr une boucle sans n dont le processus doptimisation ne pouvait se dgager. Vous avez peut-tre dj expriment ce problme sans connatre son nom. Navezvous jamais effectu une boucle sur une collection dans laquelle la suppression dun lment entranait la n ou le mauvais comportement de la boucle ? Jai personnellement rencontr ce problme rcemment, alors que je travaillais avec des contrles serveur ASP.NET. Jai t amen supprimer les enregistrements slectionns par lutilisateur dans un contrle DataGrid. Pour ce faire, jai boucl sur les enregistrements, du premier au dernier, en supprimant ceux qui taient slectionns. Ce faisant, les pointeurs utiliss dans la boucle ont t dsorganiss. Rsultat : certains enregistrements ont t supprims par erreur et dautres qui auraient d tre supprims ont t ignors. Le concepteur des contrles a trouv une solution qui consistait parcourir les enregistrements du dernier au premier. Avec LINQ to XML, vous tomberez forcment sur ce type de problme lorsque vous supprimerez des nuds dans un arbre XML, mais peut-tre galement dans dautres situations totalement diffrentes. Il est donc important davoir ce problme lesprit lorsque vous vous lancerez dans le codage.
Listing 7.13 : Mise en vidence du bogue dHalloween.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); foreach (XElement element in elements) { Console.WriteLine("Elment source: {0}: valeur = {1}", element.Name, element.Value);

Chapitre 7

LAPI LINQ to XML

201

} foreach (XElement element in elements) { Console.WriteLine("Suppressionde llment {0} = {1} ...", element.Name, element.Value); element.Remove(); } Console.WriteLine(xDocument);

La premire ligne dnit le document XML. Le bloc dinstructions suivant initialise ce document avec une squence dlments BookParticipant. La premire boucle foreach afche les deux lments de la squence. La boucle suivante numre nouveau la squence et supprime llment BookParticipant. Enn, la dernire instruction afche le document XML rsultant. Si le bogue dHalloween ne vous saute pas aux yeux, regardez de plus prs le message de suppression. Normalement, les deux lments BookParticipant devraient tre supprims et il devrait en rsulter un document XML vide. Et, pourtant, voici le rsultat :
Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Suppression de llment BookParticipant = JoeRattz ... <BookParticipants> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Sur les deux lments SourceParticipant, seul le premier, JoeRattz, est effectivement supprim. Le bogue dHalloween a eu raison de la seconde numration ! Dans certains cas, ce problme peut se manifester diffremment : lnumration peut se terminer prmaturment ou une exception peut tre leve. Vous vous demandez certainement quelle solution peut tre apporte ce problme. Eh bien, dans ce cas prcis, la solution consiste mettre les lments dans une mmoire tampon et numrer cette mmoire plutt que le document XML, pour lequel les pointeurs internes sont altrs par la suppression. Pour ce faire, nous allons utiliser un oprateur de requte standard spcialement conu pour mettre des lments dans une mmoire tampon, an dviter les problmes lis au ct diffr de certaines requtes.
Listing 7.14 : Une solution au bogue dHalloween.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"),

202

LINQ to XML

Partie III

new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } foreach (XElement element in elements.ToArray()) { Console.WriteLine("Suppression de llment {0} = {1} ...", element.Name, element.Value); element.Remove(); } Console.WriteLine(xDocument);

Ce code est proche du prcdent mais, ici, la suppression se fait en numrant les lments via loprateur ToArray. Voici le rsultat :
Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Suppression de llment BookParticipant = JoeRattz ... Suppression de llment BookParticipant = EwanBuckingham ... <BookParticipants />

Cette fois-ci, deux messages de suppression sont afchs dans la console. Les deux lments sont bien supprims, et le bogue dHalloween a t radiqu.

Cration XML
Comme il a t dit prcdemment, la construction fonctionnelle de lAPI LINQ to XML facilite grandement la cration dun arbre XML. Cela vous sera conrm tout au long de cette section, qui passe en revue la cration des principales classes XML par lintermdiaire de cette nouvelle API. tant donn que les lments sont le point central de lAPI LINQ to XML et que vous travaillerez avec ce type dobjet dans la plupart des cas, nous allons nous intresser dans un premier temps la cration dlments avec la classe XElement. Par la suite, les autres classes XML seront passes en revue par ordre alphabtique. Cration dlments avec XElement La classe XElement est la plus utilise dans cette nouvelle API. Nous allons examiner deux des constructeurs de cette classe :
XElement.XElement(XName name, object content); XElement.XElement(XName name, params object[] content);

Le premier constructeur est le plus simple. Il correspond au cas o un lment a une valeur texte et aucun nud enfant (voir Listing 7.15).

Chapitre 7

LAPI LINQ to XML

203

Listing 7.15 : Cration dun lment avec le premier prototype.


XElement firstName = new XElement("FirstName", "Joe"); Console.WriteLine((string)firstName);

Le premier argument du constructeur est un objet XName. Comme il a t dit prcdemment, cet objet sera cr en convertissant de faon implicite la chane passe en entre en un XName. Le deuxime argument reprsente la valeur de llment ; dans cet exemple une chane initialise "Joe". LAPI convertit automatiquement cette chane en un objet XText. La deuxime instruction utilise un oprateur de casting pour obtenir la valeur de llment FirstName. Voici le rsultat :
Joe

Le choix du type des objets est trs exible. Cest le type dun objet qui contrle les relations avec llment auquel il est ajout. Le Tableau 7.1 dresse la liste de tous les types de contenus autoriss et indique comment ils sont grs. Mme si bon nombre dlments sont stocks sous la forme de chanes (cest par exemple le cas des entiers, qui font partie de la catgorie "autres types" du Tableau 7.1), vous pouvez les lire dans leur format dorigine en utilisant les oprateurs de casting appropris. Par exemple, en appliquant loprateur de casting (int) un lment, vous obtenez la valeur entire de cet lment. Tant que vous utilisez un oprateur de casting licite, le casting est la faon la plus simple dobtenir la valeur dun lment, exprime dans son type dorigine. Le deuxime constructeur XElement est semblable au premier, mais il permet de spcier un contenu compos de plusieurs objets. Reportez-vous aux Listings 7.1 ou 7.2 pour avoir un exemple du deuxime constructeur.
Tableau 7.1 : Comportement de linsertion dobjets LINQ to XML dans un objet parent

Type de lobjet String XText

Gestion Un objet string ou une chane littrale est automatiquement converti en un objet XText et considr comme tel. Un tel objet peut avoir une valeur string ou XText. Il est ajout comme nud enfant de llment, mais considr comme le contenu texte de llment. Un tel objet peut avoir une valeur string ou XCData. Il est ajout comme nud enfant de llment, mais considr comme le contenu CData de llment. Cet objet est ajout en tant qulment enfant. Cet objet est ajout en tant quattribut.

XCData

XElement XAttribute

XProcessingInstruction Cet objet est ajout en tant que contenu enfant.

204

LINQ to XML

Partie III

Tableau 7.1 : Comportement de linsertion dobjets LINQ to XML dans un objet parent (suite)

Type de lobjet XComment IEnumerable null Autres types

Gestion Cet objet est ajout en tant que contenu enfant. Cet objet est numr et la manipulation des types est applique de faon rcursive. Cet objet est ignor. Comme vous le verrez par la suite, ce type dobjet peut se rvler utile lors de transformations XML. La mthode ToString est appele et la valeur rsultante est traite en tant quune chane de caractres.

Un peu plus tt dans cette section, nous avons rappel que la construction fonctionnelle allait tre trs utile pour dnir des requtes LINQ qui produisent des donnes XML. Pour illustrer ces propos, nous allons crer larbre XML BookParticipants. Plutt qucrire " la main" les valeurs des lments, nous allons les rcuprer en interrogeant une source de donnes compatible LINQ. Dans cet exemple, la source de donnes sera un tableau. Avant de commencer, nous avons besoin dune classe pour stocker les donnes. tant donn quil existe plusieurs types de BookParticipants, nous allons utiliser un enum pour les recenser.
enum ParticipantTypes { Author = 0, Editor } class BookParticipant { public string FirstName; public string LastName; public ParticipantTypes ParticipantType; }

Nous allons maintenant dnir et initialiser un tableau de BookParticipant. Larbre XML sera alors gnr en utilisant une requte LINQ qui extraira les donnes du tableau (voir Listing 7.16).
Listing 7.16 : Cration dun arbre XML avec une requte LINQ.
BookParticipant[] bookParticipants = new[] { new BookParticipant {FirstName = "Joe", LastName = "Rattz", ParticipantType = ParticipantTypes.Author}, new BookParticipant {FirstName = "Ewan", LastName = "Buckingham", ParticipantType = ParticipantTypes.Editor} }; XElement xBookParticipants = new XElement("BookParticipants", bookParticipants.Select(p =>

Chapitre 7

LAPI LINQ to XML

205

new XElement("BookParticipant", new XAttribute("type", p.ParticipantType), new XElement("FirstName", p.FirstName), new XElement("LastName", p.LastName)))); Console.WriteLine(xBookParticipants);

Le premier bloc de code cre le tableau bookParticipants dlments BookParticipant. Le deuxime bloc interroge ce tableau en utilisant un oprateur select et gnre des lments BookParticipant partir des lments membres du tableau. Voici larbre XML gnr :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Pour navoir aucun regret, reportez-vous au Listing 6.1 : ce code gnre le mme arbre en utilisant lAPI W3C XML DOM ! Cration dattributs avec XAttribute Contrairement ce qui se faisait dans lAPI W3C XML DOM, les attributs nhritent pas des nuds. Implments avec la classe XAttribute, ils consistent en des paires nom/valeur stockes dans une collection dobjets XAttribute appartenant un objet XElement. Grce la construction fonctionnelle, un attribut peut tre cr et ajout un lment la vole, comme dans le Listing 7.17.
Listing 7.17 : Dfinition dun attribut avec la construction fonctionnelle.
XElement xBookParticipant = new XElement("BookParticipant", new XAttribute("type", "Author")); Console.WriteLine(xBookParticipant);

Lexcution de ce code donne le rsultat suivant :


<BookParticipant type="Author" />

Parfois, il nest pas possible de crer un attribut pendant la construction de llment. Comme le montre le Listing 7.18, ces deux actions peuvent tout aussi bien tre spares.

206

LINQ to XML

Partie III

Listing 7.18 : La dfinition de llment et lajout de son attribut sont dissocis.


XElement xBookParticipant = new XElement("BookParticipant"); XAttribute xAttribute = new XAttribute("type", "Author"); xBookParticipant.Add(xAttribute); Console.WriteLine(xBookParticipant);

Le rsultat est identique au prcdent :


<BookParticipant type="Author" />

nouveau, remarquez quel point la mthode XElement.Add est exible. Elle accepte tout type dobjet et applique les mmes rgles au contenu de llment que lors de linstanciation du XElement. Cration de commentaires avec XComment La cration de commentaires avec LINQ to XML est vraiment simple. La classe utilise est XComment. Vous pouvez crer un commentaire et le lier un lment la vole, en utilisant la construction fonctionnelle (voir Listing 7.19).
Listing 7.19 : Dfinition dun commentaire avec la cration fonctionnelle.
XElement xBookParticipant = new XElement("BookParticipant", new XComment("Cette personne est retraite")); Console.WriteLine(xBookParticipant);

Voici le rsultat afch dans la console :


<BookParticipant> <!Cette personne est retraite--> </BookParticipant>

Parfois, il nest pas possible de dnir un commentaire lors de la construction de llment. Si ncessaire, vous pouvez utiliser la mthode Add pour ajouter le commentaire aprs que llment eut t construit (voir Listing 7.20).
Listing 7.20 : La dfinition de llment et lajout du commentaire sont dissocis.
XElement xBookParticipant = new XElement("BookParticipant"); XComment xComment = new XComment("Cette personne est retraite"); xBookParticipant.Add(xComment); Console.WriteLine(xBookParticipant);

Le rsultat est identique au prcdent :


<BookParticipant> <!Cette personne est retraite--> </BookParticipant>

Chapitre 7

LAPI LINQ to XML

207

Cration de conteneurs avec XContainer


XContainer est une classe abstraite. Il nest donc pas possible de linstancier. En revanche, vous pouvez instancier une de ses sous-classes : XDocument ou XElement. La classe XContainer hrite de la classe XNode et peut contenir dautres classes qui hritent de XNode.

Cration de dclarations avec XDeclaration Grce la classe XDeclaration de lAPI LINQ to XML, la dnition de dclarations est un jeu denfant. Contrairement la plupart des autres classes de lAPI LINQ to XML, les dclarations sajoutent au document XML et non un lment. Vous rappelez-vous quel point le constructeur de la classe XElement tait exible ? Toutes les classes pour lesquelles il ntait pas spcialement destin dclenchaient lappel de la mthode ToString et le texte retourn tait ajout llment sous une forme textuelle. Par inadvertance, il est donc possible dajouter une dclaration un lment en utilisant la classe XDeclaration. Cependant, si cela est permis, le rsultat ne sera pas celui escompt.
ATTENTION Les dclarations XML sappliquent au document XML. Cependant, il est possible de les appliquer un XElement, sans toutefois obtenir leffet recherch.

Il est possible de dnir une dclaration la vole et de lajouter un document XML en utilisant la construction fonctionnelle (voir Listing 7.21).
Listing 7.21 : Dfinition dune dclaration avec la construction fonctionnelle.
XDocument xDocument = new XDocument(new XDeclaration("1.0", "UTF-8", "yes"), new XElement("BookParticipant")); Console.WriteLine(xDocument);

Voici le rsultat :
<BookParticipant />

Comme vous pouvez le voir, la dclaration napparat pas dans la sortie console. Cependant, si vous dboguez le code et afchez la fentre Espion Express, vous verrez que la dclaration est bien l. Parfois, il nest pas possible de dnir la dclaration lors de la construction du document. Vous devez alors instancier la dclaration, puis laffecter la proprit Declaration du document (voir Listing 7.22).

208

LINQ to XML

Partie III

Listing 7.22 : Cration dune dclaration et affectation la proprit Declaration du document.


XDocument xDocument = new XDocument(new XElement("BookParticipant")); XDeclaration xDeclaration = new XDeclaration("1.0", "UTF-8", "yes"); xDocument.Declaration = xDeclaration; Console.WriteLine(xDocument);

Voici le rsultat :
<BookParticipant />

Tout comme dans lexemple prcdent, la dclaration napparat pas dans la sortie console. Cependant, si vous dboguez le code et afchez la fentre Espion Express, vous verrez que la dclaration est bien l. Cration de types de documents avec XDocumentType La classe XDocumentType de lAPI LINQ to XML facilite grandement la cration de types de documents (DTD). Contrairement la plupart des autres classes de lAPI LINQ to XML, les types de documents sajoutent au document XML et non un lment. Vous rappelez-vous quel point le constructeur de la classe XElement tait exible ? Toutes les classes pour lesquelles il ntait pas spcialement destin dclenchaient lappel de la mthode ToString et le texte retourn tait ajout llment sous une forme textuelle. Par inadvertance, il est donc possible dajouter une dclaration un lment en utilisant la classe XDeclaration. Cela est permis, mais ne donnera pas le rsultat escompt.
ATTENTION Les types de documents XML sappliquent au document XML. Cependant, il est possible de les appliquer un XElement, sans toutefois obtenir leffet recherch.

Il est possible de dnir un type de document la vole et de lajouter un document XML en utilisant la construction fonctionnelle (voir Listing 7.23).
Listing 7.23 : Dfinition dun type de document avec la construction fonctionnelle.
XDocument xDocument = new XDocument(new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XElement("BookParticipant")); Console.WriteLine(xDocument);

Chapitre 7

LAPI LINQ to XML

209

Voici le rsultat :
<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <BookParticipant />

Parfois, il nest pas possible de dnir le type de document lors de la construction du document. Vous devez alors instancier la dnition, puis lajouter au document XML avec la mthode add (voir Listing 7.24).
Listing 7.24 : Cration dun type de document et ajout au document.
XDocument xDocument = new XDocument(); XDocumentType documentType = new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null); xDocument.Add(documentType, new XElement("BookParticipants")); Console.WriteLine(xDocument);

Voici le rsultat :
<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <BookParticipants />

Dans ce code, aucun lment na t ajout avant de dnir le type du document. Si vous tentez de dnir le type du document aprs avoir ajout un ou plusieurs lments, lexception suivante est leve :
Lexception InvalidOperationException na pas t gre. Cette opration crerait un document incorrectement structur.

Si vous tes amen dnir un type de document aprs linstanciation du document, assurez-vous quaucun lment na t spci durant linstanciation du document ou avant la dclaration DTD. Cration de documents avec XDocument Comme il a t dit prcdemment, il nest pas ncessaire de dnir un document XML pour tre en mesure de crer un arbre ou un lment XML. Cependant, si vous tes amen le faire, LINQ to XML va vous simplier grandement la tche (voir Listing 7.25).
Listing 7.25 : Cration dun document XML avec XDocument.
XDocument xDocument = new XDocument(); Console.WriteLine(xDocument);

Ce code ne produit aucune sortie, puisque le document est vide.

210

LINQ to XML

Partie III

Cet exemple tant un peu trop simple, nous allons crer un nouveau document et y ajouter toutes les classes LINQ to XML spciquement conues pour tre ajoutes un objet XDocument (voir Listing 7.26).
Listing 7.26 : Un autre exemple lgrement plus complexe de cration dun document XML avec XDocument.
XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), new XElement("BookParticipants")); Console.WriteLine(xDocument);

Linstruction de traitement et llment auraient pu tre ajouts au niveau lment. Ils ont t placs au niveau du document pour lui donner un peu de consistance. Voici le rsultat :
<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <?BookCataloger out-of-print?> <BookParticipants />

Vous avez peut-tre remarqu que la dclaration napparat pas dans la sortie console. Comme indiqu dans les exemples de la section "Dnition de dclarations avec XDeclarations", vous pouvez dboguer le code et afcher une fentre Espion Express pour constater que la dclaration est bien l. Cration de noms avec XName Comme indiqu un peu plus tt dans ce chapitre, il nest pas possible de crer des noms en utilisant un objet XName. Cette classe na en effet aucun constructeur public. Vous ne pouvez donc pas linstancier. Un objet XName sera dni partir dune chane, ventuellement complte dun espace de noms, lorsque le code le ncessite. Un objet XName est constitu dun nom local (une chane) et dun espace de noms (un XNamespace). Dans le Listing 7.27, le code appelle le constructeur XElement dont largument est un XName.
Listing 7.27 : Dans cet exemple, un objet XName est automatiquement cr.
XElement xBookParticipant = new XElement("BookParticipant"); Console.WriteLine(xBookParticipant);

Dans cet exemple, un objet XElement est instanci partir de son nom au format chane. Lobjet XName BookParticipant est automatiquement cr et affect la proprit Name de lobjet XElement. Ici, aucun espace de noms ntant spci, le XName na donc aucun espace de noms.

Chapitre 7

LAPI LINQ to XML

211

Voici le rsultat :
<BookParticipant />

Le Listing 7.28 montre comment instancier un XElement en fournissant son nom et un espace de noms.
Listing 7.28 : Dans cet exemple, un objet XName est automatiquement cr, accompagn dun espace de noms.
XNamespace ns = "http://www.linqdev.com/Books"; XElement xBookParticipant = new XElement(ns + "BookParticipant"); Console.WriteLine(xBookParticipant);

Ce code produit la sortie XML suivante :


<BookParticipant xmlns="http://www.linqdev.com/Books" />

Pour avoir de plus amples informations sur la cration de noms en utilisant lAPI LINQ to XML, reportez-vous la section intitule "Noms, espaces de noms et prxes", un peu plus tt dans ce chapitre. Cration despaces de noms avec XNamespace Dans lAPI LINQ to XML, les espaces de noms sont implments avec la classe XNamespace. Vous trouverez un exemple de cration et dutilisation dun espace de noms dans le Listing 7.28. Pour avoir de plus amples informations sur la cration de noms en utilisant lAPI LINQ to XML, reportez-vous la section intitule "Noms, espaces de noms et prxes", un peu plus tt dans ce chapitre. Cration de nuds avec XNode
XNode tant une classe abstraite, il nest pas possible de linstancier. Vous pouvez en revanche instancier une de ses sous-classes : XComment, XContainer, XDocumentType, XProcessingInstruction ou XText. Thoriquement, un XNode est une classe quelconque qui fonctionne comme un nud dans un arbre XML.

Cration dinstructions de traitement avec XProcessingInstruction La dnition dinstructions de traitement na jamais t aussi simple quavec la classe XProcessingInstruction de lAPI LINQ to XML. Vous pouvez dnir des instructions de traitement au niveau document ou lment. Le Listing 7.29 illustre ces deux possibilits en utilisant la construction fonctionnelle.

212

LINQ to XML

Partie III

Listing 7.29 : Dfinition dune instruction de traitement aux niveaux document et lment.
XDocument xDocument = new XDocument( new XProcessingInstruction("BookCataloger", "out-of-print"), new XElement("BookParticipants", new XElement("BookParticipant", new XProcessingInstruction("ParticipantDeleter", "delete"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(xDocument);

Avant de donner le rsultat de ce code, je veux insister sur la simplicit dutilisation de la construction fonctionnelle. La comparaison de ce code avec celui du Listing 6.1 met clairement en vidence la supriorit de lAPI LINQ to XML par rapport lancienne API W3C XML. Voici les rsultats :
<?BookCataloger out-of-print?> <BookParticipants> <BookParticipant> <?ParticipantDeleter delete?> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Je suppose quil ne vous sera pas trop difcile dimaginer le code permettant dajouter une instruction de traitement aprs la construction du document, puisquil sapparente celui permettant dajouter un autre type de nud. Le Listing 7.30 donne un exemple plus complexe de cration et dajout dune instruction de traitement a fortiori.
Listing 7.30 : Ajout dinstructions de traitement aprs la construction du document et de llment.
XDocument xDocument = new XDocument(new XElement("BookParticipants", new XElement("BookParticipant", new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); XProcessingInstruction xPI1 = new XProcessingInstruction("BookCataloger", "out-of-print"); xDocument.AddFirst(xPI1); XProcessingInstruction xPI2 = new XProcessingInstruction("ParticipantDeleter", "delete"); XElement outOfPrintParticipant = xDocument .Element("BookParticipants") .Elements("BookParticipant") .Where(e => ((string)((XElement)e).Element("FirstName")) == "Joe" && ((string)((XElement)e).Element("LastName")) == "Rattz") .Single<XElement>(); outOfPrintParticipant.AddFirst(xPI2); Console.WriteLine(xDocument);

Chapitre 7

LAPI LINQ to XML

213

Plusieurs passages de ce listing sont dignes dintrt. Comme vous pouvez le voir, le document et larbre XML ont t crs en utilisant la construction fonctionnelle. Une instruction de traitement a t ajoute au document aprs sa construction. Ici, cest la mthode XElement.AddFirst qui a t choisie pour crer le premier nud enfant du document (cette mthode a t prfre XElement.Add, qui ajoute un nud la n des nuds enfants du document. cet emplacement, il pourrait tre trop tard pour honorer une instruction de traitement). Pour ajouter une instruction de traitement un des lments, nous devons y faire rfrence. Nous aurions pu construire un objet XElement et mmoriser sa rfrence, mais jai pens quil tait temps dintroduire les possibilits des requtes LINQ venir. Comme vous pouvez le voir, la requte utilise est plutt complexe. Elle extrait du document llment BookParticipants en utilisant la mthode Element (voir "Dplacements XML", un peu plus loin dans cette section). La squence dobjets XElement BookParticipant, pour laquelle les lments FirstName et LastName ont respectivement pour valeur "Joe" et "Ratz", est alors rcupre. Les valeurs de FirstName et LastName ont t obtenues en utilisant loprateur de casting (string). Loprateur Where retourne un IEnumerable<T>, alors que nous avons besoin dun XElement. La rponse retourne par la requte tant unique, nous pouvons utiliser loprateur de requte standard diffr First de LINQ to Object. Une fois la rfrence lobjet XElement obtenue, il est trs simple dajouter linstruction de traitement et dafcher les rsultats. Voici les rsultats afchs dans la console :
<?BookCataloger out-of-print?> <BookParticipants> <BookParticipant> <?ParticipantDeleter delete?> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Cration dlments streaming avec XStreamingElement Dans la deuxime partie de cet ouvrage, nous avons vu que plusieurs des oprateurs de requte standard diffraient leur excution jusqu lnumration des donnes retournes. Si vous utilisez de tels oprateurs tout en voulant obtenir une projection au format XML, il faudra choisir entre le ct diffr des oprateurs de requte standard et lexcution immdiate dune requte de projection LINQ to XML. titre dexemple, dans le Listing 7.31, le quatrime lment du tableau names est modi et, pourtant, lorsque nous afchons les valeurs de lobjet XElement, larbre XML contient les donnes originales. Ceci vient du fait que llment XNames a t entirement cr avant que llment du tableau names nait t modi.

214

LINQ to XML

Partie III

Listing 7.31 : Excution immdiate de larbre XML.


string[] names = { "John", "Paul", "George", "Pete" }; XElement xNames = new XElement("Beatles", from n in names select new XElement("Name", n)); names[3] = "Ringo"; Console.WriteLine(xNames);

Ce code produit larbre XML suivant :


<Beatles> <Name>John</Name> <Name>Paul</Name> <Name>George</Name> <Name>Pete</Name> </Beatles>

Comme vous le voyez, chaque objet XElement de la squence devient un lment enfant de Beatles. Llment name[3] a t initialis "Ringo" avant dafcher larbre XML et, pourtant, le dernier lment de cet arbre contient toujours la valeur originale " Pete". Ceci vient du fait que la squence names doit tre numre pour pouvoir construire lobjet XElement. La requte est donc excute immdiatement. Si vous voulez que la construction de larbre XML soit diffre, il faut utiliser des lments streaming implments avec la classe XStreamingElement. Le Listing 7.32 reprsente le mme exemple, mais cette fois-ci nous utilisons des objets XStreamingElement la place des objets XElement.
Listing 7.32 : Excution diffre de la construction de larbre XML avec la classe XStreamingElement.
string[] names = { "John", "Paul", "George", "Pete" }; XStreamingElement xNames = new XStreamingElement("Beatles", from n in names select new XStreamingElement("Name", n)); names[3] = "Ringo"; Console.WriteLine(xNames);

Si ce code fonctionne, le dernier nud Name devrait avoir la valeur "Ringo". Voici le rsultat :
<Beatles> <Name>John</Name> <Name>Paul</Name> <Name>George</Name> <Name>Ringo</Name> </Beatles>

Chapitre 7

LAPI LINQ to XML

215

Cration de textes avec XText Comme le prouve le Listing 7.33, la dnition de texte est trs simple.
Listing 7.33 : Cration dun lment et affectation dune valeur chane.
XElement xFirstName = new XElement("FirstName", "Joe"); Console.WriteLine(xFirstName);

Voici le rsultat :
<FirstName>Joe</FirstName>

Une chose napparat pas dans ce listing : la chane "Joe" est transforme en un objet XText avant dtre ajoute lobjet XElement. En examinant lobjet xFirstName dans le dbogueur, on se rend compte quil contient un seul nud : un objet XText de valeur "Joe". tant donn que cette conversion est automatique, dans la plupart des cas vous ne serez pas oblig de construire un objet texte. Cependant, si cela est ncessaire, il vous sufra dinstancier un objet XText, comme dans le Listing 7.34.
Listing 7.34 : Cration dun nud texte et utilisation dans linitialisation dun XElement.
XText xName = new XText("Joe"); XElement xFirstName = new XElement("FirstName", xName); Console.WriteLine(xFirstName);

Ce code donne le mme rsultat que le prcdent. Si vous utilisez le dbogueur pour examiner ltat interne de lobjet xFirstName, vous verrez quil est identique celui de lobjet cr dans lexemple prcdent :
<FirstName>Joe</FirstName>

Dnition dun objet CData avec XCData Le Listing 7.35 donne un exemple de dnition dun objet CData.
Listing 7.35 : Cration dun nud CData puis initialisation dun XElement.
XElement xErrorMessage = new XElement("HTMLMessage", new XCData("<H1>Invalid user id or password.</H1>")); Console.WriteLine(xErrorMessage);

Voici le rsultat :
<HTMLMessage><![CDATA[<H1>Invalid user id or password.</H1>]]></HTMLMessage>

216

LINQ to XML

Partie III

Sauvegarde de chiers XML


La cration, la modication et la suppression de donnes XML nauraient aucun intrt sil ntait pas possible de sauvegarder les donnes. Cette section va vous montrer plusieurs techniques de sauvegarde. Sauvegardes avec XDocument.Save() Vous pouvez sauvegarder vos donnes XML en utilisant un des prototypes de la mthode XDocument.Save :
void void void void void XDocument.Save(string filename); XDocument.Save(TextWriter textWriter); XDocument.Save(XmlWriter writer); XDocument.Save(string filename, SaveOptions options); XDocument.Save(TextWriter textWriter, SaveOptions options);

Le Listing 7.36 donne un exemple de sauvegarde du document XML dans le dossier du projet.
Listing 7.36 : Sauvegarde dun document avec la mthode XDocument.Save.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); xDocument.Save("bookparticipants.xml");

La mthode Save a t appele sur un objet de type XDocument. Ceci est possible car les mthodes Save sont des mthodes dinstances. Comme vous le verrez un peu plus loin, les mthodes Load sont en revanche des mthodes statiques. Elles doivent tre appeles sur des classes XDocument ou XElement. Voici le contenu du chier bookparticipants.xml, ouvert dans un diteur de texte tel que le Bloc-notes de Windows.
<?xml version="1.0" encoding="utf-8"?> <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Ce document XML est facile lire parce que la version de la mthode Save met en forme les donnes. Si, en revanche, nous appelions la mthode Save suivante :
xDocument.Save("bookparticipants.xml", SaveOptions.DisableFormatting);

Chapitre 7

LAPI LINQ to XML

217

les rsultats seraient bien moins lisibles :


<?xml version="1.0" encoding="utf-8"?><BookParticipants><BookParticipant type= "Author" experience="first-time" language="English"><FirstName>Joe</FirstName> <LastName>Rattz</LastName></BookParticipant></BookParticipants>

Les donnes sont places sur une seule et mme ligne. Pour vous en assurer, ouvrez le chier dans un diteur de texte. Si vous louvriez dans un navigateur Internet, elles seraient automatiquement mises en forme pour apparatre comme dans le rsultat du Listing 7.36.
INFO Passe en deuxime argument de la mthode Save, la valeur SaveOptions.None produit le mme rsultat que le Listing 7.36.

Sauvegarde avec XElement.Save Je lai rpt plusieurs fois, avec lAPI LINQ to XML, il nest pas ncessaire de crer un document XML. Ceci reste dactualit quant la sauvegarde de donnes XML. La classe XElement propose plusieurs mthodes qui abondent dans ce sens :
void void void void void XElement.Save(string filename); XElement.Save(TextWriter textWriter); XElement.Save(XmlWriter writer); XElement.Save(string filename, SaveOptions options); XElement.Save(TextWriter textWriter, SaveOptions options);

Le Listing 7.37 est un exemple trs proche du prcdent mais, ici, aucun document XML nest cr.
Listing 7.37 : Sauvegarde dun lment avec la mthode XElement.
XElement bookParticipants = new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz"))); bookParticipants.Save("bookparticipants.xml");

Le rsultat est identique au prcdent :


<?xml version="1.0" encoding="utf-8"?> <BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

218

LINQ to XML

Partie III

Lecture de chiers XML


Cette section passe en revue quelques-unes des techniques qui permettent de lire des donnes stockes dans un chier XML. Lecture avec XDocument.Load() Voici la liste des mthodes qui vous permettront de lire des donnes stockes dans un chier XML :
static static static static static static XDocument XDocument XDocument XDocument XDocument XDocument XDocument.Load(string uri); XDocument.Load(TextReader textReader); XDocument.Load(XmlReader reader); XDocument.Load(string uri, LoadOptions options); XDocument.Load(TextReader textReader, LoadOptions options); XDocument.Load(XmlReader reader, LoadOptions options);

Ces mthodes sont les parfaites rpliques des mthodes XDocument.Save. Il existe cependant quelques diffrences qui valent la peine dtre signales. Tout dabord, les mthodes Save tant des mthodes dinstance, elles sappliquent un objet XDocument ou XElement. Les mthodes Load tant des mthodes statiques, vous devez appeler la classe XDocument elle-mme. Par ailleurs, les mthodes Save dont le premier paramtre est de type string doivent spcier le nom du chier, alors que les mthodes Load dont le premier paramtre est de type string acceptent les URI. Le Tableau 7.2 dresse la liste des valeurs possibles du paramtre LoadOptions.
Tableau 7.2 : Le paramtre LoadOptions.

Valeur LoadOptions.None LoadOptions.PreserveWhitespace LoadOptions.SetLineInfo

Description Aucune option de chargement. Conservation des sauts de ligne et autres espaces dans la source XML. Cette option permet dobtenir la ligne et la position des objets hrits de XObject en utilisant linterface IXmlLineInfo. Cette option permet dobtenir lURI des objets qui hritent de XObject.

LoadOptions.SetBaseUri

Ces options peuvent tre combines en utilisant loprateur OR (|). Mais, attention, en fonction du contexte certaines options ne donneront pas les rsultats escompts. Par exemple, lorsquun lment ou un document est cr partir dune chane, aucune ligne dinformation ni aucun URI ne sont disponibles. De mme, lorsquun document est cr partir dun XmlReader, aucun URI nest disponible. Le Listing 7.38 montre comment lire le document XML cr dans lexemple prcdent.

Chapitre 7

LAPI LINQ to XML

219

Listing 7.38 : Lecture dun document avec la mthode XDocument.Load.


XDocument xDocument = XDocument.Load("bookparticipants.xml", LoadOptions.SetBaseUri | LoadOptions.SetLineInfo); Console.WriteLine(xDocument); XElement firstName = xDocument.Descendants("FirstName").First(); Console.WriteLine("FirstName : ligne {0}, position{1}", ((IXmlLineInfo)firstName).LineNumber, ((IXmlLineInfo)firstName).LinePosition); Console.WriteLine("Adresse URI de llment FirstName :{0}", firstName.BaseUri);

INFO Pour que le type IXmlLineInfo puisse tre utilis, vous devez ajouter une directive using System.xml; ou faire rfrence lespace de noms correspondant.

Ce code charge le chier XML cr dans lexemple prcdent. Aprs le chargement et lafchage du document, nous dnissons une rfrence pour llment FirstName et afchons sa ligne et sa position dans le document XML source. Le code se termine par lafchage de ladresse URI de llment FirstName. Voici les rsultats :
<BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> FirstName : ligne 4, position 6 Adresse URI de llment FirstName: file:///C:/Documents and Settings//Projects/ LINQChapter7/LINQChapter7/bin/Debug/bookparticipants.xml

Le document a bien lallure souhaite. Cependant, la ligne de llment FirstName na pas lair de correspondre. Un rapide coup dil au rsultat du Listing 7.37 aura tt fait de vous convaincre que cette information est correcte. En effet, la premire ligne est rserve la dclaration du document, et cette ligne napparat pas dans lafchage du document :
<?xml version="1.0" encoding="utf-8"?>

Lecture avec XElement.Load() Virtuellement, la lecture dun lment ou dun document ne prsente aucune diffrence. Voici les mthodes permettant de lire des donnes stockes dans un XDocument ou un XElement :
static XElement XElement.Load(string uri); static XElement XElement.LoadTextReader textReader); static XElement XElement.Load(XmlReader reader);

220

LINQ to XML

Partie III

static XElement XElement.Load(string uri, LoadOptions options); static XElement XElement.Load(TextReader textReader, LoadOptions options); static XElement XElement.Load(XmlReader reader, LoadOptions options);

Tout comme les mthodes XDocument.Save, ces mthodes sont statiques. Elles doivent donc tre appeles partir de la classe XElement. Le Listing 7.39 montre comment lire les donnes XML sauvegardes avec la mthode XElement.Save dans le Listing 7.37.
Listing 7.39 : Lecture dun document avec la mthode XElement.Load.
XElement xElement = XElement.Load("bookparticipants.xml"); Console.WriteLine(xElement);

Les rsultats sont bien conformes nos attentes :


<BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Tout comme pour XDocument.Load, il existe des surcharges de la mthode XElement.Load qui permettent dutiliser le paramtre LoadOptions. Reportez-vous la section intitule "Lecture avec XDocument.Load()" pour avoir de plus amples informations ce sujet. Extraction avec XDocument.Parse() ou XElement.Parse() Combien de fois avez-vous extrait des donnes XML en passant par des chanes de caractres ? Il faut bien avouer que cette tche nest pas des plus agrables ! Mais, rassurezvous, lAPI LINQ to XML va apporter une rponse lgante cette problmatique. La mthode statique Parse est accessible aux classes XDocument et XElement. Par son intermdiaire, il est possible dextraire des donnes XML. Fort de ce qui a t vu dans ce chapitre, vous ne devez avoir aucune difcult imaginer que, si lextraction de donnes est possible depuis la classe XDocument, elle lest aussi depuis la classe XElement. Nous allons donc raisonner sur un seul exemple relatif la classe XElement. Dans la section intitule "Sauvegardes avec XDocument.Save", vous avez pu voir linuence du paramtre LoadOptions lorsquil est initialis DisableFormatting : les donnes sont sauvegardes sur une seule et mme ligne XML. Le Listing 7.40 utilise cette chane XML (en ayant pris le soin dchapper les guillemets), lextrait dans un lment et afche le rsultat.
Listing 7.40 : Extraction dune chane XML dans un lment.
string xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?><BookParticipants>" + "<BookParticipant type=\"Author\" experience=\"first-time\" language=" + "\"English\"><FirstName>Joe</FirstName><LastName>Rattz</LastName>" + "</BookParticipant></BookParticipants>";

Chapitre 7

LAPI LINQ to XML

221

XElement xElement = XElement.Parse(xml); Console.WriteLine(xElement);

Voici le rsultat :
<BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Impressionnant, nest-ce pas ? Rappelez-vous les vieux jours o vous deviez crer un document en utilisant la classe XmlDocument de lAPI W3C XML DOM. Le document ntant plus llment de rfrence, un simple appel la mthode Parse suft dsormais pour transformer une chane XML en un arbre XML !

Dplacements XML
Les dplacements XML sont effectus par lintermdiaire de 4 proprits et de 11 mthodes. Dans cette section, nous allons nous efforcer dutiliser le mme code pour chacune des proprits et des mthodes, en modiant un simple argument chaque fois que cela sera possible. Le Listing 7.41 est un exemple de construction dun document XML complet.
Listing 7.41 : Le code dont seront drivs les prochains exemples.
// Dfinition dune rfrence vers un des lments de larbre XML XElement firstParticipant; XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(xDocument);

La premire ligne cre une rfrence au premier lment BookParticipant. Ceci an davoir un lment par rapport auquel effectuer le dplacement (la variable firstParticipant nest pas utilise dans ce premier exemple, mais elle le sera dans les suivants).

222

LINQ to XML

Partie III

Le document est pass en argument de la mthode Console.WriteLine. Tout le contenu du document XML sera donc afch. Dans les prochains exemples, nous choisirons un autre argument pour illustrer les diffrents types de dplacements. Voici le rsultat :
<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <?BookCataloger out-of-print?> <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Proprits de dplacement Nous commencerons par les proprits de dplacement primaires. Lorsquune direction (up, down, etc.) est spcie, elle est relative llment sur lequel la mthode est appele. Dans les exemples suivants, la rfrence au premier lment BookParticipant sera prise comme lment de base pour le dplacement.
Nud suivant avec XNode.NextNode La proprit NextNode obtient le nud frre du nud courant (voir Listing 7.42).
Listing 7.42 : Obtention du nud frre suivant dun objet XElement avec la proprit NextNode.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.NextNode);

Chapitre 7

LAPI LINQ to XML

223

Llment de base tant le premier lment BookParticipant, la proprit NextNode devrait renvoyer vers le deuxime lment BookParticipant. Voici le rsultat :
<BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant>

Nud prcdent avec XNode.PreviousNode La proprit PreviousNode donne accs au nud frre prcdent. Pour illustrer cette proprit, nous allons partir du nud FirstParticipant. Nous lui appliquerons la proprit NextNode pour obtenir le nud frre suivant puis la proprit PreviousNode pour obtenir le nud frre prcdent, cest--dire le nud de dpart (voir Listing 7.43).
Listing 7.43 : Obtention du nud frre prcdent dun objet XElement avec la proprit PreviousNode.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.NextNode.PreviousNode);

Cest bien le premier lment qui est afch dans la console :


<BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant>

Remonter au niveau du document avec XObject.Document Pour remonter au niveau du document partir dun XElement quelconque, il suft dutiliser la proprit Document (voir Listing 7.44, et en particulier lappel la mthode WriteLine).

224

LINQ to XML

Partie III

Listing 7.44 : Accs au document partir dun objet XElement en utilisant la proprit Document.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Document);

Tout comme pour le Listing 7.41, ce code afche la totalit du document :


<!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <?BookCataloger out-of-print?> <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Remonter dun niveau avec XObject.Parent Pour obtenir llment parent dun objet XElement, il vous suft dutiliser la proprit Parent (voir Listing 7.45, et en particulier lappel la mthode WriteLine).
Listing 7.45 : Accs au parent de lobjet firstParticipant en utilisant la proprit Parent.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")),

Chapitre 7

LAPI LINQ to XML

225

new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Parent);

Voici le rsultat :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Ne vous laissez pas abuser : il sagit non pas du document complet, mais du parent de lobjet firstParticipant. Remarquez labsence du DTD et de linstruction de transformation. Mthodes de dplacement tant donn que les mthodes de dplacement retournent des squences composes de plusieurs nuds, linstruction Console.WriteLine va tre remplace par une boucle foreach qui permettra dafcher les diffrents nuds :
foreach(XNode node in firstParticipant.Nodes()) { Console.WriteLine(node); }

Dans les diffrents exemples, seule diffrera la mthode applique lobjet firstParticipant dans la boucle foreach.
Nuds enfants avec XContainer.Nodes() La mthode Nodes() retourne une collection de nuds enfants XNode de llment spci (voir Listing 7.46). toutes ns utiles, nous rappelons quune squence est un objet IEnumerable<T>.
Listing 7.46 : Accs aux enfants de lobjet firstParticipant en utilisant la proprit Nodes.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant",

226

LINQ to XML

Partie III

new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Nodes());

Voici le rsultat :
<FirstName>Joe</FirstName> <LastName>Rattz</LastName>

Cette mthode retourne les lments enfants (XElement), mais galement les autres types de nuds : commentaires (XComment), texte (XText), instructions de traitement (XProcessingInstruction), type de document (XDocumentType). En revanche, elle ne retourne pas les attributs puisque ces derniers ne sont pas des nuds. Pour mieux illustrer la mthode Nodes(), plusieurs nuds enfants ont t ajouts llment firstParticipant dans le Listing 7.47.
Listing 7.47 : Accs aux diffrents types denfants de lobjet firstParticipant en utilisant la proprit Nodes.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("Nouvel auteur"), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Nodes()) { Console.WriteLine(node); }

Cet exemple est diffrent du prcdent. Ici, llment firstParticipant a galement un enfant de type XComment et un autre de type XProcessingInstruction. Voici le rsultat afch aprs lappui sur Ctrl+F5 :
<!--Nouvel auteur--> <?AuthorHandler new?>

Chapitre 7

LAPI LINQ to XML

227

<FirstName>Joe</FirstName> <LastName>Rattz</LastName>

Le commentaire et linstruction de traitement sont galement afchs. Nous allons maintenant vous montrer comment limiter la sortie un seul type de nud en utilisant loprateur OfType (voir Chapitre 4). Le Listing 7.48 ne retourne que les nuds de type lment. Il a suf pour cela de changer largument de la boucle foreach.
Listing 7.48 : Utilisation de loprateur OfType pour ne retourner que les lments.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("Nouvel auteur"), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Nodes().OfType<XElement>()) { Console.WriteLine(node); }

Bien que les nuds de type XComment et XProcessingInstruction soient implments dans ce code, ils napparaissent pas dans les rsultats :
<FirstName>Joe</FirstName> <LastName>Rattz</LastName>

Vous commencez certainement comprendre quel point les nouvelles caractristiques du langage C# et le langage LINQ vont faciliter les choses. Nest-il pas intressant de pouvoir utiliser les oprateurs de requte standard pour restreindre les nuds XML renvoys par une mthode LINQ to XML ? Supposons maintenant que vous ne vouliez obtenir que les commentaires enfants de llment firstParticipant. Il vous suft dutiliser une autre variante de loprateur OfType, comme dans le Listing 7.49.

228

LINQ to XML

Partie III

Listing 7.49 : Utilisation de loprateur OfType pour ne retourner que les lments.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("Nouvel auteur"), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Nodes().OfType<XComment>()) { Console.WriteLine(node); }

Voici le rsultat :
<!--Nouvel auteur-->

Que diriez-vous dutiliser loprateur OfType pour limiter la sortie aux attributs ? Eh bien, ceci est tout bonnement impossible puisque, selon lAPI LINQ to XML, les attributs ne sont pas des nuds de larbre XML. Ils consistent en une squence de paires nom/valeur attache un lment. Pour obtenir les attributs de lobjet firstParticipant, le code doit tre modi comme dans le Listing 7.50.
Listing 7.50 : Accs aux attributs dun lment avec la mthode Attributes.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("Nouvel auteur"), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))));

Chapitre 7

LAPI LINQ to XML

229

foreach (XAttribute attr in firstParticipant.Attributes()) { Console.WriteLine(attr); }

Comme vous pouvez le voir, nous avons chang largument de la boucle foreach, mais galement le type de la variable dnumration, puisque XAttribute nhrite pas de XNode. Voici le rsultat :
type="Author"

Nuds enfants avec XContainer.Elements() LAPI LINQ to XML tant centre sur les lments, Microsoft a dni la mthode Elements() pour retourner une collection constitue des lments enfants dun lment.

Le Listing 7.51 donne un exemple dutilisation de cette mthode. Tout en utilisant une autre technique, il est cependant quivalent au Listing 7.48.
Listing 7.51 : Accs aux lments enfants dun lment avec la mthode Elements.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XNode node in firstParticipant.Elements()) { Console.WriteLine(node); }

Ce code afche le mme rsultat que le Listing 7.48 :


<FirstName>Joe</FirstName> <LastName>Rattz</LastName>

Il existe galement une version surcharge de la mthode Elements qui permet de passer le nom de llment recherch (voir Listing 7.52).

230

LINQ to XML

Partie III

Listing 7.52 : Accs aux lments enfants dun lment nomm avec la mthode XContainer.Elements.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XContainer.Elements(FirstName) { Console.WriteLine(node); }

Voici le rsultat :
<FirstName>Joe</FirstName>

Premier nud enfant avec XContainer.Element() La mthode Element retourne le premier lment enfant de llment pass en argument. Contrairement la mthode prcdende, cest non pas une squence qui est retourne, mais un lment unique (voir Listing 7.53).
Listing 7.53 : Accs au premier lment enfant dun lment nomm avec la mthode Element.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"),

Chapitre 7

LAPI LINQ to XML

231

new XElement("LastName", "Buckingham")))); Console.WriteLine(firstParticipant.Element("FirstName"));

Voici le rsultat :
<FirstName>Joe</FirstName>

Anctres dun nud avec XNode.Ancestors() La proprit Parent permet dobtenir lanctre direct (le parent) dun nud. Si vous dsirez obtenir une squence contenant tous les anctres dun nud, jusquau niveau hirarchique le plus lev, vous utiliserez la mthode Ancestors. Seuls les lments (et non tous les nuds) anctres sont retourns.

Pour mieux illustrer cette mthode, nous allons ajouter plusieurs nuds enfants llment FirstName du premier participant. Par ailleurs, plutt qunumrer les anctres du premier participant, nous utiliserons la mthode Element pour nous dplacer de deux niveaux hirarchiques vers le bas an datteindre llment NickName. Le nombre danctres sera ainsi plus lev, ce qui facilitera la comprhension de la mthode Ancestors (voir Listing 7.54).
Listing 7.54 : Anctres dun objet XElement avec la mthode Ancestors.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", new XText("Joe"), new XElement("NickName", "Joey")), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XElement element in firstParticipant. Element("FirstName").Element("NickName").Ancestors()) { Console.WriteLine(element.Name); }

Comme vous pouvez le voir, un objet XText initialis "Joe" et un XElement nomm NickName ont t ajouts llment FirstName. Le dernier bloc dinstructions recherche les anctres de llment NickName. La boucle foreach est excute au niveau

232

LINQ to XML

Partie III

XElement (et non XNode). Ainsi, linstruction WriteLine peut accder la proprit Name des lments retourns. Plutt quafcher le code XML de chaque lment anctre, nous nous contenterons dafcher leur nom. Ceci uniquement dans un souci de clart.

Voici les rsultats :


FirstName BookParticipant BookParticipants

Anctres dun nud avec XElement.AncestorsAndSelf() Cette mthode est comparable la mthode Ancestors, mais ses rsultats incluent llment sur lequel seffectue la recherche. Le Listing 7.55 est le mme que le prcdent, ceci prs que la mthode AncestorsAndSelf remplace la mthode Ancestors.
Listing 7.55 : Anctres dun objet XElement avec la mthode AncestorsAndSelf.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", new XText("Joe"), new XElement("NickName", "Joey")), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XElement element in firstParticipant. Element("FirstName").Element("NickName").AncestorsAndSelf()) { Console.WriteLine(element.Name); }

Les rsultats sont identiques ceux du listing prcdent mais, cette fois, ils incluent llment NickName :
NickName FirstName BookParticipant BookParticipants

Chapitre 7

LAPI LINQ to XML

233

Descendants dun nud avec XContainer.Descendants() Pour obtenir une squence contenant tous les lments descendant dun nud, vous utiliserez la mthode Descendants. Vous pouvez galement utiliser la mthode DescendantNodes pour obtenir tous les nuds descendant dun autre nud. Le Listing 7.56 est le mme que le prcdent mais, ici, cest la mthode Descendants qui est appele.
Listing 7.56 : Descendants dun objet XElement avec la mthode Descendants.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", new XText("Joe"), new XElement("NickName", "Joey")), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XElement element in firstParticipant.Descendants()) { Console.WriteLine(element.Name); }

Voici les rsultats :


FirstName NickName LastName

Tous les lments qui descendent de llment firstParticipant, mais pas les autres types de nuds, sont bien lists dans la console.
Descendants dun nud avec XElement.DescendantsAndSelf() DescendantsAndSelf est le pendant de AncestorsAndSelf. Cette mthode renvoie les descendants de llment sur lequel porte la requte, en y incluant cet lment. Le Listing 7.57 est le mme que le prcdent, ceci prs que la mthode DescendantsAndSelf remplace la mthode Descendants.
Listing 7.57 : Descendants dun objet XElement avec la mthode DescendantsAndSelf.
XElement firstParticipant; // Le document complet

234

LINQ to XML

Partie III

XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("This is a new author."), new XProcessingInstruction("AuthorHandler", "new"), new XAttribute("type", "Author"), new XElement("FirstName", new XText("Joe"), new XElement("NickName", "Joey")), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XElement element in firstParticipant.DescendantsAndSelf()) { Console.WriteLine(element.Name); }

Les rsultats incluent dsormais le nom de llment firstParticipant :


BookParticipant FirstName NickName LastName

Nuds frres suivants avec XNode.NodesAfterSelf() Pour illustrer cet exemple, deux commentaires ont t ajouts llment BookParticipants. Les commentaires XComment tant des nuds et non des lments, les rsultats mettront en vidence que la mthode NodesAfterSelf retourne tous les types de nuds frres du nud cibl (voir Listing 7.58).
Listing 7.58 : Nuds frres dun objet XNode avec la mthode NodesAfterSelf.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", new XComment("Dbut de la liste"), firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")), new XComment("Fin de la liste")));

Chapitre 7

LAPI LINQ to XML

235

foreach (XNode node in firstParticipant.NodesAfterSelf()) { Console.WriteLine(node); }

Les nuds ajouts sont tous deux frres des deux lments BookParticipant. Cette modication du document XML concerne les exemples des mthodes NodesAfterSelf, ElementsAfterSelf, NodesBeforeSelf et ElementsBeforeSelf. Tous les nuds frres situs aprs le premier nud BookParticipant sont numrs. Voici le rsultat :
<BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> <!Fin de la liste-->

Comme vous le voyez, le dernier commentaire est inclus dans le rsultat. Cest en effet un nud frre du nud situ aprs le premier BookParticipant. Il se trouve au mme niveau hirarchique que les lments BookParticipant. Si les lments FirstName et LastName sont afchs dans les rsultats, cest parce que la mthode ToString est applique au nud BookParticipant. Cette mthode ne se limite pas aux lments. Elle retourne galement les autres types de nuds. Si vous voulez ltrer les nuds retourns un certain type, utilisez loprateur TypeOf. Si ce ne sont que les lments qui vous intressent, utilisez la mthode ElementsAfterSelf (voir section suivante).
lments frres suivants avec XNode.ElementsAfterSelf() Nous utiliserons le mme document XML que dans lexemple prcdent. Pour ne retenir que les lments frres qui suivent le nud rfrenc, la mthode ElementsAfterSelf est appele (voir Listing 7.59).
Listing 7.59 : lments frres qui suivent le nud rfrenc avec la mthode ElementsAfterSelf.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", new XComment("Dbut de la liste"), firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"),

236

LINQ to XML

Partie III

new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")), new XComment("Fin de la liste"))); foreach (XNode node in firstParticipant.ElementsAfterSelf()) { Console.WriteLine(node); }

Voici le rsultat :
<BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant>

Cette fois-ci, tant donn que le commentaire nest pas un lment, il est exclu du rsultat. Nous rappelons que les lments FirstName et LastName sont afchs dans les rsultats car la mthode ToString est applique au nud BookParticipant.
Nuds frres prcdents avec XNode.NodesBeforeSelf() Cet exemple utilise le mme document XML que le Listing 7.58. NodesBeforeSelf se comporte comme NodesAfterSelf, si ce nest quelle retourne les nuds frres qui prcdent le nud rfrenc. Dans cet exemple, nous invoquons la mthode NextNode avant dappeler NodesBeforeSelf pour que le rsultat ne soit pas vide (voir Listing 7.60).
Listing 7.60 : Nuds frres qui prcdent le nud rfrenc avec la mthode NodesBeforeSelf.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", new XComment("Dbut de la liste"), firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")), new XComment("Fin de la liste"))); foreach (XNode node in firstParticipant.NextNode.NodesBeforeSelf()) { Console.WriteLine(node); }

Chapitre 7

LAPI LINQ to XML

237

La mthode NextNode donne accs au deuxime participant. En lui appliquant la mthode ElementsBeforeSelf, les lments frres qui prcdent le deuxime participant sont lists. Ici, le premier participant. Voici le rsultat :
<!--Dbut de la liste--> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Ratz</LastName> </BookParticipant>

Comme vous pouvez le voir, les nuds frres sont lists dans lordre du document. On aurait pu sattendre ce que les nuds soient lists depuis le nud courant vers le dbut du document. Nous aurions alors appel loprateur Reverse ou InDocumentOrder (voir chapitre suivant) pour rtablir lordre adquat. Mais il nen est rien. Une fois encore, ne soyez pas perturb si les lments FirstName et LastName font partie des rsultats. Ils ne sont pas retourns par la mthode NodesBeforeSelf, mais proviennent de la mthode ToString, applique au nud BookParticipant par la mthode Console.WriteLine.
lments frres prcdents avec XNode.ElementsBeforeSelf() Cet exemple utilise le mme document XML que le Listing 7.58. ElementsBeforeSelf se comporte comme ElementsAfterSelf, si ce nest quelle retourne les lments frres qui prcdent le nud rfrenc. Dans cet exemple, nous invoquons la mthode NextNode avant dappeler NodesBeforeSelf pour que le rsultat ne soit pas vide (voir Listing 7.61).
Listing 7.61 : lments frres qui prcdent le nud rfrenc avec la mthode ElementsBeforeSelf.
XElement firstParticipant; // Le document complet XDocument xDocument = new XDocument( new XDeclaration("1.0", "UTF-8", "yes"), new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XProcessingInstruction("BookCataloger", "out-of-print"), // Utilisation de la rfrence au premier lment BookParticipant new XElement("BookParticipants", new XComment("Dbut de la liste"), firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")), new XComment("Fin de la liste"))); foreach (XNode node in firstParticipant.NextNode.ElementsBeforeSelf()) { Console.WriteLine(node); }

238

LINQ to XML

Partie III

La mthode NextNode donne accs au deuxime participant. En lui appliquant la mthode ElementsBeforeSelf, les lments frres qui prcdent le deuxime participant sont lists : ici, le premier participant. Bien entendu, le commentaire nest pas afch puisquil ne sagit pas dun lment :
<BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Ratz</LastName> </BookParticipant>

Modication de donnes XML


Avec lAPI LINQ to XML, la modication de donnes XML est un vrai jeu denfant : il suft dutiliser les mthodes ddies pour ajouter, modier ou supprimer les nuds ou les lments de votre choix. Comme il a t dit auparavant, LINQ to XML travaille essentiellement avec des objets de type XElement. Cest la raison pour laquelle la plupart des exemples qui vont suivre concerneront ce type dobjet. Nous nous intresserons aux classes qui hritent de XNode, puis aux attributs. Ajout de nuds Les diffrentes mthodes tudies dans cette section utiliseront larbre dni dans le Listing 7.62.
Listing 7.62 : Larbre de base contient un seul participant.
// Un document incluant un seul participant XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(xDocument);

Ce code dnit un arbre XML contenant un seul participant :


<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

INFO Tous les exemples de cette section sont galement utilisables avec les classes LINQ to XML qui hritent de la classe XNode.

Chapitre 7

LAPI LINQ to XML

239

En complment des mthodes passes en revue dans cette section, vous pouvez galement vous reporter la section intitule "XElement.SetElementValue() sur des objets enfants de XElement", un peu plus loin dans ce chapitre.

XContainer.Add()
Pour ajouter des nuds un arbre XML, vous utiliserez essentiellement cette mthode. Elle ajoute un nud aprs le dernier nud enfant du nud spci (voir Listing 7.63).
Listing 7.63 : Ajout dun nud aprs le dernier nud enfant du nud spcifi avec Add.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz"))); xDocument.Element("BookParticipants").Add( new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))); Console.WriteLine(xDocument);

Le code en gras a t ajout au Listing 7.62 pour insrer un lment BookParticipant aux lments BookParticipant dj existants. Applique au document, la mthode Element renvoie llment BookParticipants. Il suft alors dutiliser la mthode Add pour lui ajouter un lment BookParticipant. Voici le rsultat :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

La mthode Add a ajout un nouvel lment BookParticipant la n des nuds enfants de llment BookParticipants. Elle est aussi exible que le constructeur XElement et autorise la construction fonctionnelle.

XContainer.AddFirst()
Pour ajouter un nud en premire position des nuds enfants du nud spci, vous utiliserez la mthode AddFirst. Le code utilis est le mme que dans lexemple prcdent mais, ici, la mthode appele est AddFirst (voir Listing 7.64).

240

LINQ to XML

Partie III

Listing 7.64 : Ajout dun nud avant le nud enfant du nud spcifi avec AddFirst.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); xDocument.Element("BookParticipants").AddFirst( new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))); Console.WriteLine(xDocument);

Comme on pouvait sy attendre, le nouvel lment BookParticipant est ajout devant les nuds enfants de llment BookParticipants :
<BookParticipants> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

XNode.AddBeforeSelf()
Pour insrer un nud un emplacement bien dni dans une liste de nuds enfants, vous devez obtenir la rfrence du nud devant lequel ou aprs lequel doit se faire linsertion, puis appeler la mthode AddBeforeSelf ou AddAfterSelf. Nous utiliserons larbre XML du Listing 7.63 comme point de dpart, et nous ajouterons un nouveau nud entre les deux lments BookParticipant existants. Pour ce faire, il est ncessaire dobtenir la rfrence du deuxime lment BookParticipant, comme illustr dans le Listing 7.65.
Listing 7.65 : Ajout dun nud lemplacement spcifi avec AddBeforeSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); xDocument.Element("BookParticipants").Add( new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")));

Chapitre 7

LAPI LINQ to XML

241

xDocument.Element("BookParticipants"). Elements("BookParticipant"). Where(e => ((string)e.Element("FirstName")) == "Ewan"). Single<XElement>().AddBeforeSelf( new XElement("BookParticipant", new XAttribute("type", "Technical Reviewer"), new XElement("FirstName", "Fabio"), new XElement("LastName", "Ferracchiati"))); Console.WriteLine(xDocument);

Nous allons dnir la rfrence llment BookParticipant en utilisant un oprateur LINQ. Cela nous permettra de faire un rappel sur les oprateurs de requte standard introduits au Chapitre 2 et de les utiliser. Dans la premire ligne en gras, la mthode Element, applique llment BookParticipants, permet daccder aux lments qui la constituent. Les trois lignes suivantes slectionnent llment BookParticipant dont llment enfant FirstName vaut "Ewan". Un seul lment satisfaisant ce critre et tant donn que le nouvel lment doit tre insr avant llment courant, nous utilisons la mthode AddBeforeSelf. Loprateur Single retourne lobjet XElement BookParticipant. Cest la rfrence utilise pour insrer le nouveau XElement. Dans loprateur Where, llment FirstName est converti en une chane. La fonctionnalit dextraction de valeur de LINQ sera ainsi mise contribution pour comparer la valeur de llment la chane "Ewan". Une fois la rfrence llment BookParticipant obtenue, il ne reste plus qu appeler la mthode AddBeforeSelf pour effectuer linsertion. Voici les rsultats :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Technical Reviewer"> <FirstName>Fabio</FirstName> <LastName>Ferracchiati</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Le nouvel lment BookParticipant a bien t insr avant llment BookParticipant dont llment FirstName vaut "Ewan".

XNode.AddAfterSelf()
Dans lexemple prcdent, nous utilisions toute une gymnastique pour accder au second lment BookParticipant. Ici, nous nous contenterons dobtenir une rfrence

242

LINQ to XML

Partie III

au premier lment BookParticipant en utilisant la mthode Element et de la faire suivre dun nouvel lment BookParticipant en utilisant la mthode AddAfterSelf.
Listing 7.66 : Ajout dun nud lemplacement spcifi avec AddBeforeSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); xDocument.Element("BookParticipants").Add( new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))); xDocument.Element("BookParticipants"). Element("BookParticipant").AddAfterSelf( new XElement("BookParticipant", new XAttribute("type", "Technical Reviewer"), new XElement("FirstName", "Fabio"), new XElement("LastName", "Ferracchiati"))); Console.WriteLine(xDocument);

Voici le rsultat :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Technical Reviewer"> <FirstName>Fabio</FirstName> <LastName>Ferracchiati</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Suppression de nuds Deux mthodes permettent de supprimer des nuds : Remove et RemoveAll. En complment des mthodes passes en revue dans cette section, vous pouvez galement vous reporter la section "XElement.SetElementValue() sur des objets enfants de XElement", un peu plus loin dans ce chapitre.

XNode.Remove()
Cette mthode permet de supprimer un nud quelconque dans un arbre XML, ainsi que ses ventuels nuds enfants et attributs. Dans ce premier exemple, nous allons construire un arbre XML et mmoriser la rfrence au premier lment BookParticipant, en

Chapitre 7

LAPI LINQ to XML

243

utilisant la mme technique que dans la section prcdente. Larbre XML sera afch aprs la construction et avant toute suppression. Le premier lment BookParticipant sera alors supprim et larbre XML, nouveau afch (voir Listing 7.67).
Listing 7.67 : Suppression dun nud avec la mthode Remove.
// Lobjet firstParticipant sera utilis pour mmoriser un lment dans larbre XML XElement firstParticipant; Console.WriteLine(System.Environment.NewLine + "Avant la suppression du nud"); XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(xDocument); firstParticipant.Remove(); Console.WriteLine(System.Environment.NewLine + " Aprs la suppression du nud"); Console.WriteLine(xDocument);

Voici le rsultat :
Avant la suppression du nud <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Aprs la suppression du nud <BookParticipants> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Le premier lment BookParticipant a bien t supprim.

IEnumerable<T>.Remove() o T est un XNode


Dans lexemple prcdent, la mthode Remove a t applique un seul nud. Si ncessaire, il est galement possible de lappliquer une squence ( IEnumerable<T>). Dans le Listing 7.68, la mthode Descendants est utilise pour parcourir larbre XML.

244

LINQ to XML

Partie III

Elle est combine un oprateur Where, et seuls sont retourns les lments dont le nom est FirstName. La mthode Remove est enn appele sur cette squence.
Listing 7.68 : Suppression dune squence de nuds avec la mthode Remove.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); xDocument.Descendants().Where(e => e.Name == "FirstName").Remove(); Console.WriteLine(xDocument);

La mthode XDocument.Descendants retourne les nuds enfants dune squence. Loprateur de requte standard Where est alors appel pour ltrer les nuds qui correspondent au critre de slection (ici, le nom du nud doit tre FirstName). La squence retourne est alors passe la mthode Remove pour supprimer les nuds correspondants. Voici le rsultat :
<BookParticipants> <BookParticipant type="Author"> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Comme vous pouvez le voir, tous les nuds FirstName ont t supprims.

XElement.RemoveAll()
Il est parfois ncessaire de supprimer le contenu dun lment, mais pas llment luimme. Vous utiliserez pour cela la mthode RemoveAll (voir Listing 7.69).
Listing 7.69 : Suppression du contenu dun nud avec RemoveAll.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine(System.Environment.NewLine + "Avant la suppression du contenu");

Chapitre 7

LAPI LINQ to XML

245

Console.WriteLine(xDocument); xDocument.Element("BookParticipants").RemoveAll(); Console.WriteLine(System.Environment.NewLine + "Aprs la suppression du contenu"); Console.WriteLine(xDocument);

Le document est afch avant la suppression du contenu du nud BookParticipants. Le contenu de ce nud est alors supprim puis le document est nouveau afch. Voici les rsultats :
Avant la suppression du contenu <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Aprs la suppression du contenu <BookParticipants />

Mise jour de nuds Plusieurs des classes de XNode, comme XElement, XText et XComment, ont une proprit Value qui peut tre directement modie. Dautres, telles que XDocumentType et XProcessingInstruction, ont des proprits spciques qui peuvent tre modies. Les mthodes XElement.SetElementValue et XContainer.ReplaceAll (voir un peu plus loin dans ce chapitre) peuvent galement tre appeles pour modier la valeur des lments.

XElement.Value, XText.Value et XComment.Value


Pour modier la valeur dun nud XElement, XText et XComment, il suft de modier la proprit Value des sous-classes de XNode correspondantes (voir Listing 7.70).
Listing 7.70 : Mise jour de la valeur dun nud.
// Dfinition dune rfrence sur un lment de larbre XML XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XComment("Nouvel auteur"), new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine("Avant la modification des nuds"); Console.WriteLine(xDocument); // Modification dun lment, dun commentaire et dun nud texte firstParticipant.Element("FirstName").Value = "Joey";

246

LINQ to XML

Partie III

firstParticipant.Nodes().OfType<XComment>().Single().Value = "Auteur du livre Pro LINQ en C# 2008"; ((XElement)firstParticipant.Element("FirstName").NextNode) .Nodes().OfType<XText>().Single().Value = "Rattz, Jr."; Console.WriteLine("Aprs la modification des nuds"); Console.WriteLine(xDocument);

Llment FirstName puis le commentaire sont modis en utilisant la proprit Value correspondante. Llment LastName est ensuite modi par lintermdiaire de la proprit Value de son enfant XText. Cet exemple montre quel point LINQ to XML est exible lorsquil sagit daccder aux objets modier. Bien entendu, il nest pas ncessaire de passer par lenfant XText de llment LastName pour modier sa valeur. Le chemin de traverse emprunt par ce code na quun but dmonstratif. Voici le rsultat :
Avant la mise jour des nuds <BookParticipants> <BookParticipant type="Author"> <!Nouvel auteur--> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Aprs la mise jour des nuds <BookParticipants> <BookParticipant type="Author"> <!--Auteur de Pro LINQ en C# 2008--> <FirstName>Joey</FirstName> <LastName>Rattz, Jr.</LastName> </BookParticipant> </BookParticipants>

Les valeurs des nuds ont bien t mises jour.


Les proprits XDocumentType.Name, XDocumentType.PublicId, XDocumentType.SystemId et XDocumentType.InternalSubset Pour modier les valeurs relatives la dnition de type de document (DTD), vous utiliserez quatre proprits de la classe XDocumentType (voir Listing 7.71).
Listing 7.71 : Modification de la dfinition de type de document.
// Dfinition dune rfrence sur le type de document pour un usage futur XDocumentType docType; XDocument xDocument = new XDocument( docType = new XDocumentType("BookParticipants", null, "BookParticipants.dtd", null), new XElement("BookParticipants")); Console.WriteLine("Avant la mise jour du DTD"); Console.WriteLine(xDocument); docType.Name = "MyBookParticipants"; docType.SystemId = "http://www.somewhere.com/DTDs/MyBookParticipants.DTD"; docType.PublicId = "-//DTDs//TEXT Book Participants//EN";

Chapitre 7

LAPI LINQ to XML

247

Console.WriteLine("Aprs la mise jour du DTD"); Console.WriteLine(xDocument);

Voici les rsultats :


Avant la mise jour du DTD <!DOCTYPE BookParticipants SYSTEM "BookParticipants.dtd"> <BookParticipants /> Aprs la mise jour du DTD <!DOCTYPE MyBookParticipants PUBLIC "-//DTDs//TEXT Book Participants//EN" "http://www.somewhere.com/DTDs/MyBookParticipants.DTD"> <BookParticipants />

XProcessingInstruction.Target sur les objets XProcessingInstruction Objects et XProcessingInstruction.Data sur les objets XProcessingInstruction
Pour modier la valeur dune instruction de traitement, il suft de modier les proprits Target et Data de lobjet XProcessingInstruction (voir Listing 7.72).
Listing 7.72 : Mise jour dune instruction de traitement.
// Dfinition dune rfrence pour un usage futur XProcessingInstruction procInst; XDocument xDocument = new XDocument( new XElement("BookParticipants"), procInst = new XProcessingInstruction("BookCataloger", "out-of-print")); Console.WriteLine("Avant la modification de linstruction de traitement"); Console.WriteLine(xDocument); procInst.Target = "BookParticipantContactManager"; procInst.Data = "update"; Console.WriteLine("Aprs la modification de linstruction de traitement"); Console.WriteLine(xDocument);

Voici le rsultat de ce code :


Avant la modification de linstruction de traitement <BookParticipants /> <?BookCataloger out-of-print?> Aprs la modification de linstruction de traitement <BookParticipants /> <?BookParticipantContactManager update?>

XElement.ReplaceAll()
La mthode ReplaceAll permet de remplacer larbre XML relatif un lment. Il est possible de passer une simple valeur une chane ou un nombre, par exemple ou, si une mthode surcharge accepte des objets multiples via le mot-cl params, une portion darbre. La mthode ReplaceAll remplace galement les attributs. Le Listing 7.73 donne un exemple dutilisation de cette mthode.

248

LINQ to XML

Partie III

Listing 7.73 : Utilisation de la mthode ReplaceAll pour modifier larbre relatif un lment.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la modification"); Console.WriteLine(xDocument); firstParticipant.ReplaceAll( new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")); Console.WriteLine(System.Environment.NewLine + "Aprs la modification"); Console.WriteLine(xDocument);

Les instructions en gras modient larbre de llment firstParticipant. Comme vous pouvez le voir, lattribut type na pas t spci. Voici le rsultat :
Avant la modification <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Aprs la modification <BookParticipants> <BookParticipant> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Bien que les attributs ne soient pas des nuds enfants des lments, la mthode ReplaceAll a t en mesure de supprimer lattribut type de larbre XML.

XElement.SetElementValue() sur des objets enfants de XElement


Cette mthode est trs puissante. Elle permet dajouter, de modier et de supprimer les lments enfants de llment sur lequel elle est appele. Cette mthode admet deux paramtres : le nom de llment enfant atteindre et la valeur qui doit lui tre affecte. Si un enfant portant ce nom est trouv, et si la valeur passe est diffrente de null, lenfant est mis jour. Si la valeur passe vaut null, lenfant est supprim. Si aucun enfant portant ce nom nest trouv, il est cr et la valeur spcie lui est affecte.

Chapitre 7

LAPI LINQ to XML

249

La mthode SetElementValue naffecte que le premier lment enfant portant le nom spci. Si un ou plusieurs autres lments enfants portent le mme nom, ils ne sont pas affects. Le Listing 7.74 donne un exemple des trois possibilits de cette mthode.
Listing 7.74 : Utilisation de SetElementValue pour mettre jour, ajouter et supprimer des lments enfants.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la mise jour des lments"); Console.WriteLine(xDocument); // Mise jour de la valeur dun lment // Llment enfant FirstName tant trouv, sa valeur sera initialise Joseph firstParticipant.SetElementValue("FirstName", "Joseph"); // Ajout dun lment // Llment enfant MiddleInitial ntant pas trouv, il est cr firstParticipant.SetElementValue("MiddleInitial", "C"); // Suppression dun lment // La valeur de llment tant initialise null, llment est supprim firstParticipant.SetElementValue("LastName", null); Console.WriteLine(System.Environment.NewLine + "Aprs la mise jour des lments"); Console.WriteLine(xDocument);

Dans un premier temps, la mthode SetElementValue est appele sur llment enfant FirstName de llment firstParticipant. Comme un lment portant ce nom existe, sa valeur est mise jour. Dans un deuxime temps, la mthode SetElementValue est appele sur llment enfant MiddleInitial de llment firstParticipant. Comme aucun lment portant ce nom nexiste, il est cr. Enn, dans un troisime temps, la mthode SetElementValue est appele sur llment enfant LastName de llment firstParticipant. La valeur null tant passe dans le deuxime argument de la mthode, llment LastName est supprim. Voici les rsultats :
Avant la mise jour des lments <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Aprs la mise jour des lments

250

LINQ to XML

Partie III

<BookParticipants> <BookParticipant type="Author"> <FirstName>Joseph</FirstName> <MiddleInitial>C</MiddleInitial> </BookParticipant> </BookParticipants>

Llment FirstName a t mis jour, llment MiddleInitial a t cr et llment LastName, supprim.


ATTENTION Lorsque la mthode SetElementValue est appele avec un deuxime argument ayant pour valeur null, elle supprime llment spci dans le premier argument. Que ceci ne vous fasse pas croire quil sufse dinitialiser un lment avec la valeur null pour le supprimer dun arbre XML. Si vous tentez de le faire en agissant sur sa proprit Value, une exception sera leve.

Attributs XML
Lorsque lon utilise lAPI LINQ to XML, les attributs sont implments dans la classe XAttribute. Contrairement ce qui avait cours dans lAPI W3C XML DOM, ils nhritent pas dun nud. Ils nont donc aucune relation dhritage avec les lments. Et, pourtant, grce lAPI LINQ to XML, ils sont tout aussi simples utiliser. Cration dun attribut Les attributs sont crs de la mme manire que les lments et que la plupart des autres classes LINQ to XML. Reportez-vous la section "Cration dattributs avec XAttribute", au dbut de ce chapitre, pour en savoir plus ce sujet. Dplacements dans un attribut Pour vous dplacer dans les attributs, vous utiliserez les proprits XElement.FirstAttribute, XElement.LastAttribute, XAttribute.NextAttribute et XAttribute .PreviousAttribute et les mthodes XElement.Attribute et XElement.Attributes. Vous en saurez plus leur sujet dans les prochaines pages.
Premier attribut avec XElement.FirstAttribute Pour accder au premier attribut dun lment, vous pouvez utiliser la proprit FirstAttribute (voir Listing 7.75).
Listing 7.75 : Accs au premier attribut dun lment avec la proprit FirstAttribute.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant;

Chapitre 7

LAPI LINQ to XML

251

XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(firstParticipant.FirstAttribute);

Ce code produit le rsultat suivant dans la console :


type="Author"

Attribut suivant avec XAttribute.NextAttribute Pour accder lattribut suivant, il suft dutiliser la proprit NextAttribute (voir Listing 7.76).
Listing 7.76 : Accs lattribut suivant avec la proprit NextAttribute.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(firstParticipant.FirstAttribute.NextAttribute);

Avant dutiliser la proprit NextAttribute, la proprit FirstAttribute a t applique llment firstParticipant pour obtenir une rfrence au premier attribut de llment. Voici le rsultat :
experience="first-time"

Si la proprit NextAttribute dun attribut a pour valeur null, cela signie quil sagit du dernier attribut de llment.
Attribut prcdent avec XAttribute.PreviousAttribute Pour accder lattribut prcdent, il suft dutiliser la proprit PreviousAttribute (voir Listing 7.77).
Listing 7.77 : Accs lattribut prcdent avec la proprit PreviousAttribute.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant;

252

LINQ to XML

Partie III

XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(firstParticipant.FirstAttribute.NextAttribute.PreviousAttribute);

Les proprits FirstAttribute et NextAttribute ont t chanes pour obtenir une rfrence au deuxime attribut de llment firstParticipant. En appliquant la proprit PreviousAttribute, lattribut point est donc le premier. Voici le rsultat :
type="Author"

Si la proprit PreviousAttribute dun attribut vaut null, cela signie quil a t appliqu au premier attribut de llment.
Dernier attribut avec XElement.LastAttribute Pour accder au dernier attribut dun lment, vous utiliserez la proprit LastAttribute (voir Listing 7.78).
Listing 7.78 : Accs au dernier attribut avec la proprit LastAttribute.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(firstParticipant.LastAttribute);

Linstruction Writeln afche le dernier attribut du XElement firstParticipant :


language="English"

XElement.Attribute() Sil existe, cette mthode retourne le premier attribut dont le nom est pass en argument (voir Listing 7.79).
Listing 7.79 : Accs un attribut avec la mthode Attribute.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant;

Chapitre 7

LAPI LINQ to XML

253

XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(firstParticipant.Attribute("type").Value);

La mthode Attribute donne accs lattribut type. La valeur de cet attribut est alors afche en utilisant la proprit Value. Voici le rsultat :
Author

titre dinformation, sachez que la valeur de lattribut aurait galement pu tre obtenue en appliquant un casting de type string lattribut.

XElement.Attributes()
La mthode Attributes() retourne tous les attributs de llment sur lequel elle est applique. Les attributs sont retourns sous la forme dune squence dobjets XAttribute (voir Listing 7.80).
Listing 7.80 : Accs tous les attributs dun lment avec la mthode Attributes.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); foreach(XAttribute attr in firstParticipant.Attributes()) { Console.WriteLine(attr); }

Voici le rsultat :
type="Author" experience="first-time"

Modication dattributs Comme il a t dit prcdemment, les API W3C XML DOM et LINQ to XML manipulent les attributs dune faon bien diffrente. Avec lAPI W3C, les attributs sont des nuds enfants du nud dont ils sont lattribut. Avec lAPI LINQ to XML, les attributs

254

LINQ to XML

Partie III

sont des paires nom/valeur. Ils sont accessibles via la mthode Attributes ou la proprit FirstAttribute de llment. Il est important davoir cela en mmoire. Les mthodes et proprits des attributs sont trs proches de celles qui ont dj t tudies pour les lments. Vous pouvez utiliser les mthodes suivantes pour ajouter un attribut un lment :
m m m m

XElement.Add() ; XElement.AddFirst() ; XElement.AddBeforeThis() ; XElement.AddAfterThis().

Ces mthodes ont dj t illustres dans la section "Ajout de nuds", un peu plus tt dans ce chapitre. Reportez-vous aux exemples de cette section pour voir comment ajouter des attributs. Consultez galement la section relative la mthode XElement.SetAttributeValue, un peu plus loin dans ce chapitre.
Suppression dattributs Pour supprimer un attribut, vous utiliserez la mthode XAttribute.Remove. Pour supprimer une squence dattributs, vous utiliserez la mthode IEnumerable<T>.Remove.

Vous consulterez galement la section XElement.SetAttributeValue(), un peu plus loin dans ce chapitre.
XAttribute.Remove() Vous vous rappelez certainement que la mthode Remove de la classe XNode permettait de supprimer un nud. Quant elle, la mthode Remove de la classe XAttribute permet de supprimer un attribut (voir Listing 7.81).
Listing 7.81 : Suppression dun attribut.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la suppression de lattribut"); Console.WriteLine(xDocument); firstParticipant.Attribute("type").Remove(); Console.WriteLine(System.Environment.NewLine + "Aprs la suppression de lattribut"); Console.WriteLine(xDocument);

Chapitre 7

LAPI LINQ to XML

255

Dans cet exemple, nous utilisons la mthode Attribute pour obtenir la rfrence de lattribut supprimer. La mthode Remove est alors applique cette rfrence. Voici le rsultat :
Avant la suppression de lattribut <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Aprs la suppression de lattribut <BookParticipants> <BookParticipant> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Lattribut type a bien t supprim.


IEnumerable<T>.Remove() o T est un XNode Tout comme la mthode IEnumerable<T>.Remove() de la classe XNode permet de supprimer une squence de nuds, la mthode IEnumerable<T>.Remove() de la classe XAttribute permet de supprimer une squence dattributs (voir Listing 7.82).
Listing 7.82 : Suppression de tous les attributs dun lment.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la suppression des attributs"); Console.WriteLine(xDocument); firstParticipant.Attributes().Remove(); Console.WriteLine(System.Environment.NewLine + "Aprs la suppression des attributs"); Console.WriteLine(xDocument);

La mthode Attributes() retourne la squence des attributs du XElement firstParticipant. La mthode Remove supprime cette squence. Voici les rsultats :
Avant la suppression des attributs <BookParticipants> <BookParticipant type="Author" experience="first-time"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant>

256

LINQ to XML

Partie III

</BookParticipants> Aprs la suppression des attributs <BookParticipants> <BookParticipant> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Modication de la valeur des attributs Pour modier la valeur dun attribut, vous utiliserez la proprit XAttribute.Value (voir Listing 7.83).
INFO Reportez-vous galement la section XElement.SetAttributeValue(), un peu plus loin dans ce chapitre. Listing 7.83 : Suppression de tous les attributs dun lment.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la modification de la valeur de lattribut"); Console.WriteLine(xDocument); firstParticipant.Attribute("experience").Value = "beginner";

de lattribut");

Console.WriteLine(System.Environment.NewLine + "Aprs la modification de la valeur

Console.WriteLine(xDocument);

La mthode Attribute a t utilise pour obtenir une rfrence lattribut experience. La mthode Value a alors t applique cette rfrence pour accder la valeur de lattribut. Voici le rsultat :
Avant la modification de la valeur de lattribut <BookParticipants> <BookParticipant type="Author" experience="first-time"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Chapitre 7

LAPI LINQ to XML

257

Aprs la modification de la valeur de lattribut <BookParticipants> <BookParticipant type="Author" experience="beginner"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Lattribut experience avait pour valeur "first-time" avant lexcution du code. Il a dsormais pour valeur "beginner".

XElement.SetAttributeValue()
La mthode SetAttributeValue est le pendant pour les attributs de la mthode SetElementValue. Tout aussi complte, elle permet dajouter, de supprimer et de modier la valeur dun attribut. Si un nom dattribut inexistant lui est pass, cet attribut est ajout llment. Si un nom dattribut existant ayant une valeur diffrente de null lui est pass, lattribut est mis jour avec la valeur passe. Enn, si un nom dattribut existant ayant la valeur null lui est pass, il est supprim (voir Listing 7.84).
Listing 7.84 : Suppression de tous les attributs dun lment.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")))); Console.WriteLine(System.Environment.NewLine + "Avant la modification des attributs"); Console.WriteLine(xDocument); // Lattribut "type" existe et le deuxime argument est diffrent de "null". // Lattribut "type" est donc mis jour. firstParticipant.SetAttributeValue("type", "beginner"); // Lattribut "language" nexiste pas. Il est donc ajout llment. firstParticipant.SetAttributeValue("language", "English"); // Lattribut "experience" existe et le deuxime argument a pour valeur "null" // Lattribut "experience" est donc supprim. firstParticipant.SetAttributeValue("experience", null); Console.WriteLine(System.Environment.NewLine + "Aprs la modification des attributs"); Console.WriteLine(xDocument);

258

LINQ to XML

Partie III

Ce code met jour la valeur dun attribut, dnit un nouvel attribut et supprime un attribut existant. Voici les rsultats :
Avant la modification des attributs <BookParticipants> <BookParticipant type="Author" experience="first-time"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants> Aprs la modification des attributs <BookParticipants> <BookParticipant type="beginner" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> </BookParticipants>

Annotations XML
En utilisant les annotations de lAPI LINQ to XML, il est possible dassocier une donne utilisateur une classe quelconque qui hrite de la classe XObject. Il est ainsi possible daffecter une donne quelconque (une cl supplmentaire, un objet qui parse les valeurs dun lment) un lment, un document ou un autre objet dont la classe est drive de XObject. Ajout dannotations avec XObject.AddAnnotation() Voici le prototype de la mthode AddAnnotation() :
void XObject.AddAnnotation(object annotation);

Accs aux annotations avec XObject.Annotation() ou

XObject.Annotations()
Voici les prototypes de ces deux mthodes :
object XObject.Annotation(Type type); T XObject.Annotation<T>(); IEnumerable<object> XObject.Annotations(Type type); IEnumerable<T> XObject.Annotations<T>();

ATTENTION Lorsque vous accdez des annotations, veillez passer le type actuel de lobjet, et non sa classe de base ou son interface. Sans quoi lannotation ne serait pas trouve.

Suppression dannotations avec XObject.RemoveAnnotations() Voici les deux prototypes de la mthode RemoveAnnotations() :
void XObject.RemoveAnnotations(Type type); void XObject.RemoveAnnotations<T>();

Chapitre 7

LAPI LINQ to XML

259

Exemples dannotations titre dexemple, nous allons dnir un code qui ajoute, retrouve et supprime des annotations. Ici, nous utiliserons larbre XML dsormais traditionnel BookParticipants. Nous allons associer un handler chaque lment BookParticipant, en se basant sur son attribut type. Dans cet exemple, le handler afchera llment dans un format qui dpend de lattribut type : un format pour les auteurs, un autre pour les diteurs. Voici les classes handler utilises (une pour les auteurs et une pour les diteurs) :
public class AuthorHandler { public void Display(XElement element) { Console.WriteLine("BIOGRAPHIE DE LAUTEUR"); Console.WriteLine("--------------------------"); Console.WriteLine("Nom: {0} {1}", (string)element.Element("FirstName"), (string)element.Element("LastName")); Console.WriteLine("Langue: {0}", (string)element.Attribute("language")); Console.WriteLine("Exprience: {0}", (string)element.Attribute("experience")); Console.WriteLine("==========================" + System.Environment.NewLine); } } public class EditorHandler { public void Display(XElement element) { Console.WriteLine("BIOGRAPHIE DE LEDITEUR"); Console.WriteLine("--------------------------"); Console.WriteLine("Nom: {0}", (string)element.Element("FirstName")); Console.WriteLine(" {0}", (string)element.Element("LastName")); Console.WriteLine("==========================" + System.Environment.NewLine); } }

Ce code dnit deux classes au comportement distinct. Dans cet exemple, les donnes de llment sont afches diffremment. Bien entendu, le traitement pourrait tre tout autre. Les annotations pourraient mme ne pas tre des handlers Cet exemple tant plus complexe que les prcdents, nous avons divis le code en plusieurs sections. Chacune dentre elles sera suivie dexplications (voir Listing 7.85).
Listing 7.85 : Ajout, lecture et suppression dannotations.
// Dfinition dune rfrence vers un des lments de larbre XML pour un usage futur XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("experience", "first-time"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")),

260

LINQ to XML

Partie III

new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); // Affichage du document Console.WriteLine(xDocument + System.Environment.NewLine);

Ces quelques lignes de code dnissent le document XML et lafchent. Le bloc de code suivant numre les participants. Pour chacun dentre eux, un handler est instanci en fonction de lattribut type et une annotation faisant rfrence ce handler est ajoute llment.
// Ajout dannotations en fonction de la valeur de lattribut type foreach(XElement e in xDocument.Element("BookParticipants").Elements()) { if((string)e.Attribute("type") == "Author") { AuthorHandler aHandler = new AuthorHandler(); e.AddAnnotation(aHandler); } else if((string)e.Attribute("type") == "Editor") { EditorHandler eHandler = new EditorHandler(); e.AddAnnotation(eHandler); } }

Aprs lexcution de ce code, chaque lment BookParticipant possde une annotation qui rfrence un handler dont le code dpend de la valeur de lattribut type. Nous allons maintenant numrer les lments BookParticipant, retrouver les annotations et excuter les handlers associs.
AuthorHandler aHandler2; EditorHandler eHandler2; foreach(XElement e in xDocument.Element("BookParticipants").Elements()) { if((string)e.Attribute("type") == "Author") { aHandler2 = e.GetAnnotation<AuthorHandler>(); if(aHandler2 != null) { aHandler2.Display(e); } } else if((string)e.Attribute("type") == "Editor") { eHandler2 = e.GetAnnotation<EditorHandler>(); if(eHandler2 != null) { eHandler2.Display(e); } } }

Ce code excute la mthode Display du handler associ chaque lment. Le bloc de code suivant va supprimer les annotations de chaque lment :
foreach(XElement e in xDocument.Element("BookParticipants").Elements()) {

Chapitre 7

LAPI LINQ to XML

261

if((string)e.Attribute("type") == "Author") { e.RemoveAnnotation<AuthorHandler>(); } else if((string)e.Attribute("type") == "Editor") { e.RemoveAnnotation<EditorHandler>(); } }

Ce code est plus long que les prcdents. Il est compos de quatre sections principales. La premire section cre larbre XML et lafche. Ceci na rien dexceptionnel, puisque nous lavons dj fait frquemment dans les autres exemples de cet ouvrage. La deuxime section numre les lments BookParticipant et ajoute un handler en fonction de la valeur de leur attribut type. La troisime section numre les lments BookParticipant. En fonction de la valeur de leur attribut type, la mthode Display du handler correspondant est excute. Enn, la quatrime section numre les lments BookParticipant et supprime les annotations. Dans les sections 2, 3 et 4 du code, laccs aux attributs sest fait via un casting au format string. Cest ainsi quil a t possible de les comparer aux valeurs "Author" et "Editor". Voici les rsultats :
<BookParticipants> <BookParticipant type="Author" experience="first-time" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> BIOGRAPHIE AUTEUR -------------------------Nom : Joe Rattz Langue : English Exprience: first-time ========================== BIOGRAPHIE EDITEUR -------------------------Nom : Ewan Buckingham ==========================

Ce quil faut remarquer dans ces rsultats, cest que les deux handlers sont appels en fonction de lattribut type et via les annotations. Retenez galement que les annotations peuvent tre constitues dobjets quelconques, et pas seulement de handlers.

262

LINQ to XML

Partie III

vnements XML
Grce lAPI LINQ to XML, vous pouvez demander tre inform tout moment de la modication des objets qui hritent de la classe XObject. Lorsque vous faites une telle demande auprs dun objet, un vnement sera lev si cet objet, ou un de ses descendants, est modi. Cela signie que si, par exemple, vous vous abonnez un vnement situ au niveau du document, toutes les modications effectues dans larbre provoqueront lappel de la mthode laquelle vous vous tes abonn. Cest la raison pour laquelle vous ne devez faire aucune supposition sur le type de lobjet qui provoquera les vnements. Lorsque la mthode de traitement est appele, lobjet qui en est lorigine est pass en tant qumetteur de lvnement. Son type est object. Faites attention lorsque vous lui appliquerez un oprateur de casting, lorsque vous accderez ses proprits ou appellerez ses mthodes. Il se peut que son type ne corresponde pas ce que vous attendez. Ceci sera illustr dans le Listing 7.86, o lobjet sera de type XText alors que lon attend un type XElement. Sachez enn que la construction dun arbre XML ne gnre aucun vnement. Comment cela serait-il possible, puisque aucun vnement ne peut tre enregistr avant la construction de larbre ! Seule la modication ou la suppression dun lment XML peut engendrer un vnement, et seulement condition que cet vnement ait t enregistr.

XObject.Changing
Cet vnement est lev lorsquun objet qui hrite de XObject est sur le point dtre modi. Pour vous abonner lvnement, vous devez ajouter un objet de type EventHandler lvnement Changing de lobjet :
myobject.Changing += new EventHandler<XObjectChangeEventArgs>(MyHandler);

Le dlgu doit avoir la signature suivante :


void MyHandler(object sender, XObjectChangeEventArgs cea)

Lobjet sender est celui qui est sur le point dtre modi et qui provoque la leve de lvnement. La proprit ObjectChange de type XObjectChange de lobjet cea (Change Event Arguments) indique le type de changement qui est sur le point de survenir : XObjectChange.Add, XObjectChange.Name, XObjectChange.Remove ou XObjectChange.Value.

XObject.Changed
Cet vnement est lev lorsquun objet qui hrite de XObject a t modi. Pour vous abonner lvnement, vous devez ajouter un objet de type EventHandler lvnement Changed de lobjet :
myobject.Changed += new EventHandler<XObjectChangeEventArgs>(MyHandler);

Chapitre 7

LAPI LINQ to XML

263

Le dlgu doit avoir la signature suivante :


void MyHandler(object sender, XObjectChangeEventArgs cea)

Lobjet sender est celui qui a t modi et qui provoque la leve de lvnement. La proprit ObjectChange de type XObjectChange de lobjet cea (Change Event Arguments) indique le type de changement qui est sur le point de survenir : XObjectChange.Add, XObjectChange.Name, XObjectChange.Remove ou XObjectChange.Value. Quelques exemples dvnements Un exemple va vous aider bien comprendre toute la logique mise en uvre pour grer les vnements XObject. Avant dentrer dans le vif du sujet, nous allons prsenter les gestionnaires dvnements utiliss. Cette mthode est excute lorsque lvnement Changing dun lment est lev. Elle permet dtre prvenu lorsquun lment est sur le point dtre modi.
public static void MyChangingEventHandler(object sender, XObjectChangeEventArgs cea) { Console.WriteLine("Type de lobjet qui va tre modifi : {0}, Type du changement : {1}", sender.GetType().Name, cea.ObjectChange); }

Voici le gestionnaire utilis pour gnrer un vnement juste aprs quun lment a t modi. Elle permet dtre prvenu lorsquun lment a t modi. Cette mthode est excute lorsque lvnement Changed dun lment est lev :
public static void MyChangedEventHandler(object sender, XObjectChangeEventArgs cea) { Console.WriteLine("Type de lobjet qui a t modifi : {0}, Type du changement : {1}", sender.GetType().Name, cea.ObjectChange); }

Un peu plus tt, jai indiqu quun vnement serait lev si un descendant dun objet auquel vous tes abonn est modi. Pour illustrer ce fait, nous allons dnir une autre mthode que nous enregistrerons une fois le document modi. Son unique but est de montrer que le document reoit galement un vnement Changed, mme sil sagit dun descendant situ plusieurs niveaux hirarchiques de celui qui a t modi. Cette mthode est excute lorsque lvnement Changed du document XML est lev :
public static void DocumentChangedHandler(object sender, XObjectChangeEventArgs cea) { Console.WriteLine("Doc: Type de lobjet qui a t modifi : {0}, Type du changement : {1}{2}", sender.GetType().Name, cea.ObjectChange, System.Environment.NewLine); }

La seule diffrence entre les mthodes DocumentChangedHandler et MyChangedEventHandler se situe dans le dbut de lafchage : lafchage effectu dans DocumentChangedHandler dbute par le terme "Doc:", an de signaler que le gestionnaire est appel par lvnement Changed du document, et non de llment. Examinons le code du Listing 7.86.

264

LINQ to XML

Partie III

Listing 7.86 : Le gestionnaire dvnements XObject.


XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("{0}{1}", xDocument, System.Environment.NewLine);

Rien de nouveau pour linstant. Comme il a t fait de nombreuses reprises dans les pages prcdentes, un document XML a t cr en utilisant la construction fonctionnelle, puis afch dans la console. Remarquez galement quune rfrence au premier lment BookParticipant a t mmorise. Les vnements seront dclenchs par rapport cet lment :
firstParticipant.Changing += new EventHandler<XObjectChangeEventArgs>(MyChangingEventHandler); firstParticipant.Changed += new EventHandler<XObjectChangeEventArgs>(MyChangedEventHandler); xDocument.Changed += new EventHandler<XObjectChangeEventArgs>(DocumentChangedHandler);

Aprs lexcution de ces lignes de code, un vnement sera gnr :


m m m

juste avant (Changing) le changement du premier lment BookParticipant ; juste aprs (Changed) le changement du premier lment BookParticipant ; juste aprs (Changed) la modication du document.

Le dernier type dvnement a t mis en place pour prouver que des vnements sont gnrs lorsquun objet decendant est modi. Il ne reste plus qu effectuer une modication dans llment firstParticipant.
firstParticipant.Element("FirstName").Value = "Seph"; Console.WriteLine("{0}{1}", xDocument, System.Environment.NewLine);

La premire ligne change la valeur de llment FirstName du premier lment BookParticipant. La deuxime ligne afche le document XML rsultant. Voici les rsultats :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Chapitre 7

LAPI LINQ to XML

265

Type de lobjet qui va tre modifi : XText, Type du changement : Suppression Type de lobjet qui a t chang : XText, Type du changement : Suppression Doc: Type de lobjet qui a t chang : XText, Type du changement : Suppression Type de lobjet qui va tre modifi : XText, Type du changement : Add Type de lobjet qui a t modifi : XText, Type du changement : Add Doc: Type de lobjet qui a t modifi : XText, Type du changement : Add <BookParticipants> <BookParticipant type="Author"> <FirstName>Seph</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Cette sortie console montre le document avant et aprs lutilisation du gestionnaire dvnements. Comme vous pouvez le constater, llment FirstName du premier BookParticipant a t modi. Les lignes situes entre les deux afchages de larbre XML correspondent aux messages afchs par les gestionnaires dvnements. Lobjet modi est de type XText. Pour ma part, je mattendais ce quil soit de type XElement. Il est facile doublier que, lorsque vous affectez une chane la valeur dun lment, un objet XText est automatiquement cr, de faon transparente. En regardant dun peu plus prs le texte afch par les gestionnaires dvnements, on comprend mieux ce quil se passe lorsquun lment est modi : dans le premier bloc de trois lignes, la valeur XText est sur le point dtre supprime, puis elle est supprime. Lvnement Changed du document est alors lev. Cela montre que les vnements se propagent du niveau le plus bas au niveau le plus haut. Dans le deuxime bloc de trois lignes, la mme suite dvnements est gnre mais, ici, un objet XText est ajout larbre XML. Vous savez maintenant que, lorsque vous modiez la valeur dun lment, un objet XText est supprim puis restaur. Dans cet exemple, nous avons utilis des mthodes nommes. Cette dmarche nest nullement obligatoire : il est galement possible dutiliser des mthodes anonymes ou des expressions lambda. Le Listing 7.87 est identique au prcdent mais, au lieu dutiliser les gestionnaires dvnements dj implments, nous dnissons des expressions lambda pour dnir la vole le code appel par les vnements.
Listing 7.87 : Gestion dun vnement XObject avec des expressions lambda.
XElement firstParticipant; XDocument xDocument = new XDocument( new XElement("BookParticipants", firstParticipant = new XElement("BookParticipant", new XAttribute("type", "Author"),

266

LINQ to XML

Partie III

new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("{0}{1}", xDocument, System.Environment.NewLine); firstParticipant.Changing += new EventHandler<XObjectChangeEventArgs>( (object sender, XObjectChangeEventArgs cea) => Console.WriteLine("Type de lobjet qui va tre modifi : {0}, Type of change: {1}", sender.GetType().Name, cea.ObjectChange)); firstParticipant.Changed += (object sender, XObjectChangeEventArgs cea) => Console.WriteLine("Type de lobjet qui a t modifi : {0}, Type du changement : {1}", sender.GetType().Name, cea.ObjectChange); xDocument.Changed += (object sender, XObjectChangeEventArgs cea) => Console.WriteLine("Doc: Type de lobjet qui a t modifi : {0}, Type du changement : {1}{2}", sender.GetType().Name, cea.ObjectChange, System.Environment.NewLine); xDocument.Changed += new XObjectChangeEventHandler((sender, cea) => Console.WriteLine("Doc: Type de lobjet qui a t modifi : {0}, Type du changement : {1}{2}", sender.GetType().Name, cea.ObjectChange, System.Environment.NewLine)); firstParticipant.Element("FirstName").Value = "Seph"; Console.WriteLine("{0}{1}", xDocument, System.Environment.NewLine);

Ce code se suft lui-mme. Il ne dpend daucun des gestionnaires dvnements prcdemment crits. Voici les rsultats :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Type of object changing: XText, Type of change: Remove Type of object changed: XText, Type of change: Remove Doc: Type of object changed: XText, Type of change: Remove Type de lobjet qui va tre modifi : XText, Type du changement : Add Type de lobjet qui a t modifi : XText, Type du changement : Add Doc: Type de lobjet qui a t modifi : XText, Type du changement : Add <BookParticipants> <BookParticipant type="Author"> <FirstName>Seph</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor">

Chapitre 7

LAPI LINQ to XML

267

<FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Les rsultats sont identiques ceux du listing prcdent. Avouez que les expressions lambda sont vraiment pratiques et efcaces. Les dveloppeurs qui donnent leurs premires impressions sur LINQ disent souvent quils napprcient pas les expressions lambda. Peut-tre est-ce parce quelles sont nouvelles et trs diffrentes. Mais avouez que cet exemple a de quoi les rconcilier avec ce nouvel outil. Le bogue dHalloween Vous rappelez-vous du "bogue dHalloween", introduit au dbut de ce chapitre ? De grce, rsistez lenvie qui vous poussera certainement intervenir sur la portion darbre XML dans laquelle vous capturez des vnements. Le contenu de larbre XML et les vnements gnrs pourraient en effet prendre une tournure incontrlable.

Rsum
Dans ce chapitre, nous avons vu comment utiliser LINQ to XML pour crer, modier et parcourir des documents XML, ainsi que pour interroger des objets XML laide de requtes. Vous avez pu voir que la nouvelle API apporte une grande exibilit : elle permet de crer un lment XML la vole, de linitialiser et de le placer dans un arbre XML en une seule instruction. LAPI W3C DOM XML en est totalement incapable. Cest la raison pour laquelle lAPI LINQ to XML a t conue. Ce chapitre vous a montr comment appliquer une requte LINQ sur un objet XML unique. Les requtes portaient par exemple sur les descendants ou les anctres dun lment. travers de nouveaux oprateurs XML, le chapitre suivant va vous montrer comment appliquer une requte LINQ sur une squence dlments (les descendants dune squence, par exemple).

8
Les oprateurs LINQ to XML
Les requtes prises en exemple au chapitre prcdent se contentaient de retourner tous les lments enfants ou tous les anctres dun nud. Vous rappelez-vous des exemples qui faisaient appel la mthode XContainer.Elements ? Dans lafrmative, vous savez ce quest une requte XML. Cest l une autre preuve de lintgration parfaite des requtes LINQ dans le langage : il est parfois facile doublier que lon est en train deffectuer une requte. Comme beaucoup des mthodes examines jusquici retournent une squence dobjets XML, cest--dire des IEnumerable<T> (o T est une classe de lAPI LINQ to XML), il est possible dappeler les oprateurs de requte standard sur la squence retourne, ce qui procure encore plus de puissance et de exibilit. Il est donc possible dobtenir une squence dobjets XML partir dun objet XML unique (les descendants ou les anctres dun objet, par exemple) mais, ce qui manque, ce sont des oprateurs qui pourraient sappliquer sur chacun des lments de ces squences. titre dexemple, il nexiste aucune faon simple dobtenir une squence dlments et deffectuer une autre opration XML spcique sur chacun des lments de la squence retourne, comme connatre les lments enfants de chacun des lments de la squence. Pour dire les choses autrement, vous pouvez obtenir une squence des lments enfants dun lment en appelant la mthode Elements de cet lment, mais vous ne pouvez pas obtenir une squence des lments enfants des lments enfants dun lment. Ceci parce que la mthode Elements doit tre appele sur un XContainer (XElement ou XDocument, par exemple), mais pas sur une squence dobjets XContainer. Cest ce point prcis que les oprateurs LINQ to XML vont vous venir en aide.

270

LINQ to XML

Partie III

Introduction aux oprateurs LINQ to XML


LAPI LINQ to XML tend les oprateurs de requte standard de LINQ to Objects en y ajoutant des oprateurs spciques au XML. Ces oprateurs sont des mthodes dextension dnies dans la classe System.Xml.Linq.Extensions, qui joue le rle dune classe conteneur. Chacun de ces oprateurs est appel sur une squence dun type de donne LINQ to XML et effectue une action sur chacune des entres de cette squence. Il retourne par exemple les anctres ou les descendants des diffrentes entres. Virtuellement, chacun des oprateurs XML dcrits dans ce chapitre a un quivalent dans le chapitre prcdent. Cependant, les mthodes du chapitre prcdent ne sappliquent qu un objet unique, alors que les oprateurs de ce chapitre sappliquent une squence dobjets. titre dexemple, au chapitre prcdent, nous avons parl de la mthode XContainer.Elements, dont voici le prototype :
IEnumerable<XElement> XContainer.Elements()

Dans ce chapitre, nous aborderons loprateur Extensions.Elements, dont voici le prototype :


IEnumerable<XElement> Elements<T> (this IEnumerable<T> source) where T: XContainer

Il existe une diffrence de taille entre ces deux mthodes : le premier prototype est appel sur un objet unique driv de XContainer, alors que le second est appel sur une squence dobjets dont chacun est driv de XContainer. Pour bien diffrencier les mthodes du chapitre prcdent des mthodes dextensions de ce chapitre, nous qualierons les secondes du terme "oprateurs". Et, maintenant, il est temps dentrer dans le vif du sujet.

Oprateur Ancestors
Loprateur Ancestors est appel sur une squence de nuds. Il retourne une squence qui contient les lments anctres de chacun des nuds sources. Prototypes Loprateur Ancestors a deux prototypes. Premier prototype
public static IEnumerable<XElement> Ancestors<T> ( this IEnumerable<T> source ) where T : XNode

Cette version de loprateur peut tre appele sur une squence de nuds ou dobjets drivs de XNode. Elle retourne une squence dlments contenant les anctres de chacun des nuds de la squence source.

Chapitre 8

Les oprateurs LINQ to XML

271

Second prototype
public static IEnumerable<XElement> Ancestors<T> ( this IEnumerable<T> source, XName name ) where T : XNode

Ce prototype est identique au prcdent mais, ici, un nom est pass dans les arguments. Seuls les anctres qui correspondent ce nom sont retourns dans la squence de sortie. Exemples Le Listing 8.1 donne un exemple dappel du premier prototype.
Listing 8.1 : Un exemple dappel du premier prototype de loprateur Ancestors.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Descendants("FirstName"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Element source : {0} : Valeur = {1}", element.Name, element.Value); } // Affichage des lments anctres des lments sources foreach (XElement element in elements.Ancestors()) { Console.WriteLine("Elment anctre : {0}", element.Name); }

Les premires lignes de ce code dnissent un document XML. Une squence dlments FirstName est alors gnre (rappelez-vous, la mthode Ancestors est appele sur une squence de nuds, et non sur un nud unique. Il est donc ncessaire de crer une squence). Pour faciliter lidentication des nuds, nous allons afcher leurs noms. tant donn que les lments ont un nom, mais pas les nuds, nous avons choisi de dnir une squence dlments, et non de nuds. Le dernier bloc de code numre les lments retourns par la mthode Ancestors et les afche. Voici les rsultats :
lment lment lment lment lment lment source : FirstName : valeur = Joe source : FirstName : valeur = Ewan anctre : BookParticipant anctre : BookParticipants anctre : BookParticipant anctre : BookParticipants

272

LINQ to XML

Partie III

Comme vous pouvez le voir, ces rsultats afchent les deux lments de la squence source, puis les anctres de ces lments. Loprateur Ancestors retourne tous les lments anctres de chaque nud sous la forme dune squence de nuds. Dans cet exemple, la squence utilise est compose dlments, mais cela ne pose pas de problme, puisque les lments sont drivs de XNode. Assurez-vous que vous faites bien la diffrence entre loprateur Ancestors, appel sur une squence de nuds, et la mthode Ancestors, tudie au chapitre prcdent. Cet exemple nest pas aussi impressionnant quil peut le paratre. Le code a en effet t tendu des ns dmonstratives. Nous avons ainsi utilis quelques lignes de code pour numrer les lments de la squence FirstName (appel la mthode Descendants et bloc foreach suivant). La seconde boucle foreach appelle loprateur Ancestors et afche les anctres. Dans cette deuxime boucle, il aurait t possible dappeler la mthode Ancestors du chapitre prcdent sur chacun des lments de la squence dlments FirstName. Cette technique est illustre dans le Listing 8.2.
Listing 8.2 : Mme rsultat que le listing prcdent, mais sans appeler loprateur Ancestors.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Descendants("FirstName"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source: {0} : valeur = {1}", element.Name, element.Value); } foreach (XElement element in elements) { // Appel de la mthode Ancestors sur chaque lment foreach(XElement e in element.Ancestors()) // Affichage des anctres de chaque lment source Console.WriteLine("Elment anctre : {0}", e.Name); }

Cet exemple est diffrent du prcdent : ici, au lieu dappeler loprateur Ancestors sur les lments de la squence dans la boucle foreach, la boucle applique la mthode

Chapitre 8

Les oprateurs LINQ to XML

273

Ancestors du chapitre prcdent chacun des lments de la squence. Le rsultat est le mme que celui du listing prcdent :
Elment Elment Elment Elment Elment Elment source : FirstName : valeur = Joe source : FirstName : valeur = Ewan anctre : BookParticipant anctre : BookParticipants anctre : BookParticipant anctre : BookParticipants

Grce loprateur Ancestors et la concision de LINQ, cette requte peut tre rsume une dclaration bien plus rduite (voir Listing 8.3).
Listing 8.3 : Un exemple concis dappel de loprateur Ancestors.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); foreach (XElement element in xDocument.Element("BookParticipants").Descendants("FirstName").Ancestors()) { Console.WriteLine("Elment anctre : {0}", element.Name); }

Dans cet exemple, loprateur Ancestors est directement appel sur la squence dlments retourns par la mthode Descendants. Cette dernire retourne une squence dlments, et loprateur Ancestors retourne une autre squence dlments qui contient tous les anctres de chacun des lments de la premire squence. Contrairement aux deux listings prcdents, les lments FirstName ne sont pas afchs. Mais, bien videmment, les anctres sont les mmes :
Elment Elment Elment Elment anctre anctre anctre anctre : : : : BookParticipant BookParticipants BookParticipant BookParticipants

En production, vous opterez certainement pour un code concis, semblable celui prsent dans le Listing 8.3. Cependant, dans la suite de ce chapitre, nous utiliserons un code plus verbeux, comparable celui du Listing 8.1. Pour illustrer le second prototype de loprateur Ancestors, nous utiliserons le mme code que dans le Listing 8.1, mais nous changerons lappel loprateur Ancestors, de sorte quil limite la sortie aux anctres ayant pour valeur BookParticipant (voir Listing 8.4).

274

LINQ to XML

Partie III

Listing 8.4 : Appel du second prototype de loprateur Ancestors.


XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Descendants("FirstName"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des anctres de chaque lment source foreach (XElement element in elements.Ancestors("BookParticipant")) { Console.WriteLine("Elment anctre : {0}", element.Name); }

Les rsultats sont semblables ceux du Listing 8.1 mais, cette fois-ci, les anctres BookParticipants ne sont pas afchs :
Elment Elment Elment Elment source : FirstName : valeur = Joe source : FirstName : valeur = Ewan anctre : BookParticipant anctre : BookParticipant

Oprateur AncestorsAndSelf
Loprateur AncestorsAndSelf est appel sur une squence dlments. Il retourne une squence qui contient les lments anctres de chacun des lments sources, ainsi que llment source. Cet oprateur est assez proche de loprateur Ancestors, si ce nest quil ne peut tre appel que sur des lments et quil inclut llment source dans la squence de sortie. Prototypes Loprateur AncestorsAndSelf a deux prototypes. Premier prototype
public static IEnumerable<XElement> AncestorsAndSelf ( this IEnumerable<XElement> source )

Chapitre 8

Les oprateurs LINQ to XML

275

Ce prototype de loprateur AncestorsAndSelf est appel sur une squence dlments. Il retourne une squence dlments compose des lments sources et de leurs lments anctres. Second prototype
public static IEnumerable<XElement> AncestorsAndSelf<T> ( this IEnumerable<XElement> source, XName name )

Ce prototype est identique au prcdent mais, ici, un nom est pass dans les arguments. Seuls les lments sources et les anctres qui correspondent ce nom sont retourns dans la squence de sortie. Exemples Pour illustrer le premier prototype de loprateur AncestorsAndSelf, nous utiliserons le mme exemple que dans le Listing 8.1 mais, ici, nous appellerons loprateur AncestorsAndSelf et non loprateur Ancestors (voir Listing 8.5).
Listing 8.5 : Appel du premier prototype de loprateur AncestorsAndSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Descendants("FirstName"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des lments sources et de leurs anctres foreach (XElement element in elements.AncestorsAndSelf()) { Console.WriteLine("Elment anctre : {0}", element.Name); }

Le premier bloc de code cre le document XML. Une squence dlments FirstName est ensuite gnre (la mthode AncestorsAndSelf tant appele sur une squence dlments, et non sur un lment unique, il est donc ncessaire de crer une squence). Les lments de la squence source sont ensuite numrs et afchs. Enn, la squence retourne par AncestorsAndSelf est numre et les lments rsultants, afchs.

276

LINQ to XML

Partie III

Si tout fonctionne comme prvu, les rsultats devraient tre identiques ceux afchs par le premier exemple du prototype Ancestors mais, ici, les lments de la squence FirstName devraient galement tre inclus.
Elment Elment Elment Elment Elment Elment Elment Elment source : FirstName : valeur = Joe source : FirstName : valeur = Ewan anctre : FirstName anctre : BookParticipant anctre : BookParticipants anctre : FirstName anctre : BookParticipant anctre : BookParticipants

Pour illustrer le second prototype de loprateur AncestorsAndSelf, nous utiliserons le mme code que dans lexemple du second prototype de loprateur Ancestors. Mais ici, bien entendu, nous utiliserons loprateur AncestorsAndSelf et non loprateur Ancestors (voir Listing 8.6).
Listing 8.6 : Appel du second prototype de loprateur AncestorsAndSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Descendants("FirstName"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source: {0} : valeur = {1}", element.Name, element.Value); } // Affichage des anctres de chaque lment source foreach (XElement element in elements.AncestorsAndSelf("BookParticipant")) { Console.WriteLine("Elment anctre: {0}", element.Name); }

Voici les rsultats. Les anctres FirstName et BookParticipants ont t limins, car ils ne correspondent pas au paramtre pass loprateur AncestorsAndSelf :
Elment Elment Elment Elment source : FirstName : valeur = Joe source : FirstName : valeur = Ewan anctre : BookParticipant anctre : BookParticipant

Chapitre 8

Les oprateurs LINQ to XML

277

Le second prototype de cet oprateur semble avoir peu dintrt. En effet, pensez-vous que deux niveaux dlments ou plus portant le mme nom puissent cohabiter dans un arbre XML ?

Oprateur Attributes
Loprateur Attributes est appel sur une squence dlments. Il retourne une squence contenant les attributs de chacun des lments sources. Prototypes Loprateur Attributes a deux prototypes. Premier prototype
public static IEnumerable<XAttribute> Attributes ( this IEnumerable<XElement> source )

Ce premier prototype est appel sur une squence dlments. Il retourne une squence contenant tous les attributs des lments sources. Second prototype
public static IEnumerable<XAttribute> Attributes ( this IEnumerable<XElement> source, XName name )

Ce prototype est identique au prcdent mais, ici, seuls les attributs qui correspondent au nom pass en argument sont retourns dans la squence de sortie. Exemples Pour illustrer le premier prototype, nous allons ajouter des attributs larbre XML utilis dans les exemples prcdents. Nous travaillerons donc avec une squence dlments BookParticipant (voir Listing 8.7).
Listing 8.7 : Appel du premier prototype Attributes.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources

278

LINQ to XML

Partie III

foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des attributs des lments sources foreach (XAttribute attribute in elements.Attributes()) { Console.WriteLine("Attribut : {0} : valeur = {1}", attribute.Name, attribute.Value); }

La squence dlments BookParticipant est gnre puis afche. Loprateur Attributes est alors appel sur cette squence et les attributs des lments sont afchs laide dune boucle foreach. Voici les rsultats :
Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Attribut : type : valeur = Author Attribut : type : valeur = Editor

Pour illustrer le second prototype, nous utiliserons le mme code que dans lexemple prcdent, mais nous passerons un nom loprateur Attributes. Seuls les attributs portant ce nom seront inclus dans la squence de sortie (voir Listing 8.8).
Listing 8.8 : Appel du second prototype Attributes.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des attributs des lments sources foreach (XAttribute attribute in elements.Attributes("type")) { Console.WriteLine("Attribut : {0} : valeur = {1}", attribute.Name, attribute.Value); }

Chapitre 8

Les oprateurs LINQ to XML

279

Seuls les attributs portant le nom "type" sont retourns dans la squence de sortie. Voici les rsultats obtenus suite lappui sur Ctrl+F5 :
Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Attribut : type : valeur = Author Attribut : type : valeur = Editor

Si nous avions pass le paramtre "type" loprateur Attributes, les deux attributs nauraient pas t afchs. Cet oprateur est donc sensible la casse, ce qui na rien de surprenant, puisque XML est un langage sensible la casse.

Oprateur DescendantNodes
Loprateur DescendantNodes est appel sur une squence dlments ou de documents. Il retourne une squence contenant les nuds descendants de chacun des lments ou documents sources. Prototype Loprateur DescendantNodes a un seul prototype :
public static IEnumerable<XNode> DescendantNodes<T> ( this IEnumerable<T> source ) where T : XContainer

Cet oprateur est diffrent de la mthode XContainer.DescendantNodes. Le premier est appel sur une squence dlments ou de documents, la deuxime, sur un lment ou un document unique. Exemple Nous utiliserons le mme arbre XML que dans les exemples prcdents mais, ici, nous ajouterons un commentaire dans le premier lment BookParticipant. Ceci an que loprateur DescendantNodes retourne au moins un nud qui nest pas un lment. Les lments BookParticipant ayant plusieurs descendants, nous leur appliquerons loprateur DescendantNodes (voir Listing 8.9).
Listing 8.9 : Appel du prototype de loprateur DescendantNodes.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))));

280

LINQ to XML

Partie III

IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XNode node in elements.DescendantNodes()) { Console.WriteLine("Nud descendant : {0}", node); }

Les premires lignes dnissent larbre XML. Une squence dlments BookParticipant est alors dnie. Les lments de cette squence sont afchs, puis loprateur DescendantNodes lui est appliqu. Voici les rsultats :
Elment source : Elment source : Noeud descendant Noeud descendant Noeud descendant Noeud descendant Noeud descendant Noeud descendant Noeud descendant Noeud descendant Noeud descendant BookParticipant : valeur = JoeRattz BookParticipant : valeur = EwanBuckingham : <!Nouvel auteur--> : <FirstName>Joe</FirstName> : Joe : <LastName>Rattz</LastName> : Rattz : <FirstName>Ewan</FirstName> : Ewan : <LastName>Buckingham</LastName> : Buckingham

Comme vous pouvez le voir, loprateur DescendantNodes renvoie tous les nuds descendants de la squence BookParticipant : les lments, mais galement le commentaire. Remarquez aussi que chacun des lments descendants donne lieu deux nuds. Par exemple, <FirstName>Joe</FirstName> et Joe sont les deux nuds descendants relatifs llment Joe. Le premier est llment lui-mme et le deuxime, sa valeur XText. Je suis sr que vous aviez oubli que des objets XText sont automatiquement crs pour chaque lment

Oprateur DescendantNodesAndSelf
Loprateur DescendantNodesAndSelf est appel sur une squence dlments. Il retourne une squence contenant les lments sources et leurs nuds descendants. Prototype Loprateur DescendantNodesAndSelf a un seul prototype :
public static IEnumerable<XNode> DescendantNodesAndSelf ( this IEnumerable<XElement> source )

Chapitre 8

Les oprateurs LINQ to XML

281

Exemple Nous utiliserons le mme code que pour illustrer loprateur DescendantNodes mais, ici, nous appellerons loprateur DescendantNodesAndSelf (voir Listing 8.10).
Listing 8.10 : Appel du prototype de loprateur DescendantNodesAndSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des noeuds descendants des lments sources foreach (XNode node in elements.DescendantNodesAndSelf()) { Console.WriteLine("Noeud descendant : {0}", node); }

Voici le rsultat :
Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Noeud descendant : <BookParticipant type="Author"> <!Nouvel auteur--> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> Noeud descendant : <!Nouvel auteur--> Noeud descendant : <FirstName>Joe</FirstName> Noeud descendant : Joe Noeud descendant : <LastName>Rattz</LastName> Noeud descendant : Rattz Noeud descendant : <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> Noeud descendant : <FirstName>Ewan</FirstName> Noeud descendant : Ewan Noeud descendant : <LastName>Buckingham</LastName> Noeud descendant : Buckingham

Loprateur DescendantNodesAndSelf a retourn les lments de la squence dentre et leurs nuds descendants, y compris le commentaire du premier lment BookParticipant.

282

LINQ to XML

Partie III

Comme vous avez pu le voir dans lexemple prcdent, loprateur DescendantNodes "oublie" le commentaire dans la squence de sortie. Cette diffrence sera tudie un peu plus loin dans ce chapitre.

Oprateur Descendants
Loprateur Descendants peut tre appel sur une squence dlments ou de documents. Il retourne une squence qui contient tous les lments descendants des lments ou documents sources. Prototypes Loprateur Descendants a deux prototypes. Premier prototype
public static IEnumerable<XElement> Descendants<T> ( this IEnumerable<T> source ) where T : XContainer

Cet oprateur est diffrent de la mthode XContainer.Descendants. Le premier est appel sur une squence dlments ou de documents, la deuxime, sur un lment ou un document unique. Second prototype
public static IEnumerable<XElement> Descendants<T> ( this IEnumerable<T> source, XName name ) where T : XContainer

Ce prototype est identique au prcdent mais, ici, seuls les descendants des lments sources dont le nom correspond au paramtre sont retourns dans la squence de sortie. Exemples Pour illustrer le premier prototype, nous allons utiliser le mme code que pour loprateur DescendantNodes, mais nous allons appeler loprateur Descendants. Les rsultats devraient tre les mmes, ceci prs que seuls les lments devraient tre retourns dans la squence de sortie. Le Listing 8.11 reprsente le code utilis pour illustrer ce prototype.
Listing 8.11 : Appel du premier prototype de loprateur Descendants.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"),

Chapitre 8

Les oprateurs LINQ to XML

283

new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source: {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XNode node in elements.Descendants()) { Console.WriteLine("Nud descendant : {0}", node); }

Seuls les lments descendants des deux lments BookParticipant sont inclus dans la squence de sortie :
Elment Elment Elment Elment Elment Elment source : BookParticipant : valeur = JoeRattz source : BookParticipant : valeur = EwanBuckingham descendant : <FirstName>Joe</FirstName> descendant : <LastName>Rattz</LastName> descendant : <FirstName>Ewan</FirstName> descendant : <LastName>Buckingham</LastName>

En comparant ces rsultats ceux de loprateur DescendantNodes, nous pouvons noter plusieurs diffrences :
m m m

les descendants apparaissent en tant qulments et non en tant que nuds ; le commentaire nest pas inclus dans la squence de sortie ; les nuds descendants (Joe et Ratz, par exemple) sont exclus de la squence de sortie, puisquils sont de type XText et non XElement.

Nous illustrerons le second prototype avec le mme code mais, ici, nous passerons un nom dans largument de loprateur. Seuls les descendants correspondants seront inclus dans la squence de sortie (voir Listing 8.12).
Listing 8.12 : Appel du second prototype de loprateur Descendants.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements =

284

LINQ to XML

Partie III

xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XNode node in elements.Descendants("LastName")) { Console.WriteLine("Nud descendant : {0}", node); }

Voici les rsultats. Comme on pouvait sy attendre, seul le descendant LastName est inclus dans la squence de sortie :
Elment Elment Elment Elment source : BookParticipant : valeur = JoeRattz source : BookParticipant : valeur = EwanBuckingham descendant : <LastName>Rattz</LastName> descendant : <LastName>Buckingham</LastName>

Oprateur DescendantsAndSelf
Loprateur DescendantsAndSelf est appel sur une squence dlments. Il retourne une squence qui contient tous les lments descendants des lments sources. Prototypes Loprateur DescendantsAndSelf a deux prototypes. Premier prototype
public static IEnumerable<XElement> DescendantsAndSelf ( this IEnumerable<XElement> source )

Ce prototype est appel sur une squence dlments. Il retourne une squence qui contient tous les lments de la squence et leurs descendants. Second prototype
public static IEnumerable<XElement> DescendantsAndSelf ( this IEnumerable<XElement> source, XName name )

Le second prototype est semblable au premier, mais seuls les lments qui correspondent au paramtre sont retourns dans la squence de sortie. Exemples Pour illustrer le premier prototype, nous utiliserons le mme code que dans le premier exemple de loprateur Descendants mais, ici, nous appellerons loprateur DescendantAndSelf (voir Listing 8.13).

Chapitre 8

Les oprateurs LINQ to XML

285

Listing 8.13 : Appel du premier prototype de loprateur DescendantsAndSelf.


XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XNode node in elements.DescendantsAndSelf()) { Console.WriteLine("Nud descendant : {0}", node); }

Voici les rsultats :


Elment source : BookParticipant : valeur = JoeRattz Elment source : BookParticipant : valeur = EwanBuckingham Elment descendant : <BookParticipant type="Author"> <!Nouvel auteur--> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> Elment descendant : <FirstName>Joe</FirstName> Elment descendant : <LastName>Rattz</LastName> Elment descendant : <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> Elment descendant : <FirstName>Ewan</FirstName> Elment descendant : <LastName>Buckingham</LastName>

Les rsultats sont identiques ceux du premier prototype de loprateur Descendants, ceci prs quils incluent galement les lments sources eux-mmes, cest--dire les lments BookParticipant. Ne soyez pas tromp par la prsence du commentaire dans les rsultats. Cet objet est non pas un rsultat retourn par loprateur, mais bel et bien une partie de la squence

286

LINQ to XML

Partie III

dentre incluse dans les rsultats (cest la partie Self de loprateur DescendantsAndSelf). Pour illustrer le second prototype, nous utiliserons le mme code, mais nous passerons un paramtre loprateur pour limiter la sortie (voir Listing 8.14).
Listing 8.14 : Appel du second prototype de loprateur DescendantsAndSelf.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XNode node in elements.DescendantsAndSelf("LastName")) { Console.WriteLine("Nud descendant : {0}", node); }

Voici les rsultats :


Elment Elment Elment Elment source : BookParticipant : valeur = JoeRattz source : BookParticipant : valeur = EwanBuckingham descendant : <LastName>Rattz</LastName> descendant : <LastName>Buckingham</LastName>

La sortie est bien plus limite que dans lexemple prcdent. Il est mme difcile de faire la diffrence entre les oprateurs Descendants et DescendantsAndSelf. Ceci vient du fait que les lments sources nont pas t retourns, car ils ne correspondaient pas au paramtre pass loprateur. Il est peu probable que vous ayez utiliser la version "AndSelf" du second prototype de loprateur Descendants. En effet, les arbres XML que vous manipulerez nont que peu de chances davoir des lments portant le mme nom sur plusieurs niveaux hirarchiques.

Chapitre 8

Les oprateurs LINQ to XML

287

Oprateur Elements
Loprateur Elements peut tre appel sur une squence dlments ou de documents. Il retourne une squence dlments qui contient tous les lments enfants des lments ou documents sources. Les oprateurs Elements et Descendants sont diffrents. En effet, loprateur Elements ne retourne que les lments enfants de premier niveau, alors que loprateur Descendants retourne tous les enfants de la squence dentre, en parcourant rcursivement tous les niveaux hirarchiques de larborescence. Prototypes Loprateur Elements a deux prototypes. Premier prototype
public static IEnumerable<XElement> Elements<T> ( this IEnumerable<T> source ) where T : XContainer

Ce premier prototype est appel sur une squence dlments ou de documents. Il retourne une squence dlments qui contient tous les lments enfants des lments ou documents sources. Cet oprateur est diffrent de la mthode XContainer.Elements. Le premier est appel sur une squence dlments ou de documents, la deuxime, sur un lment ou un document unique. Second prototype
public static IEnumerable<XElement> Elements<T> ( this IEnumerable<T> source, XName name ) where T : XContainer

Ce prototype est identique au premier mais, ici, seuls les lments correspondant au paramtre pass loprateur sont retourns dans la squence de sortie. Exemples Nous utiliserons le mme code que dans lexemple du premier prototype de loprateur DescendantsAndSelf mais, ici, nous invoquerons loprateur Elements (voir Listing 8.15).
Listing 8.15 : Appel du premier prototype de loprateur Elements.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"),

288

LINQ to XML

Partie III

new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XElement element in elements.Elements()) { Console.WriteLine("Elment enfant : {0}", element); }

Voici les rsultats :


Elment Elment Elment Elment Elment Elment source source enfant enfant enfant enfant : : : : : : BookParticipant : valeur = JoeRattz BookParticipant : valeur = EwanBuckingham <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName>

Cet exemple retourne tous les lments enfants de la squence dentre. Pour limiter la squence de sortie aux seuls lments dont le nom est spci, nous utiliserons le second prototype de loprateur Elements (voir Listing 8.16).
Listing 8.16 : Appel du second prototype de loprateur Elements.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des nuds descendants des lments sources foreach (XElement element in elements.Elements("LastName")) { Console.WriteLine("Elment enfant : {0}", element); }

Chapitre 8

Les oprateurs LINQ to XML

289

Voici les rsultats :


Elment Elment Elment Elment source source enfant enfant : : : : BookParticipant : valeur = JoeRattz BookParticipant : valeur = EwanBuckingham <LastName>Rattz</LastName> <LastName>Buckingham</LastName>

Oprateur InDocumentOrder
Loprateur InDocumentOrder est appel sur une squence de nuds. Il retourne une squence compose des nuds enfants des nuds sources, dans lordre du document. Prototype Loprateur InDocumentOrder a un seul prototype :
public static IEnumerable<T> InDocumentOrder<T> ( this IEnumerable<T> source ) where T : XNode

Cet oprateur doit tre appel sur une squence compose de nuds ou dobjets drivs. Il retourne une squence du mme type compose des nuds enfants des nuds sources, dans lordre du document. Exemple Pour illustrer cet oprateur, nous avons besoin dune squence de nuds, lments et non lments. Pour ce faire, nous utiliserons la squence des nuds enfants des lments BookParticipant. Lun des nuds est un commentaire, pas un lment. Nous verrons ainsi comment loprateur InDocumentOrder se comporte sur ce type de nud (voir Listing 8.17).
Listing 8.17 : Appel du prototype de loprateur InDocumentOrder.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XNode> nodes = xDocument.Element("BookParticipants").Elements("BookParticipant"). Nodes().Reverse();

290

LINQ to XML

Partie III

// Affichage des nuds sources foreach (XNode node in nodes) { Console.WriteLine("Noeud source : {0}", node); } // Affichage des noeuds enfants des noeuds sources foreach (XNode node in nodes.InDocumentOrder()) { Console.WriteLine("Noeud ordonn : {0}", node); }

Aprs avoir construit larbre XML, les nuds enfants des lments BookParticipants sont obtenus en invoquant loprateur Nodes. Loprateur Reverse est appliqu au rsultat de loprateur Nodes pour inverser lordre de la squence (si ncessaire, reportez-vous la section relative loprateur LINQ to SQL Reverse, dans la deuxime partie de louvrage, pour avoir des informations complmentaires). La squence utilise en entre de loprateur InDocumentOrder est donc compose des nuds des lments BookParticipant, disposs dans lordre inverse de celui du document. Voici le rsultat :
Noeud Noeud Noeud Noeud Noeud Noeud Noeud Noeud Noeud Noeud source : <LastName>Buckingham</LastName> source : <FirstName>Ewan</FirstName> source : <LastName>Rattz</LastName> source : <FirstName>Joe</FirstName> source : <!Nouvel auteur--> ordonn : <!--Nouvel auteur--> ordonn : <FirstName>Joe</FirstName> ordonn : <LastName>Rattz</LastName> ordonn : <FirstName>Ewan</FirstName> ordonn : <LastName>Buckingham</LastName>

Comme vous pouvez le voir, les nuds sources sont dans lordre inverse des nuds de la squence de sortie.

Oprateur Nodes
Loprateur Nodes peut tre appel sur une squence dlments ou de documents. Il retourne une squence de nuds compose des nuds enfants des lments/documents sources. Cet oprateur est diffrent de loprateur DescendantNodes, car il ne retourne que les lments enfants de premier niveau, alors que loprateur DescendantNodes retourne tous les enfants de la squence dentre, en parcourant rcursivement tous les niveaux hirarchiques de larborescence. Prototype Loprateur Nodes na quun seul prototype :
public static IEnumerable<XNode> Nodes<T> ( this IEnumerable<T> source ) where T : XContainer

Chapitre 8

Les oprateurs LINQ to XML

291

Cet oprateur est diffrent de la mthode XContainer.Nodes. Le premier est appel sur une squence dlments ou de documents, la deuxime, sur un lment ou un document unique. Exemple Nous allons dnir un arbre XML, crer une squence source dlments BookParticipant et lui appliquer loprateur Nodes. Comme toujours, nous afcherons les lments sources et ceux retourns par loprateur (voir Listing 8.18).
Listing 8.18 : Appel du prototype de loprateur Nodes.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant"); // Affichage des lments sources foreach (XElement element in elements) { Console.WriteLine("Elment source : {0} : valeur = {1}", element.Name, element.Value); } // Affichage des noeuds enfants des lments sources foreach (XNode node in elements.Nodes()) { Console.WriteLine("Noeud enfant : {0}", node); }

Loprateur Nodes retourne une squence de nuds (et non dlments) enfants de la squence dentre. Le commentaire devrait donc tre inclus dans la squence de sortie. Voici les rsultats :
Elment source Elment source Noeud enfant : Noeud enfant : Noeud enfant : Noeud enfant : Noeud enfant : : BookParticipant : valeur = JoeRattz : BookParticipant : valeur = EwanBuckingham <!Nouvel auteur--> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName>

tant donn que seuls les nuds enfants de premier niveau sont retourns par loprateur Nodes, les nuds XText, enfants des lments FirstName et LastName, ne sont pas

292

LINQ to XML

Partie III

retourns. Si vous retournez quelques pages en arrire, vous verrez que loprateur DescendantNodes les incluait dans la squence de sortie.

Oprateur Remove
Loprateur Remove est appel sur une squence de nuds ou dattributs supprimer. Pour viter le bogue dHalloween, introduit au chapitre prcdent, les nuds/attributs sont mmoriss dans une liste. Prototypes Loprateur Remove a deux prototypes. Premier prototype
public static void Remove ( this IEnumerable<XAttribute> source )

Ce prototype est appel sur une squence dattributs. Il supprime tous les attributs de la squence dentre. Second prototype
public static void Remove<T> ( this IEnumerable<T> source ) where T : XNode

Ce prototype est appel sur une squence de nuds (ou dautres types qui en sont drivs). Il supprime tous les nuds de la squence dentre. Exemples Pour illustrer le premier prototype, nous avons besoin dune squence dattributs. Nous allons donc utiliser notre arbre XML standard et travailler sur une squence compose des attributs des lments BookParticipant. Nous allons afcher la squence des attributs sources, appeler loprateur Remove sur cette squence, puis afcher le document XML dans sa totalit, pour nous assurer que loprateur Remove a bien fait son travail (voir Listing 8.19).
Listing 8.19 : Appel du premier prototype de loprateur Remove.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))));

Chapitre 8

Les oprateurs LINQ to XML

293

IEnumerable<XAttribute> attributes = xDocument.Element("BookParticipants").Elements("BookParticipant").Attributes(); // Affichage des attributs sources foreach (XAttribute attribute in attributes) { Console.WriteLine("Attribut source : {0} : valeur = {1}", attribute.Name, attribute.Value); } attributes.Remove(); // Affichage du document XML Console.WriteLine(xDocument);

Voici les rsultats :


Attribut source : type : valeur = Author Attribut source : type : valeur = Editor <BookParticipants> <BookParticipant> <!--Nouvel auteur--> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Nous allons maintenant illustrer le second prototype. Plutt que nous contenter dobtenir puis de supprimer une squence de nuds, nous allons envisager quelque chose de plus intressant : extraire la squence de commentaires de certains lments et supprimer uniquement ces objets (voir Listing 8.20).
Listing 8.20 : Appel du second prototype.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XComment("Nouvel auteur"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XComment> comments = xDocument.Element("BookParticipants").Elements("BookParticipant"). Nodes().OfType<XComment>(); // Affichage des commentaires sources foreach (XComment comment in comments) { Console.WriteLine("Source comment: {0}", comment); }

294

LINQ to XML

Partie III

comments.Remove(); // Affichage du document XML Console.WriteLine(xDocument);

Aprs avoir construit la squence source, les nuds enfants (Nodes) de type XComment (OfType<XComment>) sont placs dans la squence comments. Reportez-vous si ncessaire la deuxime partie de ce livre pour en savoir plus sur loprateur de requte standard OfType. La mthode Remove est alors applique la squence comments. Aprs lexcution de cet oprateur, larbre XML est priv de tout commentaire dans les lments BookParticipant. Voici le rsultat :
Source comment: <!Nouvel auteur--> <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Loprateur OfType est trs pratique et il sintgre parfaitement dans une requte LINQ to XML. Il pourrait se rvler trs utile en situation relle.

Rsum
Au chapitre prcdent, nous avons introduit lAPI LINQ to XML et montr comment lutiliser pour crer, modier, sauvegarder et lire des arbres XML. Nous avons intentionnellement utilis le mot "arbre" et non le mot "document", car avec LINQ to XML il nest plus ncessaire de manipuler des documents. Nous avons galement montr comment effectuer une requte sur un nud/un lment pour atteindre les nuds/lments qui lui sont hirarchiquement lis. Dans ce chapitre, vous avez galement appris interroger des squences de nuds ou dlments en utilisant les oprateurs de LINQ to XML. Arriv ce point dans la lecture du livre, vous devriez tre en mesure deffectuer des requtes lmentaires sur des arbres XML en utilisant les oprateurs LINQ to XML. Cette nouvelle API devrait se rvler trs utile pour interroger des donnes en particulier si vous lui adjoignez des oprateurs de requte standard. Vous connaissez maintenant toutes les techniques de base permettant de dnir des requtes LINQ to SQL. Au chapitre suivant, nous aborderons des requtes lgrement plus complexes et nous nous intresserons dautres domaines daction de LINQ to XML tels que la validation et la transformation.

9
Les autres possibilits de XML
Dans les deux chapitres prcdents, vous avez appris crer, modier et parcourir des donnes XML en utilisant lAPI LINQ to XML. Nous avons galement vu comment utiliser des blocs de construction pour crer des requtes XML trs puissantes. Je pense que, ds prsent, vous serez daccord pour afrmer que LINQ to XML peut couvrir 90 % de vos besoins en matire de XML. Mais quen est-il des 10 % restants ? Voyons si nous pouvons diminuer ce pourcentage. Si Microsoft avait ajout la validation de schma, les transformations et les requtes XPath, quel serait le pourcentage selon vous ? Nous avons vu les bases de lAPI LINQ to XML et comment effectuer les requtes lmentaires. Nous allons maintenant nous intresser des requtes plus complexes et aussi plus proches du monde rel. Dans ce chapitre, nous allons passer en revue quelques exemples qui, je lespre, rendront vos yeux les requtes XML des plus triviales lorsquelles seront effectues via lAPI LINQ to XML. Pour dcrire plus compltement cette API, nous aborderons des fonctionnalits complmentaires (essentiellement la transformation et la validation) et vous donnerons diverses informations bonnes connatre en LINQ to XML. Dune faon plus spcique, nous verrons comment effectuer des transformations avec et sans XSLT, comment valider un document XML par rapport un schma et donnerons un exemple de requte utilisant le style XPath.

Espaces de noms rfrencs


Outre les espaces de noms LINQ et LINQ to XML dsormais traditionnels, System.Linq et System.Xml.Linq, les exemples de ce chapitre utilisent galement les espaces de noms System.Xml, System.Xml.Schema, System.Xml.Xsl et System.Xml.XPath.

296

LINQ to XML

Partie III

moins quelles ne soient dj prsentes dans votre code, vous devrez donc ajouter les directives using suivantes :
using using using using using using System.Linq; System.Xml; System.Xml.Linq; System.Xml.Schema; System.Xml.XPath; System.Xml.Xsl;

Requtes
Dans le chapitre prcdent, nous avons vu les principes de base permettant dexcuter des requtes XML via LINQ to XML. La plupart des exemples avaient pour but lillustration dun oprateur ou dune proprit. Dans cette section, nous allons passer en revue plusieurs exemples "orients solution" et, donc, plus proches de la ralit. La description du chemin nest pas une obligation Dans les chapitres prcdents, la plupart des exemples "plongeaient" dans la hirarchie XML pour obtenir une rfrence sur un lment particulier en utilisant les oprateurs Element ou Elements de faon rcursive, jusqu ce que llment vis soit atteint. Ainsi, beaucoup dexemples contenaient ce type dinstruction :
IEnumerable<XElement> elements = xDocument.Element("BookParticipants").Elements("BookParticipant");

Cet exemple accde llment enfant BookParticipants du document, puis aux lments enfants BookParticipant de llment BookParticipants. Cette technique nest pas toujours ncessaire. Vous pouvez en effet utiliser un code comparable au Listing 9.1.
Listing 9.1 : Accs des lments sans dcrire leur chemin.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument.Descendants("BookParticipant"); foreach (XElement element in elements) { Console.WriteLine("Elment: {0} : valeur = {1}", element.Name, element.Value); }

Chapitre 9

Les autres possibilits de XML

297

Dans cet exemple, linstruction en gras obtient les descendants BookParticipant du document. tant donn que laccs ne se fait pas dans une branche particulire de larbre XML, il est ncessaire de connatre le schma, car il serait possible daccder par erreur certaines branches de larbre. Cependant, cette technique fonctionne dans de nombreux cas. Voici les rsultats :
Elment: BookParticipant : valeur = JoeRattz Elment: BookParticipant : valeur = EwanBuckingham

Si tous les lments BookParticipant ne sont pas utiles, vous pouvez restreindre la requte. Le Listing 9.2, par exemple, ne retourne que les lments dont llment FirstName a pour valeur "Ewan".
Listing 9.2 : Accs restreint des lments sans dcrire le chemin.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = xDocument .Descendants("BookParticipant") .Where(e => ((string)e.Element("FirstName")) == "Ewan"); foreach (XElement element in elements) { Console.WriteLine("Elment: {0} : valeur = {1}", element.Name, element.Value); }

Cette fois-ci, nous avons appliqu loprateur Where en sufxe dans la dnition de lobjet elements. Remarquez lutilisation de loprateur de casting (string) pour comparer la valeur de llment avec la chane "Ewan". Voici les rsultats :
Elment: BookParticipant: valeur = EwanBuckingham

Il est parfois ncessaire de contrler lordre des rsultats. Dans le Listing 9.3, nous allons modier lexpression lambda de loprateur Where pour que deux lments soient retourns. La requte portera sur lattribut type.
Listing 9.3 : Accs restreint des lments sans dcrire le chemin, en dfinissant lordre et en utilisant la syntaxe dinterrogation des requtes.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"),

298

LINQ to XML

Partie III

new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); IEnumerable<XElement> elements = from e in xDocument.Descendants("BookParticipant") where ((string)e.Attribute("type")) != "Illustrator" orderby ((string)e.Element("LastName")) select e; foreach (XElement element in elements) { Console.WriteLine("Elment: {0} : valeur = {1}", element.Name, element.Value); }

La requte porte toujours sur les lments BookParticipant du document mais, ici, seuls les lments dont lattribut type a une valeur diffrente de "Illustrator" sont slectionns. Dans cet arbre, cela correspond tous les lments BookParticipant. Les rsultats sont alors classs par lments LastName croissants. Remarquez lutilisation doprateurs de casting pour obtenir la valeur de lattribut type et de llment LastName. Voici les rsultats :
Elment: BookParticipant : valeur = EwanBuckingham Elment: BookParticipant : valeur = JoeRattz

Une requte complexe Jusquici, toutes les requtes passes en revue taient simplistes. Avant de passer un autre sujet, nous allons tudier une requte complexe. Nous utiliserons des donnes mises disposition par le W3C des ns de tests. Lexemple du Listing 9.4 contient des donnes issues de trois documents XML diffrents. Ces documents sont obtenus en divisant une reprsentation texte des documents XML suggrs par le W3C. Nous allons expliquer de faon dtaille chacune des parties du code. La premire tape va consister dnir les documents en utilisant du code XML.
Listing 9.4 : Une requte complexe qui effectue une jointure sur trois documents en utilisant la syntaxe dexpression de requte de LINQ.
XDocument users = XDocument.Parse( @"<users> <user_tuple> <userid>U01</userid> <name>Tom Jones</name> <rating>B</rating> </user_tuple> <user_tuple> <userid>U02</userid> <name>Mary Doe</name>

Chapitre 9

Les autres possibilits de XML

299

<rating>A</rating> </user_tuple> <user_tuple> <userid>U03</userid> <name>Dee Linquent</name> <rating>D</rating> </user_tuple> <user_tuple> <userid>U04</userid> <name>Roger Smith</name> <rating>C</rating> </user_tuple> <user_tuple> <userid>U05</userid> <name>Jack Sprat</name> <rating>B</rating> </user_tuple> <user_tuple> <userid>U06</userid> <name>Rip Van Winkle</name> <rating>B</rating> </user_tuple> </users>"); XDocument items = XDocument.Parse( @"<items> <item_tuple> <itemno>1001</itemno> <description>Red Bicycle</description> <offered_by>U01</offered_by> <start_date>1999-01-05</start_date> <end_date>1999-01-20</end_date> <reserve_price>40</reserve_price> </item_tuple> <item_tuple> <itemno>1002</itemno> <description>Motorcycle</description> <offered_by>U02</offered_by> <start_date>1999-02-11</start_date> <end_date>1999-03-15</end_date> <reserve_price>500</reserve_price> </item_tuple> <item_tuple> <itemno>1003</itemno> <description>Old Bicycle</description> <offered_by>U02</offered_by> <start_date>1999-01-10</start_date> <end_date>1999-02-20</end_date> <reserve_price>25</reserve_price> </item_tuple> <item_tuple> <itemno>1004</itemno> <description>Tricycle</description> <offered_by>U01</offered_by> <start_date>1999-02-25</start_date> <end_date>1999-03-08</end_date> <reserve_price>15</reserve_price> </item_tuple> <item_tuple> <itemno>1005</itemno> <description>Tennis Racket</description> <offered_by>U03</offered_by> <start_date>1999-03-19</start_date> <end_date>1999-04-30</end_date>

300

LINQ to XML

Partie III

<reserve_price>20</reserve_price> </item_tuple> <item_tuple> <itemno>1006</itemno> <description>Helicopter</description> <offered_by>U03</offered_by> <start_date>1999-05-05</start_date> <end_date>1999-05-25</end_date> <reserve_price>50000</reserve_price> </item_tuple> <item_tuple> <itemno>1007</itemno> <description>Racing Bicycle</description> <offered_by>U04</offered_by> <start_date>1999-01-20</start_date> <end_date>1999-02-20</end_date> <reserve_price>200</reserve_price> </item_tuple> <item_tuple> <itemno>1008</itemno> <description>Broken Bicycle</description> <offered_by>U01</offered_by> <start_date>1999-02-05</start_date> <end_date>1999-03-06</end_date> <reserve_price>25</reserve_price> </item_tuple> </items>"); XDocument bids = XDocument.Parse( @"<bids> <bid_tuple> <userid>U02</userid> <itemno>1001</itemno> <bid>35</bid> <bid_date>1999-01-07</bid_date> </bid_tuple> <bid_tuple> <userid>U04</userid> <itemno>1001</itemno> <bid>40</bid> <bid_date>1999-01-08</bid_date> </bid_tuple> <bid_tuple> <userid>U02</userid> <itemno>1001</itemno> <bid>45</bid> <bid_date>1999-01-11</bid_date> </bid_tuple> <bid_tuple> <userid>U04</userid> <itemno>1001</itemno> <bid>50</bid> <bid_date>1999-01-13</bid_date> </bid_tuple> <bid_tuple> <userid>U02</userid> <itemno>1001</itemno> <bid>55</bid> <bid_date>1999-01-15</bid_date> </bid_tuple> <bid_tuple> <userid>U01</userid> <itemno>1002</itemno> <bid>400</bid>

Chapitre 9

Les autres possibilits de XML

301

<bid_date>1999-02-14</bid_date> </bid_tuple> <bid_tuple> <userid>U02</userid> <itemno>1002</itemno> <bid>600</bid> <bid_date>1999-02-16</bid_date> </bid_tuple> <bid_tuple> <userid>U03</userid> <itemno>1002</itemno> <bid>800</bid> <bid_date>1999-02-17</bid_date> </bid_tuple> <bid_tuple> <userid>U04</userid> <itemno>1002</itemno> <bid>1000</bid> <bid_date>1999-02-25</bid_date> </bid_tuple> <bid_tuple> <userid>U02</userid> <itemno>1002</itemno> <bid>1200</bid> <bid_date>1999-03-02</bid_date> </bid_tuple> <bid_tuple> <userid>U04</userid> <itemno>1003</itemno> <bid>15</bid> <bid_date>1999-01-22</bid_date> </bid_tuple> <bid_tuple> <userid>U05</userid> <itemno>1003</itemno> <bid>20</bid> <bid_date>1999-02-03</bid_date> </bid_tuple> <bid_tuple> <userid>U01</userid> <itemno>1004</itemno> <bid>40</bid> <bid_date>1999-03-05</bid_date> </bid_tuple> <bid_tuple> <userid>U03</userid> <itemno>1007</itemno> <bid>175</bid> <bid_date>1999-01-25</bid_date> </bid_tuple> <bid_tuple> <userid>U05</userid> <itemno>1007</itemno> <bid>200</bid> <bid_date>1999-02-08</bid_date> </bid_tuple> <bid_tuple> <userid>U04</userid> <itemno>1007</itemno> <bid>225</bid> <bid_date>1999-02-12</bid_date> </bid_tuple> </bids>");

302

LINQ to XML

Partie III

Ces trois documents reprsentent les donnes (utilisateurs, objets vendus et enchres) manipules sur un site web de vente aux enchres. Ils ont t crs en appelant la mthode XDocument.Parse sur des reprsentations chanes des donnes. La requte va consister extraire les enchres suprieures 50 dollars. Les rsultats doivent faire apparatre la date, le montant de lenchre, le nom de la personne qui en est lorigine, le numro de lobjet et sa description. Voici la requte :
var biddata = from b in bids.Descendants("bid_tuple") where ((double)b.Element("bid")) > 50 join u in users.Descendants("user_tuple") on ((string)b.Element("userid")) equals ((string)u.Element("userid")) join i in items.Descendants("item_tuple") on ((string)b.Element("itemno")) equals ((string)i.Element("itemno")) select new {Item = ((string)b.Element("itemno")), Description = ((string)i.Element("description")), User = ((string)u.Element("name")), Date = ((string)b.Element("bid_date")), Price = ((double)b.Element("bid"))};

La requte est plus complexe que celles tudies jusquici. La premire ligne utilise la mthode Descendants pour accder aux descendants bid_tuple du document bids. La ligne suivante utilise loprateur Where pour ne conserver que les enchres suprieures 50 dollars. Il peut sembler inhabituel dutiliser une clause Where si tt dans la requte. Cette clause aurait tout aussi bien pu tre spcie juste avant la clause select, mais cela aurait signi que le Where aurait t appliqu sur la jointure entre les utilisateurs et les objets, y compris pour les enchres infrieures 50 dollars. En ayant rduit le nombre de donnes avant la jointure, la charge de travail a ainsi t allge pour la suite de la requte et les performances, amliores. Une fois limites aux seules enchres suprieures 50 dollars, les donnes sont jointes au document XML users par lintermdiaire de llment userid (lignes 3 5), an dobtenir le nom de chaque utilisateur. Arrivs ce point dans la requte, nous avons joint les documents bids et users et limit les donnes aux enchres suprieures 50 dollars. Les trois prochaines lignes (6 8) effectuent une jointure sur le document XML items par lintermdiaire du champ itemno an dobtenir la description de lobjet. ce point, les documents bids, users et items sont joints. Remarquez que diffrents oprateurs de casting ont t utiliss pour obtenir la valeur des lments dans le type souhait. Ainsi, par exemple, le montant de lenchre a t obtenu avec un oprateur (double). Les enchres sont au format string mais, tant donn que leur contenu peut tre converti en une valeur double, loprateur de casting a fait son travail.

Chapitre 9

Les autres possibilits de XML

303

La prochaine tape va consister slectionner une classe anonyme qui contient les lments enfants des lments issus de cette double jointure. Nous allons commencer par afcher un en-tte :
Console.WriteLine("{0,-12} {1,-12} {2,-6} {3,-14} {4,10}", "Date", "User", "Item", "Description", "Price"); Console.WriteLine("===================================================");

Les instructions suivantes numrent la squence et afchent les valeurs correspondantes :


bid: foreach (var bd in biddata) { Console.WriteLine("{0,-12} {1,-12} {2,-6} {3,-14} {4,10:C}", bd.Date, bd.User, bd.Item, bd.Description, bd.Price); }

Cette portion de code est triviale. En fait, mis part la requte elle-mme, tout le reste du code est simplissime. Voici les rsultats :
Date User Item Description Price

=================================================================================== 1999-01-15 1999-02-14 1999-02-16 1999-02-17 1999-02-25 1999-03-02 1999-01-25 1999-02-08 1999-02-12 Mary Doe Tom Jones Mary Doe Dee Linquent Roger Smith Mary Doe Dee Linquent Jack Sprat Roger Smith 1001 1002 1002 1002 1002 1002 1007 1007 1007 Red Bicycle Motorcycle Motorcycle Motorcycle Motorcycle Motorcycle Racing Bicycle Racing Bicycle Racing Bicycle $55.00 $400.00 $600.00 $800.00 $1,000.00 $1,200.00 $175.00 $200.00 $225.00

Quelques lignes de code ont suf pour joindre trois documents XML ! Maintenant, je suis sr que vous vous rendez compte de la puissance de LINQ to XML. Mais attendez un peu, dautres possibilits trs intressantes vous attendent dans les pages suivantes

Transformations
LINQ to XML vous permet deffectuer des transformations en utilisant deux approches diamtralement opposes. La premire consiste utiliser XSLT via les classes passerelles XmlReader et XmlWriter. La seconde approche consiste utiliser LINQ to XML en construisant fonctionnellement le document XML cible et en incluant une requte LINQ to XML dans le document source XML.

304

LINQ to XML

Partie III

XSLT est une technologie XML standard. Des outils permettant dcrire, de dboguer et de tester les transformations XSLT sont dores et dj disponibles. Par ailleurs, il est possible que vous disposiez dj de documents XSLT. Si tel est le cas, vous pouvez les utiliser dans vos nouvelles applications par lintermdiaire de LINQ to XML. De nombreux documents XSLT sont disponibles. Vous navez qu choisir celui qui sadapte le mieux vos souhaits. De plus, lutilisation de XSLT pour vos transformations se rvle plus dynamique. Contrairement lapproche "construction fonctionnelle" de LINQ to XML, il nest pas ncessaire de recompiler le code pour changer la transformation : le simple fait de modier le document XSLT suft pour changer la transformation lexcution. Enn, la technologie XSLT est bien connue et bon nombre de dveloppeurs experts dans ce domaine peuvent vous assister. Ce fait nest bien entendu plus dactualit si vous choisissez lapproche "construction fonctionnelle". Lapproche "construction fonctionnelle" ne vous demandera pas un gros investissement. Les transformations XML seront en effet effectues par lintermdiaire de LINQ to XML. Si vous ne connaissez pas XSLT, et si vos besoins en matire de transformations sont modestes, cette approche peut vous convenir. Par ailleurs, bien que la construction fonctionnelle soit moins pratique que la modication dun document XSLT, la ncessit davoir recompiler le code pour modier une transformation peut tre considre comme une scurit supplmentaire : un tiers ne peut ainsi modier un document externe pour changer le sens dune transformation. Transformations avec XSLT Pour effectuer une transformation XML en utilisant XSLT, vous utiliserez les classes passerelles XmlWriter et XmlReader. Vous les obtiendrez partir des mthodes CreateWriter et CreateReader des classes XDocument. Lexemple du Listing 9.5 demande quelques explications. Nous les donnerons au fur et mesure, en sparant le code en plusieurs blocs fonctionnels.
Listing 9.5 : Transformation dun document XML avec XSLT.
string xsl = @"<xsl:stylesheet version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform> <xsl:template match=//BookParticipants> <html> <body> <h1>Book Participants</h1> <table> <tr align=left> <th>Role</th> <th>First Name</th> <th>Last Name</th> </tr> <xsl:apply-templates></xsl:apply-templates> </table> </body>

Chapitre 9

Les autres possibilits de XML

305

</html> </xsl:template> <xsl:template match=BookParticipant> <tr> <td><xsl:value-of select=@type/></td> <td><xsl:value-of select=FirstName/></td> <td><xsl:value-of select=LastName/></td> </tr> </xsl:template> </xsl:stylesheet>";

Ce code se contente de dnir quelques instructions XSL qui vont crer du code HTML an dafcher les donnes XML BookParticipant dans un tableau HTML. La prochaine tape va consister crer le document XML avec les participants :
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))));

Ce code a dj t utilis de maintes reprises dans les pages prcdentes. Cest partir de maintenant que la magie va oprer. Nous allons crer un document XDocument pour la version transforme. partir de ce document, nous dnirons un XmlWriter, nous instancierons un objet XslCompiledTransform, nous chargerons lobjet transform avec la feuille de style de transformation et nous transformerons le document XML dentre en la sortie XmlWriter :
XDocument transformedDoc = new XDocument(); using (XmlWriter writer = transformedDoc.CreateWriter()) { XslCompiledTransform transform = new XslCompiledTransform(); transform.Load(XmlReader.Create(new StringReader(xsl))); transform.Transform(xDocument.CreateReader(), writer); } Console.WriteLine(transformedDoc);

Voici le rsultat de la transformation. Comme vous pouvez le voir, nous utilisons les passerelles XmlWriter et XmlReader pour effectuer la transformation :
<html> <body> <h1>Book Participants</h1> <table> <tr align="left"> <th>Role</th> <th>First Name</th> <th>Last Name</th> </tr> <tr> <td>Author</td> <td>Joe</td> <td>Rattz</td> </tr>

306

LINQ to XML

Partie III

<tr> <td>Editor</td> <td>Ewan</td> <td>Buckingham</td> </tr> </table> </body> </html>

Transformations avec la construction fonctionnelle Cette section va vous montrer comment effectuer des transformations XSLT en utilisant lAPI LINQ to XML. Logiquement parlant, une transformation peut tre aussi simple que la combinaison dun arbre XML dni par la construction fonctionnelle et dune requte XML incorpore dans cet arbre. Nous allons expliquer les transformations XML travers un exemple. Dans de nombreux autres exemples des chapitres ddis LINQ to XML, nous avons utilis larbre XML suivant :
<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Supposons que nous devions transformer cet arbre XML comme suit :
<MediaParticipants type="book"> <Participant Role="Author" Name="Joe Rattz" /> <Participant Role="Editor" Name="Ewan Buckingham" / > </MediaParticipants>

Pour accomplir cette transformation, nous allons utiliser la construction fonctionnelle en incluant une requte dans larbre. Cette approche va consister construire un nouveau document dont lallure correspond larbre XML cible en appliquant une requte LINQ to XML au document XML source pour y piocher les donnes. Cest la structure de larbre XML cible qui va guider la construction fonctionnelle et la logique de la requte. tant donn que cette tche est lgrement plus complexe que la plupart des exemples LINQ to XML prcdents, nous donnerons des explications chaque fois que cela est ncessaire (voir Listing 9.6).
Listing 9.6 : Transformation dun document XML.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"),

Chapitre 9

Les autres possibilits de XML

307

new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML original :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine);

Ce code dnit le document XML source que nous allons transformer. La prochaine tape consiste construire le nouveau document et llment racine :
XDocument xTransDocument = new XDocument( new XElement("MediaParticipants",

Rappelez-vous que la structure de larbre XML de sortie guide la construction fonctionnelle. Arrivs ce point, nous avons un document et llment racine, Mediaparticipants. Nous devons maintenant ajouter lattribut type llment racine :
new XAttribute("type", "book"),

Lattribut type et sa valeur nexistent pas dans le document XML source. Ils ont donc t dnis dans le code. Maintenant que lattribut type est dni, nous allons gnrer un lment Participant pour chacun des lments BookParticipant du document XML original. Pour ce faire, il va sufre dexcuter la requte suivante :
xDocument.Element("BookParticipants") .Elements("BookParticipant")

Ces deux lignes de code fournissent une squence dlments BookParticipant. Nous allons maintenant gnrer et initialiser un lment Participant pour chaque lment BookParticipant. Pour ce faire, nous utiliserons loprateur de projection Select :
.Select(e => new XElement("Participant",

Nous allons maintenant construire les attributs Role et Name de llment Participant en piochant leurs valeurs dans llment BookParticipant :
new XAttribute("Role", (string)e.Attribute("type")), new XAttribute("Name", (string)e.Element("FirstName") + " " + (string)e.Element("LastName"))))));

Enn, nous afchons le document XML transform :


Console.WriteLine("Document XML transform:"); Console.WriteLine(xTransDocument);

Voici le rsultat, tout fait conforme aux attentes :


Document XML original: <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName>

308

LINQ to XML

Partie III

</BookParticipant> </BookParticipants> Document XML transform : <MediaParticipants type="book"> <Participant Role="Author" Name="Joe Rattz" /> <Participant Role="Ed </MediaParticipants>

Astuces Vous devez connatre quelques astuces si vous prvoyez deffectuer des transformations XML via lAPI LINQ to XML.
Simplication de tches complexes avec les mthodes Helper La totalit du code responsable dune transformation ou dune requte nest pas oblige de se trouver dans le code de transformation lui-mme : vous pouvez crer des mthodes Helper pour effectuer des transformations plus complexes.

Dans cette section, vous trouverez du code qui vous montrera comment crer une mthode Helper pour diviser une tche complexe en plusieurs tches moins complexes. Transformation dun document XML avec une mthode Helper
static IEnumerable<XElement> Helper() { XElement[] elements = new XElement[] { new XElement("Element", "A"), new XElement("Element", "B")}; return(elements); }

Le Listing 9.7 dbute par la construction dun arbre XML. Le nud racine, RootElement, est cr lors de lappel du constructeur. Pour crer les nuds enfants, la mthode Helper est appele. Il nest pas important de savoir ce que fait cette mthode. Ce qui importe, cest quelle va nous aider construire larbre XML et quelle peut tre appele depuis la construction fonctionnelle de larbre XML.
Listing 9.7 : Utilisation dune mthode Helper pour transformer un document XML.
XElement xElement = new XElement("RootElement", Helper()); Console.WriteLine(xElement);

Voici les rsultats :


<RootElement> <Element>A</Element> <Element>B</Element> </RootElement>

Chapitre 9

Les autres possibilits de XML

309

Comme il a t dit au Chapitre 7, le constructeur XElement sait comment grer les IEnumerable<T>. Cest justement le type retourn par la mthode Helper. La vie est belle, nest-ce pas ?
Suppression de nuds la construction avec la valeur null Pour une raison ou une autre (donne manquante ou valeurs inappropries, par exemple), il se peut que vous dsiriez supprimer certains nuds de la construction. Dans la section "Cration dlments avec XElement" du Chapitre 7, javais indiqu quil tait possible de passer la valeur null un lment et que cela pourrait tre utile lorsque lon effectue une transformation. Cette technique est en effet trs utile, puisquelle supprime la construction du nud correspondant.

Nous allons raisonner sur un exemple. Nous allons crer une squence dlments, puis lancer la construction dun arbre XML bas sur cette squence. Si la valeur dun lment est "A", nous nallons pas linclure dans la squence de sortie. Dans ce cas, nous passerons la valeur null au constructeur (voir Listing 9.8).
Listing 9.8 : Suppression de nuds la construction en transmettant la valeur null au constructeur.
IEnumerable<XElement> elements = new XElement[] { new XElement("Element", "A"), new XElement("Element", "B")}; XElement xElement = new XElement("RootElement", elements.Select(e => (string)e != "A" ? new XElement(e.Name, (string)e) : null)); Console.WriteLine(xElement);

Les quatre premires lignes dnissent une squence dlments. Les deux lignes suivantes construisent llment racine et numrent la squence dentre. Loprateur Select agit de faon binaire : il renvoie un lment de la squence dentre si sa valeur est diffrente de "A". Dans le cas contraire, il renvoie la valeur null. Dans le premier cas, le constructeur ajoute cet lment larbre XML. Dans le second, il lignore, ce qui revient dire quil lexclut de larbre XML. titre dinformation, remarquez que, pour accder la valeur de llment, nous avons utilis loprateur de casting ( string) dans lexpression lambda de loprateur Select. Voici les rsultats :
<RootElement> <Element>B</Element> </RootElement>

Comme on pouvait sy attendre, llment "A" ne fait pas partie de larbre. Dautres approches sont possibles pour exclure un nud de larbre XML. Par exemple, nous aurions tout aussi bien pu utiliser loprateur Where pour ltrer les lments dont la

310

LINQ to XML

Partie III

valeur est gale "A". Mais, ici, le propos tait de montrer le rsultat obtenu lorsque la valeur null est passe un constructeur. Il existe dautres faons dutiliser ce concept. Supposons que vous deviez gnrer un arbre XML dans lequel certaines instances peuvent contenir un lment vide et que vous prfriez que ces lments ne fassent pas partie de larbre (voir Listing 9.9).
Listing 9.9 : Cet exemple gnre un lment vide.
IEnumerable<XElement> elements = new XElement[] { new XElement("BookParticipant", new XElement("Name", "Joe Rattz"), new XElement("Book", "Pro LINQ: Language Integrated Query in C# 2008")), new XElement("BookParticipant", new XElement("Name", "John Q. Public"))}; XElement xElement = new XElement("BookParticipants", elements.Select(e => new XElement(e.Name, new XElement(e.Element("Name").Name, e.Element("Name").Value), new XElement("Books", e.Elements("Book"))))); Console.WriteLine(xElement);

Le premier bloc de ce code gnre une squence compose de deux lments BookParticipants. Le premier a un lment enfant nomm Book, mais pas le second. Le deuxime bloc construit un arbre XML en utilisant la squence dlments du premier bloc. Un lment nomm BookParticipant est cr. Le nom du participant puis la liste des livres du participant sont ajouts larbre en tant qulment enfant de llment BookParticipant. Voici le rsultat de ce code :
<BookParticipants> <BookParticipant> <Name>Joe Rattz</Name> <Books> <Book>Pro LINQ: Language Integrated Query in C# 2008</Book> </Books> </BookParticipant> <BookParticipant> <Name>John Q. Public</Name> <Books /> </BookParticipant> </BookParticipants>

Larbre XML est bien conforme aux attentes. Remarquez que llment Books du deuxime participant est vide. Comment supprimer cet lment de larbre ? Le Listing 9.10 vous montre comment transmettre la valeur null au constructeur si la squence source ne contient aucun lment Book.
Listing 9.10 : Cet exemple supprime les lments vides de larborescence.
IEnumerable<XElement> elements = new XElement[] { new XElement("BookParticipant",

Chapitre 9

Les autres possibilits de XML

311

new XElement("Name", "Joe Rattz"), new XElement("Book", "Pro LINQ: Language Integrated Query in C# 2008")), new XElement("BookParticipant", new XElement("Name", "John Q. Public"))}; XElement xElement = new XElement("BookParticipants", elements.Select(e => new XElement(e.Name, new XElement(e.Element("Name").Name, e.Element("Name").Value), e.Elements("Book").Any() ? new XElement("Books", e.Elements("Book")) : null))); Console.WriteLine(xElement);

Les instructions en gras reprsentent les modications par rapport au Listing 9.8. Plutt que crer un lment Books et dy dnir tous les lments enfants Book, nous utilisons ici loprateur de requte standard Any, combin loprateur ternaire (if? then: else) pour crer llment Books si et seulement si au moins un lment Book est prsent dans la squence dentre. Si aucun lment Book nest trouv, loprateur ternaire renvoie la valeur null au constructeur, qui limine la cration de llment Books correspondant. Voici les rsultats du Listing 9.10 :
<BookParticipants> <BookParticipant> <Name>Joe Rattz</Name> <Books> <Book>Pro LINQ: Language Integrated Query in C# 2008</Book> </Books> </BookParticipant> <BookParticipant> <Name>John Q. Public</Name> </BookParticipant> </BookParticipants>

Comme vous pouvez le voir, llment Books a disparu du deuxime lment BookParticipant.
Gestion de nuds multiples de mme niveau dans une structure aplatie Dans certaines situations, lorsque vous ralisez une transformation XML, vous savez exactement combien dlments de chaque type vous voulez. Que se passe-t-il si, en plus des lments souhaits, il existe un certain nombre dlments "parasites" de mme niveau pour chaque entre de la source XML ? Supposons que vous disposiez du code XML suivant :

Lallure souhaite de la source XML


<BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <Nickname>Joey</Nickname> <Nickname>Null Pointer</Nickname> </BookParticipant> <BookParticipant type="Editor">

312

LINQ to XML

Partie III

<FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

Supposons que vous vouliez "aplatir" la structure de telle sorte que le nud racine BookParticipants ne contienne que des ensembles dlments FirstName, LastName et NickName et que ces lments ne soient pas inclus dans llment BookParticipant. Le code XML cible devrait avoir lallure suivante : Lallure des donnes XML aprs transformation
<BookParticipants> <! BookParticipant --> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <Nickname>Joey</Nickname> <Nickname>Null Pointer</Nickname> <! BookParticipant --> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipants>

Les commentaires ne sont pas ncessaires, mais ils permettent de mieux cerner les donnes manipules. Sans eux, il serait difcile de savoir si le prnom apparat avant ou aprs le nom de famille. Une lecture rapide des donnes XML pourrait ainsi laisser penser quun certain "Ewan Rattz" fait partie du jeu de donnes. Cet exemple tant plus complexe, nous donnerons des explications chaque fois que cela sera ncessaire (voir Listing 9.11).
Listing 9.11 : Gestion de nuds multiples de mme niveau dans une structure aplatie.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz"), new XElement("Nickname", "Joey"), new XElement("Nickname", "Null Pointer")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML original :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine);

ce point du code, larbre XML source a t construit et afch. Il correspond bien entendu larbre prsent au dbut de cette section. Il ne nous reste plus qu transformer le code source XML :
XDocument xTransDocument = new XDocument( new XElement("BookParticipants", xDocument.Element("BookParticipants") .Elements("BookParticipant")

Chapitre 9

Les autres possibilits de XML

313

Cest ici que lanalyse est intressante. Doit-on utiliser une projection via loprateur Select pour crer un objet dans lequel seront placs les commentaires, le prnom, le nom et le ou les surnoms ? Dans ce cas, quel type dobjet doit-on crer ? Nous pourrions crer un lment, puis ajouter les commentaires, prnoms, noms et surnoms comme lments enfants. Mais cela ajouterait un niveau dans larbre XML. Nous devons donc construire quelque chose qui najoute aucun niveau larbre XML. Un tableau dobjets conviendrait. En effet, en C# 3.0, les tableaux implmentent linterface IEnumerable<T>. Ils fonctionnent donc comme des squences. Lorsquun IEnumerable est pass un constructeur XElement en tant que contenu, la squence est numre et chaque objet de la squence est appliqu llment en cours de construction (voir Chapitre 7). Nous utiliserons la fonctionnalit dinitialisation des collections de C# 3.0 pour remplir ce tableau avec les commentaires, prnoms, noms et surnoms.
.Select(e => new object[] { new XComment(" BookParticipant "), new XElement("FirstName", (string)e.Element("FirstName")), new XElement("LastName", (string)e.Element("LastName")), e.Elements("Nickname")}))); Console.WriteLine("Document XML transform :"); Console.WriteLine(xTransDocument);

ce stade, un tableau contenant un commentaire, un prnom, un nom et autant de surnoms que prsents dans le code XML a t projet. Les deux dernires instructions afchent le document ainsi transform. Cet exemple est complexe. Remarquez que le tableau contient un objet XComment, deux objets XElement et un IEnumerable<XElement>. En le projetant en tant que valeur retourne par loprateur Select, une squence de object[],IEnumerable<object[]> est insre dans llment BookParticipants. Dans ce cas, chacun des objets de cette squence est un tableau dobjets et contient un commentaire, les lments FirstName et LastName et la squence dlments NickName. tant donn quun tableau dobjets ne cre pas un niveau supplmentaire dans larbre XML, les lments du tableau sont simplement ajouts dans llment BookParticipants. Voici les rsultats :
Document XML original : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <Nickname>Joey</Nickname> <Nickname>Null Pointer</Nickname> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants>

314

LINQ to XML

Partie III

Document XML transform : <BookParticipants> <!-- BookParticipant --> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> <Nickname>Joey</Nickname> <Nickname>Null Pointer</Nickname> <!-- BookParticipant --> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipants>

Le document transform respecte exactement les spcications. La partie la plus intressante de ce code est la projection du tableau dobjets (une classe non XML) pour dnir des lments XML en supprimant un niveau dans larbre XML.

Validation
LAPI XML ne serait pas complte si elle ntait pas en mesure de valider le code XML. Comme vous allez le voir, LINQ to XML sait valider un document XML par rapport un schma XML. Les mthodes dextension La validation de donnes XML relve de la classe statique System.Xml.Schema.Extensions, qui met disposition toutes les mthodes de validation ncessaires. Ces mthodes sont implmentes en tant que mthodes dextension. Prototypes Voici quelques-uns des prototypes des mthodes de validation de la classe System.Xml.Schema.Extensions :
System.Xml.Schema.Extensions class: void Extensions.Validate(this XDocument source, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler) void Extensions.Validate(this XDocument source, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler, bool addSchemaInfo) void Extensions.Validate(this XElement source, XmlSchemaObject partialValidationType, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler) void Extensions.Validate(this XElement source, XmlSchemaObject partialValidationType, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler, bool addSchemaInfo) void Extensions.Validate(this XAttribute source, XmlSchemaObject partialValidationType, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler)

Chapitre 9

Les autres possibilits de XML

315

void Extensions.Validate(this XAttribute source, XmlSchemaObject partialValidationType, XmlSchemaSet schemas, ValidationEventHandler validationEventHandler, bool addSchemaInfo)

Chaque mthode admet deux prototypes. Les objets valider peuvent tre de type XDocument, XElement ou XAttribute. Les seconds prototypes ajoutent un argument boolen indiquant si linformation de schma doit tre ajoute au XElement et XAttribute aprs la validation. Les premiers prototypes (ceux sans largument bool) se comportent comme si la valeur false tait affecte largument addSchemaInfo des seconds prototypes. Si vous les utilisez, aucune information de schma nest donc ajoute dans les objets LINQ to XML aprs la validation. Pour obtenir linformation de schma dun objet XElement ou XAttribute, il suft dinvoquer la mthode GetSchemaInfo sur cet objet. Si une information de schma nest pas prsente, cela signie que le premier prototype ou que le second prototype avec un argument addSchemaInfo initialis false a t appel. Dans ce cas, la mthode GetSchemaInfo retourne la valeur null. Si une information de schma est trouve, un objet qui implmente linterface IXmlSchemaInfo est retourn. Cet objet contient une proprit SchemaElement qui retournera un objet XmlSchemaElement et une autre SchemaAttribute qui retournera un objet XmlSchemaAttribute ( condition que llment ou lattribut soit valide). Ces objets pourront tre utiliss pour obtenir des informations complmentaires sur le schma.
ATTENTION Linformation de schma est accessible non pas pendant la validation, mais uniquement lorsque cette phase est termine. Si vous appelez la mthode GetSchemaInfo dans le gestionnaire dvnements de la validation, la valeur null sera retourne.

Les prototypes des mthodes Validate ddies aux XElement et XAttributes demandent un argument XmlSchemaObject. Cela signie que le document doit avoir t valid avant de les appeler. Par ailleurs, si vous passez la valeur null dans largument ValidationEventHandler, une exception de type XmlSchemaValidationException est leve. Cest lapproche la plus simple pour valider un document XML. Obtention dun schma XML Si vous vous intressez la validation de documents XML, il y a de grandes chances pour que vous sachiez ce quest un schma XSD, voire comment le produire. Si vous navez aucune connaissance ce sujet, rassurez-vous : nous allons vous montrer comment laisser lenvironnement .NET grer tout cela votre place (voir Listing 9.12).

316

LINQ to XML

Partie III

Listing 9.12 : Cration dun document XSD partir dun document XML.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); xDocument.Save("bookparticipants.xml"); XmlSchemaInference infer = new XmlSchemaInference(); XmlSchemaSet schemaSet = infer.InferSchema(new XmlTextReader("bookparticipants.xml")); XmlWriter w = XmlWriter.Create("bookparticipants.xsd"); foreach (XmlSchema schema in schemaSet.Schemas()) { schema.Write(w); } w.Close(); XDocument newDocument = XDocument.Load("bookparticipants.xsd"); Console.WriteLine("Schma :"); Console.WriteLine("{0}{1}{1}", newDocument, System.Environment.NewLine);

Les premires lignes crent un (dsormais traditionnel) document XML et lafchent dans la console. Ce document est alors sauvegard sur le disque dur de lordinateur. Le bloc de code suivant instancie un objet XmlSchemaInference et cre un XmlSchemaSet en invoquant la mthode InferSchema sur lobjet XmlSchemaInference. Le bloc suivant cre un objet XmlWriter, numre lensemble des schmas et crit chacun dentre eux dans le chier bookparticipants.xsd. Enn, le dernier bloc de code ouvre le schma XSD ainsi gnr et afche son contenu. Voici les rsultats :
Document XML source : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Schma : <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <xs:element name="BookParticipants"> <xs:complexType> <xs:sequence>

Chapitre 9

Les autres possibilits de XML

317

<xs:element maxOccurs="unbounded" name="BookParticipant"> <xs:complexType> <xs:sequence> <xs:element name="FirstName" type="xs:string" /> <xs:element name="LastName" type="xs:string" /> </xs:sequence> <xs:attribute name="type" type="xs:string" use="required" /> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>

Nous utiliserons la classe XmlSchemaSet ainsi que ce schma XSD (chier bookparticipants.xsd) dans les exemples de validation qui vont suivre. Exemples Dans le premier exemple, nous allons vous montrer la faon la plus simple de valider un document XML. Cette approche sera adopte par de nombreux dveloppeurs. Pour ce faire, nous allons passer la valeur null largument ValidationEventHandler de la mthode Validate (voir Listing 9.13).
Listing 9.13 : Validation dun document XML avec la mthode de validation par dfaut.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("MiddleInitial", "C"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add(null, "bookparticipants.xsd"); try { xDocument.Validate(schemaSet, null); Console.WriteLine("Document validated successfully."); } catch (XmlSchemaValidationException ex) { Console.WriteLine("Une exception a eu lieu : {0}", ex.Message); Console.WriteLine("Le document nest pas valide."); }

Le traditionnel document XML est quelque peu modi : llment MiddleInitial est ajout pour que le document soit intentionnellement invalide. Nous utiliserons le schma qui a t infr dans lexemple prcdent. Dans la seconde ligne en gras, remarquez que le

318

LINQ to XML

Partie III

deuxime argument de la mthode Validate a pour valeur null. Si une erreur est gnre pendant la validation, une exception de type XmlSchemaValidationException sera automatiquement leve. Voici les rsultats :
Document XML source : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <MiddleInitial>C</MiddleInitial> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Une exception a eu lieu : Llment BookParticipant a un lment enfant MiddleInitial invalide. lments attendus : LastName. Le document nest pas valide.

Dans lexemple suivant, nous allons valider le document XML habituel (celui qui a t utilis pour infrer le schma). Puisque le schma a t obtenu partir du document XML que nous souhaitons valider, la validation ne va pas poser de problme. Dans cet exemple, nous allons utiliser la mthode ValidationEventHandler ci-aprs :
static void MyValidationEventHandler(object o, ValidationEventArgs vea) { Console.WriteLine("A validation error occurred processing object type {0}.", o.GetType().Name); Console.WriteLine(vea.Message); throw (new Exception(vea.Message)); }

Ce gestionnaire est vraiment minimaliste. Il se contente dafcher le message derreur et de lever une exception. La gestion des erreurs repose entirement sur cette mthode. Il ntait pas ncessaire de lever une exception : nous aurions pu grer les erreurs dune faon moins grossire, par exemple en ignorant certaines erreurs spciques. Examinons le Listing 9.14, qui utilise la mthode ValidationEventHandler.
Listing 9.14 : Validation russie dun document XML par un schma XSD.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham"))));

Chapitre 9

Les autres possibilits de XML

319

Console.WriteLine("Here is the source XML document:"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add(null, "bookparticipants.xsd"); try { xDocument.Validate(schemaSet, MyValidationEventHandler); Console.WriteLine("Le document est valide."); } catch (Exception ex) { Console.WriteLine("Une exception a t gnre : {0}", ex.Message); Console.WriteLine("Le document nest pas valide."); }

Aprs avoir cr et afch le document XML, ce code instancie un objet XmlSchemaSet et y ajoute le schma infr bookparticipants.xsd laide de la mthode Add. Le dernier bloc de donnes applique la mthode dextension Validate au document en lui passant le schma et le gestionnaire dvnements de la validation. Pour des raisons de scurit, lappel la mthode Validate a t ralis lintrieur dun bloc try/ catch. Voici les rsultats :
Document source : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Le document est valide.

Nous allons maintenant donner un exemple de document non valide (voir Listing 9.15).
Listing 9.15 : chec dans la validation dun document XML par un schma XSD.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); XmlSchemaSet schemaSet = new XmlSchemaSet();

320

LINQ to XML

Partie III

schemaSet.Add(null, "bookparticipants.xsd"); try { xDocument.Validate(schemaSet, MyValidationEventHandler); Console.WriteLine("Le document est valide."); } catch (Exception ex) { Console.WriteLine("Une exception a t leve: {0}", ex.Message); Console.WriteLine("Le document nest pas valide."); }

Ce code est identique celui de lexemple prcdent, ceci prs que nous avons ajout lattribut language dans le premier lment BookParticipant. Le schma ne faisant pas rfrence cet attribut, le document XML nest pas valide. Voici les rsultats :
Document source : <BookParticipants> <BookParticipant type="Author" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Une erreur de validation sest produite pendant le traitement dun XAttribute. Lattribut language nest pas dclar. Une exception a t leve: Lattribut language nest pas dclar. Le document nest pas valide.

Le document XML nest pas valide. Dans les deux exemples prcdents, nous avons cr la mthode MyValidationEventHandler pour grer les vnements lis la validation. Rappelez-vous, C# 2.0 a introduit les mthodes anonymes et C# 3.0, les expressions lambda. Le Listing 9.16 est identique au prcdent mais, ici, nous utilisons une expression lambda la place de la mthode nomme ValidationEventHandler.
Listing 9.16 : chec dans la validation dun document XML par un schma XSD en utilisant une expression lambda.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XAttribute("language", "English"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine);

Chapitre 9

Les autres possibilits de XML

321

XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add(null, "bookparticipants.xsd"); try { xDocument.Validate(schemaSet, (o, vea) => { Console.WriteLine( "Une erreur de validation sest produite sur un objet de type {0}.", o.GetType().Name); Console.WriteLine(vea.Message); throw (new Exception(vea.Message)); }); Console.WriteLine("Document validated successfully."); } catch (Exception ex) { Console.WriteLine("Exception occurred: {0}", ex.Message); Console.WriteLine("Document validated unsuccessfully."); }

Dans ce listing, lexpression lambda est, en fait, une mthode complte. Voici les rsultats :
Document source : <BookParticipants> <BookParticipant type="Author" language="English"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Une erreur de validation sest produite sur un objet de type XAttribute. Lattribut language nest pas dclar. Une exception a t leve: Lattribut language nest pas dclar. Le document nest pas valide.

Nous allons reprendre le code de lexemple prcdent mais, cette fois-ci, nous ajouterons linformation du schma (voir Listing 9.17).
Listing 9.17 : chec dans la validation dun document XML par un schma XSD en utilisant une expression lambda et en ajoutant linformation du schma.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("MiddleName", "Carson"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML source :");

322

LINQ to XML

Partie III

Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add(null, "bookparticipants.xsd"); xDocument.Validate(schemaSet, (o, vea) => { Console.WriteLine("Une exception sest produite pendant le traitement dun objet de type {0}.", o.GetType().Name); Console.WriteLine("{0}{1}", vea.Message, System.Environment.NewLine); }, true); foreach(XElement element in xDocument.Descendants()) { Console.WriteLine("Element {0} est {1}", element.Name, element.GetSchemaInfo().Validity); XmlSchemaElement se = element.GetSchemaInfo().SchemaElement; if (se != null) { Console.WriteLine( "Llment du schma {0} doit avoir MinOccurs = {1} et MaxOccurs = {2}{3}", se.Name, se.MinOccurs, se.MaxOccurs, System.Environment.NewLine); } else { // Les lments non valides nont pas dlment SchemaElement Console.WriteLine(); } }

Cet exemple commence comme le prcdent. Il cre un document XML mais, cette fois-ci, llment MiddleName est ajout au premier lment BookParticipant. Cet lment nest pas valide, puisquil nest pas spci dans le schma XSD. Contrairement lexemple prcdent, la mthode Validate ajoute les informations du schma dans lobjet schemaSet et ne lve aucune exception. Comme il a t dit prcdemment, le processus de validation doit en effet tre termin pour que les informations du schma soient accessibles. Le gestionnaire dvnements ne peut donc pas lever des exceptions durant cette phase. Pour les mmes raisons, le bloc try catch a t supprim. Lorsque la validation est termine, tous les lments du document sont numrs, et, pour chacun dentre eux, une information indiquant leur validit est afche. Lobjet SchemaElement est obtenu partir des informations de schma stockes dans lobjet schemaSet ltape prcdente. Une instruction if teste la proprit XmlSchemaElement se. Une valeur null signie que llment nest pas valide. Dans ce cas, aucune information de schma ne peut tre afche. Dans le cas contraire, le nom du XmlSchemaElement ainsi que les proprits MinOccurs et MaxOccurs sont afchs. La mme technique pourrait tre applique la proprit SchemaAttribute pour dtecter les attributs non valides.

Chapitre 9

Les autres possibilits de XML

323

Voici les rsultats :


Document XML source : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <MiddleName>Carson</MiddleName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Une exception sest produite pendant le traitement dun objet de type XElement. Llment BookParticipant a un lment enfant non valide : MiddleName. lments attendus possibles : LastName. Llment BookParticipants nest pas valide Llment BookParticipants doit avoir MinOccurs = 1 et MaxOccurs = 1 Llment BookParticipant nest pas valide Llment BookParticipants doit avoir MinOccurs = 1 et MaxOccurs = 79228162514264337593543950335 Llment FirstName est valide Llment FirstName doit avoir MinOccurs = 1 et MaxOccurs = 1 Llment MiddleName nest pas valide Llment LastName nest pas connu Llment BookParticipant est valide Llment BookParticipant doit avoir MinOccurs = 1 et MaxOccurs = 79228162514264337593543950335 Llment FirstName est valide Llment FirstName doit avoir MinOccurs = 1 et MaxOccurs = 1 Llment LastName est valide Llment LastName doit avoir MinOccurs = 1 et MaxOccurs = 1

Ces informations nont rien de surprenant. Remarquez que la proprit MaxOccurs de llment BookParticipant a une trs grande valeur. Ceci est d au fait que, dans le schma, lattribut maxOccurs a t initialis la valeur "unbounded". Pour les deux derniers exemples de validation, nous utiliserons un des prototypes de la mthode Validate ddi la validation des lments. Vous remarquerez sans peine que ces prototypes ncessitent un argument de type XmlSchemaObject. Cela signie que le document doit avoir t valid au pralable. Ce scnario concerne les situations o la validation a t effectue une premire fois, mais o il est ncessaire de revalider une partie de larbre XML. Nous allons supposer quun document XML est charg et valid et que, par la suite, un utilisateur modie les donnes concernant un participant. Le document XML doit donc tre mis jour pour reter ces modications, et la portion darbre XML correspondante

324

LINQ to XML

Partie III

doit tre valide. Cest ici que les mthodes Validate rserves aux attributs et aux lments se rvlent trs pratiques. Cet exemple (voir Listing 9.18) tant assez complexe, nous donnerons des explications chaque fois que cela sera ncessaire. Plutt que charger le schma habituel depuis un chier, nous allons dnir un nouveau schma, lgrement diffrent des prcdents, an de faciliter ldition de larbre XML.
Listing 9.18 : Validation russie dun lment XML.
string schema = @"<?xml version=1.0 encoding=utf-8?> <xs:schema attributeFormDefault=unqualified elementFormDefault=qualified xmlns:xs=http://www.w3.org/2001/XMLSchema> <xs:element name=BookParticipants> <xs:complexType> <xs:sequence> <xs:element maxOccurs=unbounded name=BookParticipant> <xs:complexType> <xs:sequence> <xs:element name=FirstName type=xs:string /> <xs:element minOccurs=0 name=MiddleInitial type=xs:string /> <xs:element name=LastName type=xs:string /> </xs:sequence> <xs:attribute name=type type=xs:string use=required /> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>"; XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add("", XmlReader.Create(new StringReader(schema)));

Ce schma est lgrement diffrent de celui utilis dans les autres exemples. Ici, les guillemets de dlimitation sont remplacs par des apostrophes et llment MiddleInitial est ajout, entre les lments FirstName et LastName. Remarquez galement que lattribut minOccurs de llment MiddleInitial a t initialis "0". Cet lment nest donc pas obligatoire. Les deux dernires lignes crent un objet schemaSet en utilisant les donnes du schma. La prochaine tape va consister crer un document XML.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"), new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Le document XML source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine);

Chapitre 9

Les autres possibilits de XML

325

Rien de nouveau dans ce code : le document XML habituel est cr et afch. Nous allons maintenant le valider :
bool valid = true; xDocument.Validate(schemaSet, (o, vea) => { Console.WriteLine("Une exception sest produite pendant le traitement de lobjet {0}.", o.GetType().Name); Console.WriteLine(vea.Message); valid = false; }, true); Console.WriteLine("Le document {0}.{1}", valid ? "est valide" : "nest pas valide", System.Environment.NewLine);

La validation est lgrement diffrente de celle utilise dans les exemples prcdents. Une variable boolenne indiquant si le document est valide est initialise la valeur true. lintrieur du gestionnaire de validation, elle est initialise la valeur false. Ainsi, si une erreur de validation se produit, valid aura pour valeur false. La valeur de la variable est teste pour dterminer si le document est valide et un message correspondant est afch. Arriv ce point dans lexcution du code, le document est valide. Imaginons maintenant que nous autorisons un utilisateur diter les lments des diffrents participants. Ici, par exemple, lutilisateur dite le participant dont le prnom est "Joe". Le code obtient une rfrence de cet lment, le met jour et effectue une validation aprs la mise jour.
XElement bookParticipant = xDocument.Descendants("BookParticipant"). Where(e => ((string)e.Element("FirstName")).Equals("Joe")).First(); bookParticipant.Element("FirstName"). AddAfterSelf(new XElement("MiddleInitial", "C")); valid = true; bookParticipant.Validate(bookParticipant.GetSchemaInfo().SchemaElement, schemaSet, (o, vea) => { Console.WriteLine("An exception occurred processing object type {0}.", o.GetType().Name); Console.WriteLine(vea.Message); valid = false; }, true); Console.WriteLine("Llment {0}.{1}", valid ? "est valide" : "nest pas valide", System.Environment.NewLine);

La variable valid est initialise true, puis la mthode Validate est appele sur llment BookParticipant (et non sur le document complet). lintrieur du gestionnaire

326

LINQ to XML

Partie III

dvnement de validation, valid est initialise true. Aprs ltape de validation du participant, la validit de llment est afche. Voici les rsultats :
Le document XML source <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Le document est valide Llment est valide

Dans cet exemple, llment a t considr comme valide. Dans notre dernier exemple, nous allons utiliser le mme code mais, ici, pendant la mise jour de llment BookParticipant, nous allons crer un lment MiddleName, et non MiddleInitial. Llment sera donc considr comme invalide (voir Listing 9.19).
Listing 9.19 : chec de validation dun lment XML.
string schema = @"<?xml version=1.0 encoding=utf-8?> <xs:schema attributeFormDefault=unqualified elementFormDefault=qualified xmlns:xs=http://www.w3.org/2001/XMLSchema> <xs:element name=BookParticipants> <xs:complexType> <xs:sequence> <xs:element maxOccurs=unbounded name=BookParticipant> <xs:complexType> <xs:sequence> <xs:element name=FirstName type=xs:string /> <xs:element minOccurs=0 name=MiddleInitial type=xs:string /> <xs:element name=LastName type=xs:string /> </xs:sequence> <xs:attribute name=type type=xs:string use=required /> </xs:complexType> </xs:element> </xs:sequence> </xs:complexType> </xs:element> </xs:schema>"; XmlSchemaSet schemaSet = new XmlSchemaSet(); schemaSet.Add("", XmlReader.Create(new StringReader(schema))); XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"),

Chapitre 9

Les autres possibilits de XML

327

new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); Console.WriteLine("Document XML source :"); Console.WriteLine("{0}{1}{1}", xDocument, System.Environment.NewLine); bool valid = true; xDocument.Validate(schemaSet, (o, vea) => { Console.WriteLine("Une exception sest produite pendant la validation dun objet de type {0}.", o.GetType().Name); Console.WriteLine(vea.Message); valid = false; }, true); Console.WriteLine("Le document {0}.{1}", valid ? "est valide" : "nest pas valide", System.Environment.NewLine); XElement bookParticipant = xDocument.Descendants("BookParticipant"). Where(e => ((string)e.Element("FirstName")).Equals("Joe")).First(); bookParticipant.Element("FirstName"). AddAfterSelf(new XElement("MiddleName", "Carson")); valid = true; bookParticipant.Validate(bookParticipant.GetSchemaInfo().SchemaElement, schemaSet, (o, vea) => { Console.WriteLine("Une exception sest produite pendant la validation dun objet de type {0}.", o.GetType().Name); Console.WriteLine(vea.Message); valid = false; }, true); Console.WriteLine("Llment {0}.{1}", valid ? "est valide" : "nest pas valide", System.Environment.NewLine);

Ce code est identique au prcdent mais, ici, au lieu dajouter un lment MiddleInitial, nous ajoutons un lment MiddleName. Voici les rsultats :
Document XML source : <BookParticipants> <BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant> <BookParticipant type="Editor"> <FirstName>Ewan</FirstName> <LastName>Buckingham</LastName> </BookParticipant> </BookParticipants> Le document est valide

328

LINQ to XML

Partie III

Une exception sest produite pendant la validation dun objet de type XElement. Llment BookParticipant a un enfant non valide : MiddleName. lments attendus : MiddleInitial, LastName. Llment nest pas valide

Comme on sy attendait, llment BookParticipant nest pas valide. Cet exemple est quelque peu irraliste. En effet, il est peu probable quun dveloppeur dnisse une interface pour que des utilisateurs puissent modier un document XML. Mais imaginez que le document XML passe entre les mains dun programmeur qui cherche personnellement vous nuire (un hacker, par exemple). Dans ce cas, la revalidation des donnes prend tout son sens

XPath
Si vous utilisez couramment XPath, vous pouvez tirer avantage de la classe System.Xml.XPath.Extensions de lespace de noms System.Xml.XPath.Extensions. Cette classe ajoute la possibilit de faire des recherches XPath par lintermdiaire de mthodes dextension. Prototypes Voici la liste des principaux System.Xml.XPath.Extensions : prototypes des mthodes de la classe

XPathNavigator Extensions.CreateNavigator(this XNode node); XPathNavigator Extensions.CreateNavigator(this XNode node, XmlNameTable nameTable); object Extensions.XPathEvaluate(this XNode node, string expression); object Extensions.XPathEvaluate(this XNode node, string expression, IXmlNamespaceResolver resolver); XElement Extensions.XPathSelectElement(this XNode node, string expression); XElement Extensions.XPathSelectElement(this XNode node, string expression, IXmlNamespaceResolver resolver); IEnumerable<XElement> Extensions.XPathSelectElements(this XNode node, string expression); IEnumerable<XElement> Extensions.XPathSelectElements(this XNode node, string expression, IXmlNamespaceResolver resolver);

En utilisant ces mthodes dextension, vous pouvez appliquer une requte sur un document LINQ to XML en utilisant les expressions de recherche XPath (voir Listing 9.20).
Listing 9.20 : Interrogation de donnes XML avec la syntaxe XPath.
XDocument xDocument = new XDocument( new XElement("BookParticipants", new XElement("BookParticipant", new XAttribute("type", "Author"), new XElement("FirstName", "Joe"), new XElement("LastName", "Rattz")), new XElement("BookParticipant", new XAttribute("type", "Editor"),

Chapitre 9

Les autres possibilits de XML

329

new XElement("FirstName", "Ewan"), new XElement("LastName", "Buckingham")))); XElement bookParticipant = xDocument.XPathSelectElement( "//BookParticipants/BookParticipant[FirstName=Joe]"); Console.WriteLine(bookParticipant);

Ces quelques lignes de code dnissent le document XML conventionnel mais, contrairement ce qui a t fait dans les exemples prcdents, le document original nest pas afch. La mthode XPathSelectElement est appele sur le document. Une expression de recherche XPath lui est passe en argument an de trouver llment BookParticipant dont llment FirstName a pour valeur "Joe". Voici les rsultats :
<BookParticipant type="Author"> <FirstName>Joe</FirstName> <LastName>Rattz</LastName> </BookParticipant>

Les mthodes dextension XPath donnent une rfrence sur un objet System.Xml.XPath.XPathNavigator. Par son intermdiaire, vous pouvez parcourir un document XML, excuter une requte XPath pour obtenir un lment ou une squence dlments ou valuer une expression de requte XPath.

Rsum
Arriv ce point dans la lecture de cet ouvrage, si vous naviez aucune exprience en XML, vous vous sentez peut-tre dpass. Si vous aviez une exprience en XML, mais pas en LINQ to XML, jespre que vous avez pu comprendre tout ce qui a t dit. La puissance et la exibilit de lAPI LINQ to XML est vraiment grisante ! Pendant lcriture de ce chapitre et des exemples qui le ponctuent, je me suis trouv dans un tel tat deuphorie que je nai jamais eu envie de faire "machine arrire" et dutiliser le langage XML traditionnel. Et ce malgr le fait que mon projet professionnel ntait pas encore en mesure dutiliser LINQ to XML. Bien des fois, jai pens "si seulement je pouvais utiliser la construction fonctionnelle pour dnir un fragment XML", mais jai d me replier sur la mthode String.Format de la librairie XML traditionnelle. Ne me jetez pas la pierre : comme je lai dit prcdemment, un prsentateur a utilis les mmes mthodes que moi lors dun sminaire Microsoft ! Aprs avoir crit les exemples des chapitres relatifs LINQ to XML, je peux vous dire que je serais vraiment intress si je pouvais utiliser lAPI LINQ to XML dans mon code de production. La cration de documents XML est grandement facilite, car elle est essentiellement base sur les lments (et non les documents) et quelle tire parti des normes possibilits de la construction fonctionnelle. Le processus peut mme se rvler amusant : combinez la facilit de cration, le parcours et la modication intuitifs de

330

LINQ to XML

Partie III

documents XML, et cela devient un vrai plaisir dutiliser LINQ to XML surtout si lon considre les autres alternatives ! Ces facilits dutilisation ainsi que la puissance et la exibilit du langage dinterrogation font de LINQ to XML mon prfr dans le petit monde de LINQ. Si votre approche de XML est plutt difcile, vous devriez vous intresser LINQ to XML. Il fera certainement sauter bien des barrires.

IV
LINQ to DataSet

10
LINQ to DataSet
Bien que LINQ to SQL nait pas encore t abord dans cet ouvrage, je voudrais signaler que, pour utiliser LINQ to SQL sur une base de donnes, les classes de code source doivent tre gnres et compiles spciquement pour cette base de donnes, ou quun chier de mapping doit tre cr. Cela signie quil est impossible deffectuer des requtes LINQ to SQL sur une base de donnes inconnue jusqu lexcution. Mais alors que doit faire le dveloppeur ? Les oprateurs LINQ to DataSet permettent dexcuter des requtes LINQ sur des DataSet. tant donn quun DataSet peut tre rcupr par une requte SQL ADO.NET, LINQ to DataSet permet deffectuer des requtes sur toute base de donnes accessible via ADO.NET. Cela offre un dynamisme bien plus grand que si vous utilisiez LINQ to SQL. Vous pouvez vous demander dans quelles circonstances la base de donnes pourrait ne pas tre connue jusqu lexcution. Effectivement, dans les applications traditionnelles, la base de donnes est connue pendant le dveloppement, et LINQ to DataSet nest pas un passage oblig. Mais quen est-il si vous dveloppez un utilitaire pour bases de donnes ? Considrons une application telle que SQL Server Enterprise Manager (linterface graphique de SQL Server pour les tches de cration et dadministration des bases de donnes). Jusqu lexcution, cette application ne connat pas les bases de donnes qui ont t installes. Cependant, elle vous permet de connatre leur nom ainsi que celui des diffrentes tables accessibles dans chacune dentre elles. Le dveloppeur dune telle application na aucun moyen de gnrer les classes LINQ to SQL ncessaires linterfaage des diffrentes bases de donnes lexcution. LINQ to DataSet devient donc une ncessit. Bien que ce chapitre soit intitul "LINQ to DataSet", vous verrez que les oprateurs passs en revue sont essentiellement relatifs aux objets DataTable, DataRow et DataColumn. Ne soyez pas surpris si ce chapitre ne fait pas souvent rfrence aux objets DataSets. Il est bien entendu quen circonstances relles vos objets DataTable viendront

334

LINQ to DataSet

Partie IV

essentiellement dobjets DataSets. Cependant, pour des raisons dindpendance, de concision et de clart, la plupart des exemples de ce chapitre se basent sur de simples objets DataTable crs par programme. Les donnes traites ne sont donc pas extraites dune base de donnes existante. LINQ to DataSet donne accs plusieurs oprateurs spciques issus de diffrents assemblies et espaces de noms. Ces oprateurs permettent au dveloppeur deffectuer les actions suivantes :
m m m

dnitions de squences dobjets DataRows; recherche et modication de valeurs DataColumn; obtention de squences LINQ standard IEnumerable<T> partir de DataTable an de pouvoir leur appliquer des oprateurs de requte standard ; copie de squences de DataRow modies dans un DataTable.

Outre ces oprateurs LINQ to DataSet, une fois loprateur AsEnumerable appel, vous pouvez utiliser les oprateurs de requte standard de LINQ to Objects sur la squence DataRow retourne, ce qui ajoute encore plus de puissance et de exibilit LINQ to DataSet.

Rfrence des assemblies


Pour excuter les exemples de ce chapitre, vous devrez (si elles ne sont pas dj prsentes) ajouter des rfrences aux assemblies System.Data.dll et System.Data.DataSetExtensions.dll.

Espaces de noms rfrencs


Pour tre en mesure dutiliser les oprateurs LINQ to DataSet, vous devez ajouter (si elles ne sont pas dj prsentes) les deux directives using suivantes en tte de votre code :
using System.Data; using System.Linq;

Code commun utilis dans les exemples


Tous les exemples de ce chapitre ont besoin dun objet DataTable pour effectuer des requtes LINQ to DataSet. Dans un code de production rel, ces objets sont typiquement obtenus en effectuant une requte sur une base de donnes. Dans plusieurs exemples de ce chapitre, cette conguration est inconfortable, voire insufsante. titre dexemple, nous aurons besoin de deux enregistrements identiques pour illustrer la mthode Distinct. Plutt que jongler avec une base de donnes pour obtenir les enregistrements

Chapitre 10

LINQ to DataSet

335

ncessaires, nous avons prfr crer par programme un objet DataTable qui contient les donnes ncessaires chaque exemple. Pour faciliter la dnition de lobjet DataTable, nous utiliserons un tableau dobjets contenu dans la classe prdnie Student. Une classe simpliste avec deux membres publics
class Student { public int Id; public string Name; }

Vous navez qu imaginer que nous interrogeons la table Students, compose de deux colonnes (Id et Name) et dans laquelle chaque enregistrement reprsente un tudiant. Pour faciliter la cration du DataTable et pour ne pas nuire aux dtails de chaque exemple, nous utiliserons une mthode commune pour convertir un tableau dobjets Student en un objet DataTable. Ceci nous permettra de faire varier simplement les donnes dun exemple lautre. Voici le code de cette mthode commune : Conversion dun tableau dobjets Student en un DataTable
static DataTable GetDataTable(Student[] students) { DataTable table = new DataTable(); table.Columns.Add("Id", typeof(Int32)); table.Columns.Add("Name", typeof(string)); foreach (Student student in students) { table.Rows.Add(student.Id, student.Name); } return (table); }

Cette mthode na rien de bien compliqu. Elle se contente dinstancier un objet DataTable, puis dajouter deux colonnes et une ligne pour chacun des lments du tableau students pass en argument. Pour plusieurs des exemples de ce chapitre, il est ncessaire dafcher un objet DataTable, pour sassurer que les rsultats sont conformes aux attentes. Dun exemple lautre, les donnes du DataTable peuvent varier, mais le code permettant dafcher le contenu du DataTable reste le mme. Plutt que rpter ce code dans tous les exemples, nous avons cr une mthode commune que nous appellerons chaque fois que cela sera ncessaire : La mthode OutputDataTableHeader
static void OutputDataTableHeader(DataTable dt, int columnWidth) { string format = string.Format("{0}0,-{1}{2}", "{", columnWidth, "}"); // Display the column headings.

336

LINQ to DataSet

Partie IV

foreach(DataColumn column in dt.Columns) { Console.Write(format, column.ColumnName); } Console.WriteLine(); foreach(DataColumn column in dt.Columns) { for(int i = 0; i < columnWidth; i++) { Console.Write("="); } } Console.WriteLine(); }

Cette mthode afche len-tte dun objet DataTable sous une forme tabulaire.

Oprateurs ddis aux DataRow


Vous vous souvenez certainement que lAPI LINQ to Objects contient un ensemble doprateurs de requte standard trs utiles lorsquil sagit dinitialiser et/ou de comparer des squences. Je fais rfrence aux oprateurs Distinct, Except, Intersect, Union et SequenceEqual, qui dnissent une squence en fonction dune autre. Chacun de ces oprateurs doit tre en mesure de tester lgalit des lments dune squence pour effectuer lopration pour laquelle il a t conu. Le test dgalit se fait en appliquant les mthodes GetHashCode et Equals aux lments. En ce qui concerne les DataRow, ces deux mthodes provoquent la comparaison des rfrences des lments, ce qui nest pas le comportement souhait. Mais, rassurez-vous, ces oprateurs possdent un autre prototype dont nous navons pas parl dans les chapitres relatifs LINQ to Objects. Ce prototype permet de passer un argument complmentaire : IEqualityComparer. Par commodit, un objet comparateur a t spcialement dni pour ces versions des oprateurs : System.Data.DataRowComparer.Default. Cette classe se trouve dans lespace de noms System.Data et lassembly System.Data.Entity.dll. Lgalit est dtermine en comparant le nombre de colonnes et le type de donne statique de chaque colonne et en utilisant linterface IComparable sur le type de donne dynamique de la colonne si celui-ci limplmente. Dans le cas contraire, la mthode System.Object Equals est appele. Ces prototypes sont dnis dans la mme classe statique que les autres : System.Linq.Enumerable. Dans cette section, nous allons donner quelques exemples pour illustrer la mauvaise et, bien entendu, la bonne faon deffectuer des comparaisons sur des objets DataSet. Oprateur Distinct Loprateur Distinct supprime les lignes en double dans une squence dobjets. Il retourne un objet dont lnumration renvoie la squence source prive des doublons.

Chapitre 10

LINQ to DataSet

337

Cet oprateur devrait pouvoir dterminer lgalit entre les diffrentes lignes en appelant les mthodes GetHashCode et Equals sur chacun des lments. Cependant, pour des objets de type DataRow, cette technique ne donne pas le rsultat recherch. Pour obtenir le rsultat escompt, nous appellerons un nouveau prototype de cet oprateur et nous lui passerons le comparateur System.Data.DataRowComparer.Default dans son deuxime argument. La comparaison est effectue sur chacune des colonnes dune ligne. Elle se base sur le type de donne statique de chaque colonne. Linterface IComparable est utilise sur les colonnes qui implmentent cette interface. La mthode statique System.Object.Equals est utilise sur les autres.
Prototype Un seul prototype de loprateur Distinct sera tudi dans ce chapitre :
public static IEnumerable<T> Distinct<T> ( this IEnumerable<T> source, IEqualityComparer<T> comparer);

Exemples Dans le premier exemple, lobjet DataTable sera cr en appliquant la mthode commune GetDataTable un tableau dobjet Student. dessein, ce tableau comprendra deux fois la mme ligne : celle dont le champ Id vaut 1. Pour mettre en vidence la ligne en double dans le DataTable, le tableau sera afch. La ligne en double sera ensuite enleve laide de loprateur Distinct, et lobjet DataTable sera nouveau afch, pour montrer que le doublon a t supprim. Le code utilis apparat dans le Listing 10.1.
Listing 10.1 : Loprateur Distinct associ un comparateur dgalit.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = = = = = { 1, Name = "Joe Rattz" }, 6, Name = "Ulyses Hutchens" }, 19, Name = "Bob Tanko" }, 45, Name = "Erin Doutensal" }, 1, Name = "Joe Rattz" }, 12, Name = "Bob Mapplethorpe" }, 17, Name = "Anthony Adams" }, 32, Name = "Dignan Stephens" }

DataTable dt = GetDataTable(students); Console.WriteLine("{0}Avant lappel Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15);

338

LINQ to DataSet

Partie IV

foreach (DataRow dataRow in dt.Rows) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } IEnumerable<DataRow> distinct = dt.AsEnumerable().Distinct(DataRowComparer.Default); Console.WriteLine("{0}Aprs lappel Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in distinct) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); }

Loprateur AsEnumerable a t utilis pour obtenir la squence dobjets DataRow partir du DataTable. Ceci an dassurer la compatibilit avec loprateur Distinct. Remarquez galement que, dans le tableau students, la ligne dont le champ Id vaut "1" apparat en double. Vous avez sans doute not que la mthode Field a t appele sur lobjet DataRow. Pour linstant, tout ce que vous devez en savoir, cest quil sagit dune mthode qui facilite lobtention des valeurs des objets DataColumn partir dun DataRow. Loprateur Field<T> sera tudi en dtail un peu plus loin dans ce chapitre, dans la section "Oprateurs ddis aux champs". Voici les rsultats :
Avant lappel Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal 1 Joe Rattz 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens Aprs lappel Distinct() Id Name ============================== 1 Joe Rattz 6 aUlyses Hutchens 19 Bob Tanko 45 Erin Doutensal 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens

Chapitre 10

LINQ to DataSet

339

Comme vous le voyez, la ligne dont le champ Id vaut 1 apparat en double avant lappel loprateur Distinct. Elle napparat plus quune seule fois lorsque cet oprateur a t appel. Dans notre deuxime exemple, nous allons voir ce qui se passerait si loprateur Distinct avait t appel sans spcier lobjet comparer (voir Listing 10.2).
Listing 10.2 : Loprateur Distinct appel sans comparateur dgalit.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = = = = = { 1, Name = "Joe Rattz" }, 6, Name = "Ulyses Hutchens" }, 19, Name = "Bob Tanko" }, 45, Name = "Erin Doutensal" }, 1, Name = "Joe Rattz" }, 12, Name = "Bob Mapplethorpe" }, 17, Name = "Anthony Adams" }, 32, Name = "Dignan Stephens" }

DataTable dt = GetDataTable(students); Console.WriteLine("{0}Avant lappel Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in dt.Rows) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } IEnumerable<DataRow> distinct = dt.AsEnumerable().Distinct(); Console.WriteLine("{0}Aprs lappel Distinct(){0}", System.Environment.NewLine); OutputDataTableHeader(dt, 15); foreach (DataRow dataRow in distinct) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); }

La seule diffrence entre ce code et le prcdent se situe au niveau de loprateur Distinct : dans le premier cas, on utilise un comparateur dgalit, dans le second cas, non. Cette deuxime technique va-t-elle supprimer le doublon ? Jetons un il aux rsultats :
Avant lappel Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal

340

LINQ to DataSet

Partie IV

1 12 17 32

Joe Rattz Bob Mapplethorpe Anthony Adams Dignan Stephens

Aprs lappel Distinct() Id Name ============================== 1 Joe Rattz 6 Ulyses Hutchens 19 Bob Tanko 45 Erin Doutensal 1 Joe Rattz 12 Bob Mapplethorpe 17 Anthony Adams 32 Dignan Stephens

Ces rsultats ne sont pas concluants. Comme vous le voyez, la deuxime technique de comparaison est inefcace. Oprateur Except Loprateur Except renvoie une squence compose des objets DataRow de la premire squence qui nappartiennent pas la seconde. Les lments de la squence de sortie apparaissent dans lordre original de la squence dentre. Pour dterminer quels lments sont uniques, loprateur Except doit tre en mesure de dterminer si deux lments sont gaux. Pour ce faire, il devrait sufre dutiliser les mthodes GetHashCode et Equals. Cependant, tant donn que les objets compars sont des DataRow, un prototype spcique doit tre utilis. Le deuxime argument de ce prototype dsigne le comparateur (System.Data.DataRowComparer.Default) utiliser. La comparaison est effectue sur chacune des colonnes dune ligne. Elle se base sur le type de donne statique de chaque colonne. Linterface IComparable est utilise sur les colonnes qui limplmentent. La mthode statique System.Object.Equals est utilise sur les autres.
Prototype Nous nous intresserons un seul prototype de cet oprateur :
public static IEnumerable<T> Except<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer);

Exemple Dans cet exemple, nous appellerons loprateur Except deux reprises. Dans le premier appel, le comparateur pass sera System.Data.DataRowComparer.Default. Les rsultats de la comparaison devraient donc tre conformes aux attentes. Dans le

Chapitre 10

LINQ to DataSet

341

second appel, aucun comparateur ne sera pass au prototype. Comme vous le verrez, la comparaison ne fonctionnera pas (voir Listing 10.3).
Listing 10.3 : Appel de loprateur Except avec et sans comparateur.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> except = seq1.Except(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Rsultats de loprateur Except() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in except) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } except = seq1.Except(seq2); Console.WriteLine("{0}Rsultats de loprateur Except() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in except) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); }

Cet exemple cre deux objets DataTable et les remplit avec les donnes stockes dans les tableaux Student. La mthode AsEnumerable est alors appele pour transformer les deux objets DataTable en squences. Enn, loprateur Except est appel sur les deux squences et les rsultats sont afchs. Comme vous pouvez le voir, le premier appel loprateur Except transmet le comparateur System.Data.DataRowComparer.Default dans le deuxime argument. Le second appel ne transmet aucun comparateur.

342

LINQ to DataSet

Partie IV

Voici les rsultats afchs lors de lappui sur Ctrl+F5 :


Rsultats de loprateur Except() avec le comparateur Id Name ============================== 1 Joe Rattz 13 Stacy Sinclair Rsultats de loprateur Except() sans le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens

Comme vous pouvez le voir, seul le premier appel loprateur Except a t en mesure de comparer de faon correcte les donnes des deux squences. Oprateur Intersect Loprateur Intersect renvoie une squence dobjets DataRow qui reprsente lintersection des deux squences DataRow passes en entre. La squence de sortie contient les lments uniques des deux squences dentre, lists dans leur ordre dapparition original. Pour dterminer quels lments sont uniques, loprateur Intersect doit tre en mesure de dterminer si deux lments sont gaux. Pour ce faire, il devrait lui sufre dutiliser les mthodes GetHashCode et Equals. Cependant, tant donn que les objets compars sont de type DataRow, un prototype spcique doit tre utilis. Le deuxime argument de ce prototype dsigne le comparateur (System.Data.DataRowComparer.Default) utiliser. La comparaison est effectue sur chacune des colonnes dune ligne. Elle se base sur le type de donne statique de chaque colonne. Linterface IComparable est utilise sur les colonnes qui implmentent cette interface. La mthode statique System.Object.Equals est utilise sur les autres colonnes.
Prototype Nous nous intresserons un seul prototype de cet oprateur :
public static IEnumerable<T> Intersect<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer);

Exemple Nous utiliserons le mme code que dans lexemple de loprateur Except mais, ici, cest loprateur Intersect qui sera appel (voir Listing 10.4).

Chapitre 10

LINQ to DataSet

343

Listing 10.4 : Appel de loprateur Intersect avec et sans comparateur.


Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> intersect = seq1.Intersect(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Rsultats de loprateur Intersect() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in intersect) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } intersect = seq1.Intersect(seq2); Console.WriteLine("{0}Rsultats de loprateur Intersect() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in intersect) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); }

Rien de nouveau dans ce code : deux objets DataTable sont crs et initialiss avec les donnes des tableaux Student. Ils sont ensuite convertis en squences, puis loprateur Intersect leur est appliqu, avec puis sans comparateur. Les rsultats sont afchs aprs chaque appel loprateur Intersect.

344

LINQ to DataSet

Partie IV

Voici les informations afches suite lappui sur Ctrl+F5 :


Rsultats de loprateur Intersect() avec le comparateur Id Name ============================== 7 Anthony Adams 72 Dignan Stephens Rsultats de loprateur Intersect() sans le comparateur Id Name ==============================

Comme vous pouvez le voir, seul le premier appel loprateur Intersect a t en mesure de comparer de faon correcte les donnes des deux squences. Oprateur Union Loprateur Union renvoie une squence dobjets DataRow qui reprsente la runion des deux squences DataRow passes en entre. La squence de sortie contient les lments de la premire squence suivis des lments de la seconde squence qui nont pas dj t cits. Pour dterminer quels lments ont dj t slectionns dans la premire squence, loprateur Union doit tre en mesure de dterminer si deux lments sont gaux. Pour ce faire, il devrait lui sufre dutiliser les mthodes GetHashCode et Equals. Cependant, tant donn que les objets compars sont de type DataRow, un prototype spcique doit tre utilis. Le deuxime argument de ce prototype dsigne le comparateur (System.Data.DataRowComparer.Default) utiliser. La comparaison est effectue sur chacune des colonnes dune ligne. Elle se base sur le type de donne statique de chaque colonne. Linterface IComparable est utilise sur les colonnes qui implmentent cette interface. La mthode statique System.Object.Equals est utilise sur les autres colonnes.
Prototype Nous nous intresserons un seul prototype de cet oprateur :
public static IEnumerable<T> Union<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer);

Exemple Nous utiliserons le mme code que dans lexemple de loprateur Intersect mais, ici, cest loprateur Union qui sera appel (voir Listing 10.5).

Chapitre 10

LINQ to DataSet

345

Listing 10.5 : Appel de loprateur Union avec et sans comparateur.


Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

Student[] students2 = { new Student { Id = 5, Name = "Abe Henry" }, new Student { Id = 7, Name = "Anthony Adams" }, new Student { Id = 29, Name = "Future Man" }, new Student { Id = 72, Name = "Dignan Stephens" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students2); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); IEnumerable<DataRow> union = seq1.Union(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("{0}Rsultats de loprateur Union() avec le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in union) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); } union = seq1.Union(seq2); Console.WriteLine("{0}Rsultats de loprateur Union() sans le comparateur{0}", System.Environment.NewLine); OutputDataTableHeader(dt1, 15); foreach (DataRow dataRow in union) { Console.WriteLine("{0,-15}{1,-15}", dataRow.Field<int>(0), dataRow.Field<string>(1)); }

Ici encore, rien de nouveau dans ce code : deux objets DataTable sont crs et initialiss avec les donnes des tableaux Student. Ils sont ensuite convertis en squences, puis loprateur Union leur est appliqu, avec puis sans comparateur. Les rsultats sont afchs aprs chaque appel loprateur Union.

346

LINQ to DataSet

Partie IV

Voici les informations afches suite lappui sur Ctrl+F5 :


Rsultats de loprateur Union() avec le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens 5 Abe Henry 29 Future Man Rsultats de loprateur Union() sans le comparateur Id Name ============================== 1 Joe Rattz 7 Anthony Adams 13 Stacy Sinclair 72 Dignan Stephens 5 Abe Henry 7 Anthony Adams 29 Future Man 72 Dignan Stephens

Comme vous pouvez le voir, seul le premier appel loprateur Union a donn les rsultats escompts. Oprateur SequencialEqual Loprateur SequencialEqual compare deux squences dobjets DataRow et dtermine leur galit. Pour ce faire, les deux squences sources sont numres et leurs objets DataRow, compars. Si les deux squences sources ont le mme nombre de lignes, et si tous les objets DataRow sont gaux, loprateur retourne la valeur true. Dans le cas contraire, il retourne la valeur false. Cet oprateur doit tre en mesure de dterminer si deux lments sont gaux. Pour ce faire, il devrait lui sufre dutiliser les mthodes GetHashCode et Equals. Cependant, tant donn que les objets compars sont de type DataRow, un prototype spcique doit tre utilis. Le deuxime argument de ce prototype dsigne le comparateur (System.Data.DataRowComparer.Default) utiliser. La comparaison est effectue sur chacune des colonnes dune ligne. Elle se base sur le type de donne statique de chaque colonne. Linterface IComparable est utilise sur les colonnes qui limplmentent. La mthode statique System.Object.Equals est utilise sur les autres colonnes.
Prototype Nous nous intresserons un seul prototype de cet oprateur :
public static bool SequenceEqual<T> ( this IEnumerable<T> first, IEnumerable<T> second, IEqualityComparer<T> comparer);

Chapitre 10

LINQ to DataSet

347

Exemple Dans cet exemple, nous allons construire deux squences identiques dobjets DataRow et les comparer avec loprateur SequencialEqual. Deux comparaisons seront effectues. La premire utilisera un comparateur et la seconde, non (voir Listing 10.6).
Listing 10.6 : Appel de loprateur SequenceEqual avec et sans comparateur.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable(students); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); bool equal = seq1.SequenceEqual(seq2, System.Data.DataRowComparer.Default); Console.WriteLine("Appel de SequenceEqual() avec comparateur : {0}", equal); equal = seq1.SequenceEqual(seq2); Console.WriteLine("Appel de SequenceEqual() sans le comparateur : {0}", equal);

Comme on pouvait sy attendre, le premier appel loprateur SequenceEqual indique que les deux squences sont gales, alors que le second indique quelles sont diffrentes :
Appel de SequenceEqual() avec comparateur : True Appel de SequenceEqual() sans comparateur : False

Oprateurs ddis aux champs


Ces oprateurs viennent complter ceux passs en revue dans la section prcdente. Ils sont dnis dans lassembly System.Data.DataSetExtensions.dll, dans la classe statique System.Data.DataRowExtensions. Vous avez certainement remarqu que, dans la plupart des exemples prcdents, nous avons utilis loprateur Field<T> pour extraire dun DataRow la valeur dun objet DataColumn. Cet oprateur a deux intrts : grce lui, la comparaison de donnes est possible, et il gre la valeur null. La manipulation des objets DataRow prsente un problme : les DataColumn, de type "valeur" ( opposer au type "rfrence"), ne peuvent pas tre compars correctement. En effet, ils peuvent contenir une donne de type quelconque : un entier, une chane ou un autre type de donne. Si, par exemple, un DataColumn contient une valeur de type int, il doit tre converti en une rfrence de type Object. Cette conversion est connue sous le nom "boxing" dans lenvironnement de dveloppement .NET. Lopration inverse (la transformation dun type rfrence en un type valeur) est appele "unboxing". Le problme se situe au niveau du boxing.

348

LINQ to DataSet

Partie IV

Pour mieux comprendre ce problme, nous allons raisonner sur quelques exemples. Dans le Listing 10.7, nous comparons deux entiers littraux de mme valeur.
Listing 10.7 : Comparaison des valeurs 3 et 3.
Console.WriteLine("(3 == 3) vaut {0}.", (3 == 3));

Voici le rsultat :
(3 == 3) vaut True.

Aucune surprise dans ce rsultat. Mais que devient la comparaison si les entiers comparer ont subi un boxing ? Examinons le code du Listing 10.8.
Listing 10.8 : Comparaison des valeurs 3 et 3 aprs leur casting dans des Object.
Console.WriteLine("((Object)3 == (Object)3) vaut {0}.", ((Object)3 == (Object)3));

Voici le rsultat :
((Object)3 == (Object)3) vaut False.

Que sest-il pass ? Loprateur de casting (Object) convertit chacune des valeurs en un objet de type Object. Dans cet exemple, la comparaison porte non pas sur la valeur de ces objets mais sur leur rfrence, cest--dire leur adresse. Bien entendu, les deux adresses ne sont pas identiques. Lorsque vous accdez aux objets DataColumn en indexant un objet DataRow, si une colonne a un type valeur, elle subit un boxing, et sa comparaison ne donne pas le rsultat escompt. Pour mettre en vidence ce problme, nous allons raisonner sur un exemple plus complexe qui utilise des objets DataColumn. Ici, nous utiliserons deux tableaux de classe diffrente. Le premier est le tableau Student, utilis dans les exemples prcdents. Le deuxime a pour classe designations. Il contient des donnes trangres au tableau Student. Voici la classe StudentClass : Une classe lmentaire contenant deux proprits publiques
class StudentClass { public int Id; public string Class; }

Pour convertir un tableau de classe StudentClass en un objet DataTable, nous utiliserons la mthode suivante :
static DataTable GetDataTable2(StudentClass[] studentClasses) { DataTable table = new DataTable(); table.Columns.Add("Id", typeof(Int32)); table.Columns.Add("Class", typeof(string)); foreach (StudentClass studentClass in studentClasses)

Chapitre 10

LINQ to DataSet

349

{ table.Rows.Add(studentClass.Id, studentClass.Class); } return (table); }

Cette mthode est une copie de la mthode commune GetTableData, modie pour fonctionner avec des tableaux dobjets StudentClass. Si vous tes amen travailler avec des tableaux dans un code de production rel, vous ne dnirez pas une mthode spcique pour chacune des classes qui utilise des objets DataTable. Vous vous tournerez plutt vers une mthode dextension gnrique. Comme il a t dit il y a quelques pages, dans un environnement rel les donnes seront gnralement obtenues en appliquant des requtes LINQ to DataSet une base de donnes titre dexemple, nous convertirons les tableaux en squences dobjets DataRow et nous tenterons deffectuer une jointure sur le champ Id. Ce champ sera obtenu en utilisant le nom des colonnes pour indexer les DataRow (voir Listing 10.9).
Listing 10.9 : Ralisation dune jointure en indexant le DataRow.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

StudentClass[] classDesignations = { new StudentClass { Id = 1, Class = "Sophmore" }, new StudentClass { Id = 7, Class = "Freshman" }, new StudentClass { Id = 13, Class = "Graduate" }, new StudentClass { Id = 72, Class = "Senior" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable2(classDesignations); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); string anthonysClass = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" from c in seq2 where c["Id"] == s["Id"] select (string)c["Class"]). SingleOrDefault<string>(); Console.WriteLine("La classe dAnthony est : {0}", anthonysClass != null ? anthonysClass : "null");

Quelques lments dans la requte prcdente mritent des explications. La ligne en gras ralise une jointure sur les deux tableaux. Pour ce faire, elle indexe les deux objets DataRow an daccder aux valeurs du champ Id. Ces valeurs tant de type string, elles subissent un boxing. Il sera donc impossible de dterminer leur galit en utilisant des moyens conventionnels. Deux lignes plus haut, nous utilisons loprateur Field<T> pour comparer la valeur du champ Name et la valeur littrale "Anthony Adams". Cet

350

LINQ to DataSet

Partie IV

oprateur est appel pour liminer le problme de boxing qui va tre mis en vidence sur le champ Id. Remarquez galement que la requte mlange la syntaxe dinterrogation de requte de LINQ et la notation point classique. Voici les rsultats :
La classe dAnthony est : Null

Ce problme vient du fait que la ligne en gras na pas t en mesure de raliser la jointure. Le boxing du champ Id en est videmment la cause. Pour corriger ce problme, nous allons modier la ligne :
where c["Id"] == s["Id"]

en :
where (int)c["Id"] == (int)s["Id"]

Le code devient donc celui du Listing 10.10.


Listing 10.10 : Utilisation dun oprateur de casting pour pouvoir tester lgalit des champs Id.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

StudentClass[] classDesignations = { new StudentClass { Id = 1, Class = "Sophmore" }, new StudentClass { Id = 7, Class = "Freshman" }, new StudentClass { Id = 13, Class = "Graduate" }, new StudentClass { Id = 72, Class = "Senior" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable2(classDesignations); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); string anthonysClass = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" from c in seq2 where (int)c["Id"] == (int)s["Id"] select (string)c["Class"]). SingleOrDefault<string>(); Console.WriteLine("La classe dAnthony est : {0}", anthonysClass != null ? anthonysClass : "null");

Lexcution de ce code produit le rsultat suivant :


La classe dAnthony est: Freshman

Le problme li au boxing du champ Id a t vit. Cependant, un autre problme est toujours prsent : lorsque vous tentez dobtenir une valeur dans une colonne en indexant un objet DataRow, lobjet retourn est de type Object. Pour le comparer une

Chapitre 10

LINQ to DataSet

351

valeur littrale ou laffecter une variable, vous devrez utiliser un oprateur de casting. Dans cet exemple, nous utilisons loprateur (int). tant donn que les objets DataSet utilisent la valeur DBNull.Value pour reprsenter une valeur null, si une colonne contient la valeur null son casting au format int produira une exception. Heureusement, les oprateurs LINQ to DataSet Field<T> et SetField<T> liminent ces deux problmes. Le Listing 10.11 reprsente lexemple prcdent, dans lequel loprateur de casting (int) a t remplac par un Field<T>.
Listing 10.11 : Utilisation de loprateur Field.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

StudentClass[] classDesignations = { new StudentClass { Id = 1, Class = "Sophmore" }, new StudentClass { Id = 7, Class = "Freshman" }, new StudentClass { Id = 13, Class = "Graduate" }, new StudentClass { Id = 72, Class = "Senior" } }; DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataTable dt2 = GetDataTable2(classDesignations); IEnumerable<DataRow> seq2 = dt2.AsEnumerable(); string anthonysClass = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" from c in seq2 where c.Field<int>("Id") == s.Field<int>("Id") select (string)c["Class"]). SingleOrDefault<string>(); Console.WriteLine("La classe dAnthony est : {0}", anthonysClass != null ? anthonysClass : "null");

Ce code tant quivalent au prcdent, ceci prs que loprateur Field<T> remplace loprateur de casting (int), il produit le mme rsultat :
La classe dAnthony est: Freshman

Oprateur Field<T> Comme nous venons de le montrer dans le Listing 10.11, loprateur Field<T> permet dobtenir la valeur dune colonne dans un objet DataRow. Par ailleurs, il vite les problmes lis au boxing et aux valeurs null.
Prototypes Six prototypes de cet oprateur seront tudis dans cette section.

352

LINQ to DataSet

Partie IV

Le premier prototype retourne la valeur de la colonne pour le DataColumn et la version spcis : Le premier prototype
public static T Field ( this DataRow first, System.Data.DataColumn column, System.Data.DataRowVersion version);

Le deuxime prototype retourne la valeur de la colonne pour la colonne dont le nom et la version sont spcis : Le deuxime prototype
public static T Field ( this DataRow first, string columnName, System.Data.DataRowVersion version);

Le troisime prototype retourne la valeur de la colonne pour la colonne dont lordinal et la version sont spcis : Le troisime prototype
public static T Field ( this DataRow first, int ordinal, System.Data.DataRowVersion version);

Le quatrime prototype retourne la valeur de la colonne dont le DataColumn est spci : Le quatrime prototype
public static T Field ( this DataRow first, System.Data.DataColumn column);

Le cinquime prototype retourne la valeur de la colonne pour la colonne dont le nom est spci : Le cinquime prototype
public static T Field ( this DataRow first, string columnName);

Le sixime prototype retourne la valeur de la colonne pour la colonne dont lordinal est spci : Le sixime prototype
public static T Field ( this DataRow first, int ordinal);

Vous lavez certainement remarqu, les trois premiers prototypes permettent de choisir la version (DataRowVersion) de lobjet DataColumn obtenir.

Chapitre 10

LINQ to DataSet

353

Exemples Lorsque vous tes arriv ce point dans la lecture de louvrage, plusieurs exemples vous ont montr comment utiliser loprateur Field<T>. Le Listing 10.12 va aller plus loin en vous montrant les six facettes de cet oprateur.
Listing 10.12 : Un exemple des six prototypes de loprateur Field.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); int id; // Utilisation du premier prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>(dt1.Columns[0], DataRowVersion.Current)). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le premier prototype, a pour valeur {0}", id); // Utilisation du deuxime prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>("Id", DataRowVersion.Current)). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le deuxime prototype, a pour valeur {0}", id); // Utilisation du troisime prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>(0, DataRowVersion.Current)). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le troisime prototype, a pour valeur {0}", id); // Utilisation du quatrime prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>(dt1.Columns[0])). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le quatrime prototype, a pour valeur {0}", id); // Utilisation du cinquime prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>("Id")). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le cinquime prototype, a pour valeur {0}", id);

354

LINQ to DataSet

Partie IV

// Utilisation du sixime prototype id = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s.Field<int>(0)). Single<int>(); Console.WriteLine("Le champ Id dAnthony, obtenu avec le sixime prototype, a pour valeur {0}", id);

Les premires lignes du code dnissent le tableau Students, initialisent un objet DataTable avec son contenu et le convertissent en une squence. La suite du code applique tour tour les six prototypes de loprateur Field<T> la squence pour obtenir la valeur du champ Id. Notez que loprateur Field<T> est galement utilis dans la partie Where de la requte. Voici les rsultats :
Le Le Le Le Le Le champ champ champ champ champ champ Id Id Id Id Id Id dAnthony, dAnthony, dAnthony, dAnthony, dAnthony, dAnthony, obtenu obtenu obtenu obtenu obtenu obtenu avec avec avec avec avec avec le le le le le le premier prototype, a pour valeur 7 deuxime prototype, a pour valeur 7 troisime prototype, a pour valeur 7 quatrime prototype, a pour valeur 7 cinquime prototype, a pour valeur 7 sixime prototype, a pour valeur 7

Pour illustrer lutilisation de largument DataRowVersion, nous avons modi une valeur DataColumn en utilisant loprateur SetField<T>. Cet oprateur na pas encore t tudi. Pour linstant, ignorez le code qui lutilise. Vous en apprendrez plus son sujet dans la section suivante. Ce chapitre tant consacr aux oprateurs LINQ to DataSet et non au fonctionnement dtaill de la classe DataSet, nous naborderons ce sujet que trs brivement, travers les deux mthodes DataSet utilises dans lexemple du Listing 10.13.
Listing 10.13 : Dmonstration de largument DataRowVersion de loprateur Field.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); DataRow row = (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s).Single<DataRow>(); row.AcceptChanges(); row.SetField("Name", "George Oscar Bluth"); Console.WriteLine("Valeur originale = {0} : Valeur actuelle = {1}", row.Field<string>("Name", DataRowVersion.Original), row.Field<string>("Name", DataRowVersion.Current));

Chapitre 10

LINQ to DataSet

355

row.AcceptChanges(); Console.WriteLine("Valeur originale = {0} : Valeur actuelle = {1}", row.Field<string>("Name", DataRowVersion.Original), row.Field<string>("Name", DataRowVersion.Current));

Cet exemple dnit une squence partir des donnes du tableau students. Une requte est alors lance pour obtenir un objet DataRow unique. Le premier code digne dintrt est la mthode AcceptChanges, appele juste aprs avoir obtenu lobjet DataRow. Cette mthode est appele pour que lobjet DataRow considre ses valeurs actuelles comme tant des valeurs originales. Si cette mthode navait pas t appele, les valeurs originales de lobjet DataRow ne seraient pas dnies, et une exception se produirait si on essayait de les afcher. Une fois la mthode AcceptChanges excute, lobjet DataRow est prt pour pister les changements dans ses valeurs DataColumn. Le premier appel de la mthode AcceptChanges est suivi de la modication du champ Name avec loprateur SetField. Le bloc dinstructions suivant afche la valeur originale ("Anthony Adams") et la valeur actuelle ("George Oscar Bluth") du DataColumn Name. La mthode AcceptChanges est appele une deuxime fois, puis les valeurs originale et actuelle du DataColumn Name sont nouveau afches. Cette fois-ci, les deux valeurs devraient tre identiques et gales "George Oscar Bluth", puisque la mthode AcceptChanges a t appele. Examinons les rsultats :
Valeur originale = Anthony Adams : Valeur actuelle = George Oscar Bluth Valeur originale = George Oscar Bluth : Valeur actuelle = George Oscar Bluth

Si vous ne deviez retenir quune chose de cet exemple, que ce soit lutilisation de la mthode AcceptChanges. Cette mthode permet de mmoriser la valeur originale et daffecter une autre valeur un objet DataColumn. Comme il a t dit prcdemment, loprateur Field<T> sait galement viter le problme li aux champs vides (null). Dans le Listing 10.14, nous allons voir ce qui se passe lorsquun nom dtudiant nest pas initialis et que loprateur Field<T> nest pas utilis.
Listing 10.14 : Un exemple de champ null sans utiliser loprateur Field.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = null }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); string name = seq1.Where(student => student.Field<int>("Id") == 7) .Select(student => (string)student["Name"]) .Single(); Console.WriteLine("Students name is {0}", name);

356

LINQ to DataSet

Partie IV

Deux passages apparaissent en gras pour attirer votre attention. Dans le premier, le nom de ltudiant dont la colonne id vaut 7 est initialis null. Dans le second, loprateur Field<T> est remplac par un simple casting (string). Voici les rsultats :
Une exception non gre sest produite: System.InvalidCastException: Impossible deffectuer un cast dun objet de type System.DBNull en type System.String.

Que sest-il pass ? La valeur de lobjet DataColumn dont la colonne id vaut 7 tant null, il est impossible de lui appliquer un casting (string). Il existe des solutions plus verbeuses pour viter ce problme, mais le plus simple consiste utiliser loprateur Field<T> (voir Listing 10.15).
Listing 10.15 : Un exemple de champ null avec loprateur Field.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = null }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); string name = seq1.Where(student => student.Field<int>("Id") == 7) .Select(student => student.Field<string>["Name"]) .Single(); Console.WriteLine("Le nom de ltudiant est {0}", name);

Ce code est identique au prcdent mais, ici, le casting (string) est remplac par un appel loprateur Field<T>. Voici le rsultat :
Le nom de ltudiant est

Oprateur SetField<T> La valeur null affecte galement linitialisation des objets DataColumn. Pour viter tout problme, vous utiliserez loprateur SetField<T>. Par son intermdiaire, il est en effet possible daffecter un DataColumn une donne de type nullable dont la valeur est null.
Prototypes Nous nous intresserons trois prototypes de cet oprateur dans ce chapitre.

Le premier prototype vous permet de dnir la valeur de la colonne spcie : Le premier prototype
public static void SetField ( this DataRow first,

Chapitre 10

LINQ to DataSet

357

System.Data.DataColumn column, T value);

Le deuxime prototype vous permet de dnir la valeur de la colonne dont le nom est spci : Le deuxime prototype
public static void SetField ( this DataRow first, string columnName, T value);

Le troisime prototype vous permet de dnir la valeur de la colonne dont lordinal est spci : Le troisime prototype
public static void SetField ( this DataRow first, int ordinal, T value);

Exemples Dans le Listing 10.16, nous dnissons puis afchons la squence de DataRow avec laquelle nous allons travailler. Le DataRow dun des tudiants est alors obtenu en effectuant une requte, puis le nom de ltudiant est modi avec loprateur SetField<T>. Enn, la squence de DataRow ainsi modie est nouveau afche. Ce processus est rpt pour chacun des prototypes de loprateur SetField.
Listing 10.16 : Un exemple dutilisation des prototypes de loprateur SetField.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); IEnumerable<DataRow> seq1 = dt1.AsEnumerable(); Console.WriteLine("{0}Rsultats avant dappeler les prototypes :", System.Environment.NewLine); foreach (DataRow dataRow in seq1) { Console.WriteLine("Student Id = {0} is {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); } // Utilisation du premier prototype (from s in seq1 where s.Field<string>("Name") == "Anthony Adams" select s).Single<DataRow>().SetField(dt1.Columns[1], "George Oscar Bluth"); Console.WriteLine("{0}Rsultats aprs lappel du premier prototype :", System.Environment.NewLine);

358

LINQ to DataSet

Partie IV

foreach (DataRow dataRow in seq1) { Console.WriteLine("Student Id = {0} is {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); } // Utilisation du deuxime prototype (from s in seq1 where s.Field<string>("Name") == "George Oscar Bluth" select s).Single<DataRow>().SetField("Name", "Michael Bluth"); Console.WriteLine("{0}Rsultats aprs lappel du deuxime prototype :", System.Environment.NewLine); foreach (DataRow dataRow in seq1) { Console.WriteLine("Student Id = {0} is {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); } // Utilisation du troisime prototype (from s in seq1 where s.Field<string>("Name") == "Michael Bluth" select s).Single<DataRow>().SetField("Name", "Tony Wonder"); Console.WriteLine("{0}Rsultats aprs lappel du troisime prototype :", System.Environment.NewLine); foreach (DataRow dataRow in seq1) { Console.WriteLine("Ltudiant dont lId = {0} est {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); }

Le code nest pas aussi difcile quil en a lair. Aprs avoir obtenu et afch la squence dtudiants, un mme bloc de code est rpt trois reprises (une pour chaque prototype). Chacun des blocs contient une requte LINQ qui rcupre le champ Name, modie sa valeur et afche dans la console une ligne den-tte suivie des lments de la squence. Nous allons nous attarder sur plusieurs passages dans ce listing. Dans chacune des requtes LINQ portant sur le champ Name du DataRow, nous mlangeons la syntaxe de requte propre LINQ et la traditionnelle syntaxe point. Remarquez galement que nous utilisons loprateur Field<T> pour accder lenregistrement sur lequel nous allons appliquer loprateur SetField<T>. Chaque requte extrait un DataRow de la squence par son champ Name (modi ltape prcdente, sauf pour le premier prototype) et le modie avec loprateur SetField. Par exemple, dans le code relatif au premier prototype la requte extrait le DataRow dont le champ Name vaut "Anthony Adams" et modie ce nom en "George Oscar Bluth". Dans le code du deuxime prototype, la requte extrait le DataRow dont le Name est "George Oscar Bluth" et le modie en une valeur qui sera utilise comme critre de slection dans le troisime prototype. Pour chaque prototype, une boucle foreach afche les lments de la squence ainsi modis, an que vous puissiez vrier que la modication a effectivement t effectue.

Chapitre 10

LINQ to DataSet

359

Avez-vous remarqu que linterrogation de la squence et la mise jour du DataRow se font dans une seule et mme instruction ? Il ny a rien de magique l-dedans : nous utilisons simplement la puissance de LINQ. Voici les rsultats :
Rsultats avant Ltudiant dont Ltudiant dont Ltudiant dont Ltudiant dont Rsultats aprs Ltudiant dont Ltudiant dont Ltudiant dont Ltudiant dont Rsultats aprs Ltudiant dont Ltudiant dont Ltudiant dont Ltudiant dont Rsultats aprs Ltudiant dont Ltudiant dont Ltudiant dont Ltudiant dont dappeler les prototypes : lId = 1 est Joe Rattz lId = 7 est Anthony Adams lId = 13 est Stacy Sinclair lId = 72 est Dignan Stephens lappel du premier prototype : lId = 1 est Joe Rattz lId = 7 est George Oscar Bluth lId = 13 est Stacy Sinclair lId = 72 est Dignan Stephens lappel du deuxime prototype : lId = 1 est Joe Rattz lId = 7 est Michael Bluth lId = 13 est Stacy Sinclair lId = 72 est Dignan Stephens lappel du troisime prototype : lId = 1 est Joe Rattz lId = 7 est Tony Wonder lId = 13 est Stacy Sinclair lId = 72 est Dignan Stephens

Oprateurs ddis aux DataTable


Dans la classe DataRowExtensions, en complment des oprateurs ddis aux DataRow, plusieurs oprateurs spciques DataTable ont t dnis. Nous les avons regroups dans cette section. Ces oprateurs sont dnis dans lassembly System.Data.Entity.dll, dans la classe statique System.Data.DataTableExtensions. Oprateur AsEnumerable Vous devez certainement tre surpris de trouver un oprateur AsEnumerable ddi la classe DataTable qui retourne une squence dobjets DataRow. Si tel est le cas, cela signie que vous ne vous tes pas demand pourquoi nous nen avions pas dj parl alors que nous lavons utilis dans pratiquement tous les exemples ! Si vous jetez un il la classe statique System.Data.DataTableExtensions, vous trouverez effectivement un oprateur AsEnumerable. Cet oprateur retourne une squence de type IEnumerable<DataRow> partir dun objet DataTable.
Prototype Un seul prototype de cet oprateur sera trait dans cette section :
public static IEnumerable<DataRow> AsEnumerable ( this DataTable source );

360

LINQ to DataSet

Partie IV

Cet oprateur est appliqu un objet DataTable. Il retourne une squence dobjets DataRow. Cest traditionnellement la premire tape lors de lexcution dune requte LINQ to DataSet sur un DataTable dun objet DataSet. Cet oprateur retourne une squence IEnumerable<T>, o T est un DataRow. Aprs son appel, vous pouvez donc utiliser les nombreux oprateurs de LINQ qui sont appels sur une squence de type IEnumerable<T>.
Exemples tant donn que loprateur AsEnumerable est la premire tape permettant deffectuer une requte LINQ to DataSet, la plupart des exemples de ce chapitre utilisent cet oprateur.

Oprateur CopyToDataTable<DataRow> Vous savez maintenant comment effectuer une requte et modier les valeurs DataColumn dun DataRow. Loprateur CopyToDataTable va vous permettre de placer cette squence dobjets DataRow ainsi modie dans un DataTable.
Prototypes Deux prototypes de cet oprateur seront examins dans ce chapitre.

Le premier prototype est appel sur un IEnumerable<DataRow> et il retourne un DataTable. Vous lutiliserez donc pour crer un nouvel objet DataTable partir dune squence dobjets DataRow : Le premier prototype
public static DataTable CopyToDataTable<T> ( this IEnumerable<T> source ) where T : DataRow;

Ce premier prototype cre automatiquement les versions originales de chaque champ sans quil soit ncessaire dappeler la mthode AcceptChange. Le second prototype est appel sur un IEnumerable<DataRow> dune table source DataTable. Il met jour cette table en se basant sur la valeur LoadOption spcie dans largument. Le second prototype
public static void CopyToDataTable<T> ( this IEnumerable<T> source, DataTable table, LoadOption options ) where T : DataRow;

La valeur de largument LoadOption indique loprateur si les valeurs originales et/ou les valeurs actuelles des colonnes doivent tre modies. Voici les valeurs possibles pour cet argument :
m

OverwriteChanges. Les valeurs originale et actuelle de chaque colonne sont modies.

Chapitre 10

LINQ to DataSet

361

m m

PreserveChanges. Seule la valeur originale de chaque colonne est modie. Upsert. Seule la valeur actuelle de chaque colonne est modie.

Largument LoadOption induit un nouveau problme : comment loprateur CopyToDataTable peut-il savoir quel enregistrement de la DataTable de destination correspond lenregistrement de la DataTable source ? Lenregistrement source doit-il tre ajout au tableau de destination, ou un des enregistrements dj prsents doit-il tre mis jour ? Impossible de rpondre ces deux questions, moins que loprateur nutilise des cls primaires. Pour que le second prototype de loprateur CopyToDataTable fonctionne, lobjet DataTable de destination doit donc tre pourvu de champs appropris spcis en tant que cls primaires. Dans le cas contraire, les enregistrements sources seront ajouts au tableau de destination. Une autre complication est inhrente au second prototype : les champs nont aucune valeur originale, moins que vous nappeliez la mthode AcceptChanges pour les crer. Si vous essayez daccder la version originale dun champ qui en est dnu, une exception se produit. Notez cependant que vous pouvez appeler la mthode HasVersion sur chacun des objets DataRow pour savoir sils possdent une version originale, et ainsi viter quune exception ne soit gnre.
Exemples Pour illustrer le premier prototype, nous allons modier un champ dans un DataTable, crer un nouveau DataTable partir du DataTable modi en invoquant loprateur CopyToDataTable et afcher le contenu du nouveau DataTable (voir Listing 10.17).
Listing 10.17 : Appel du premier prototype de loprateur CopyToDataTable.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); Console.WriteLine("Le DataTable original :"); foreach (DataRow dataRow in dt1.AsEnumerable()) { Console.WriteLine("Student Id = {0} is {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); } (from s in dt1.AsEnumerable() where s.Field<string>("Name") == "Anthony Adams" select s).Single<DataRow>().SetField("Name", "George Oscar Bluth"); DataTable newTable = dt1.AsEnumerable().CopyToDataTable();

362

LINQ to DataSet

Partie IV

Console.WriteLine("{0}Le nouveau DataTable :", System.Environment.NewLine); foreach (DataRow dataRow in newTable.AsEnumerable()) { Console.WriteLine("Ltudiant dId = {0} est {1}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name")); }

Les premires lignes dnissent un objet DataTable partir du tableau students. Le bloc dinstructions suivant afche le contenu du DataTable dans la fentre console. Le bloc dinstructions suivant modie le champ Name dun des objets DataRow. Un nouveau DataTable est alors cr partir des donnes modies en invoquant loprateur CopyToDataTable. Le dernier bloc dinstructions afche le contenu du nouveau DataTable. Voici le rsultat :
Le DataTable original : Ltudiant dId = 1 est Joe Rattz Ltudiant dId = 7 est Anthony Adams Ltudiant dId = 13 est Stacy Sinclair Ltudiant dId = 72 est Dignan Stephens Le nouveau Ltudiant Ltudiant Ltudiant Ltudiant DataTable : dId = 1 est Joe Rattz dId = 7 est George Oscar Bluth dId = 13 est Stacy Sinclair dId = 72 est Dignan Stephens

Comme vous pouvez le voir, le nouveau DataTable contient la version modie du DataTable original. Nous allons maintenant illustrer le deuxime prototype de loprateur CopyToDataTable. Comme il a t dit prcdemment, il est ncessaire de dnir une cl primaire dans le DataSet de destination pour que largument LoadOption produise leffet escompt. des ns dmonstratives, nous ne dnirons aucune cl primaire (voir Listing 10.18). Cet exemple tant plus complexe que le prcdent, nous donnerons des explications chaque fois que cela sera ncessaire.
Listing 10.18 : Appel du second prototype sans dfinir une cl primaire dans le DataSet de destination.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); DataTable newTable = dt1.AsEnumerable().CopyToDataTable();

Jusquici, rien de nouveau : le DataTable source est cr partir du tableau students et le DataTable de destination, en appelant loprateur CopyToDataTable sur lobjet DataTable source. tant donn que nous avons utilis le premier prototype de loprateur

Chapitre 10

LINQ to DataSet

363

CopyToDataTable, il nest pas ncessaire dutiliser la mthode AcceptChanges sur le DataTable de destination. Il est important de le signaler car, dans le prochain bloc de code, la version originale du champ Name sera invoque. Si la version originale de cet oprateur nexistait pas, une exception serait leve.
Console.WriteLine("Avant la mise jour du DataTable :"); foreach (DataRow dataRow in newTable.AsEnumerable()) { Console.WriteLine("Student Id = {0} : original {1} : current {2}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name", DataRowVersion.Original), dataRow.Field<string>("Name", DataRowVersion.Current)); }

Rien dexceptionnel dans ce code, si ce nest que la version originale du champ Name est utilise. Aucune exception ne sera leve, puisquune version originale a t cre de faon transparente par le premier prototype de loprateur CopyToDataTable.
(from s in dt1.AsEnumerable() where s.Field<string>("Name") == "Anthony Adams" select s).Single<DataRow>().SetField("Name", "George Oscar Bluth"); dt1.AsEnumerable().CopyToDataTable(newTable, LoadOption.Upsert);

Ce bloc de code est le plus intressant de cet exemple. Comme vous pouvez le voir, la valeur du champ Name dun des enregistrements de lobjet DataTable source est modie avec loprateur SetField<T>. Cette modication effectue, loprateur CopyToDataTable est appel en spciant quune copie de type LoadOption.Upsert (limite la valeur actuelle de chaque colonne) doit tre effectue. Ce deuxime oprateur CopyToDataTable pose un problme : la mthode AcceptChange nayant pas t appele au pralable, la valeur initiale des colonnes na pas t dnie. Si nous essayons daccder ces valeurs initiales, une exception sera gnre. Pour viter ce problme, il est ncessaire dutiliser la mthode HasVersion. Comme aucune cl primaire na t dnie, tous les enregistrements sources seront ajouts dans le tableau de destination.
Console.WriteLine("{0}Aprs la mise jour du DataTable:", System.Environment.NewLine); foreach (DataRow dataRow in newTable.AsEnumerable()) { Console.WriteLine("Etudiant dId = {0} : valeur originale {1}, valeur actuelle{2}", dataRow.Field<int>("Id"), dataRow.HasVersion(DataRowVersion.Original) ? dataRow.Field<string>("Name", DataRowVersion.Original) : "-inexistante-", dataRow.Field<string>("Name", DataRowVersion.Current)); }

Ce bloc de code se contente dafcher le contenu de lobjet DataTable dans la console. tant donn quaucune cl primaire na t dnie dans le tableau de destination, aucune galit ne sera tablie entre les enregistrements lors de la copie du tableau. Tous les enregistrements sources seront donc ajouts la n du DataTable de destination.

364

LINQ to DataSet

Partie IV

Remarquez galement que seuls les champs Name dont la valeur initiale existe, cest-dire pour lesquels dataRow.HasVersion vaut true, sont afchs. Voici les rsultats :
Avant la mise Ltudiant dId Ltudiant dId Ltudiant dId Sinclair Ltudiant dId Stephens Aprs la mise Ltudiant dId Ltudiant dId Ltudiant dId Sinclair Ltudiant dId Stephens Ltudiant dId Ltudiant dId Bluth Ltudiant dId Sinclair Ltudiant dId Stephens jour du DataTable : = 1 : valeur originale Joe Rattz, valeur actuelle Joe Rattz = 7 : valeur originale Anthony Adams, valeur actuelle Anthony Adams = 13 : valeur originale Stacy Sinclair, valeur actuelle Stacy = 72 : valeur originale Dignan Stephens, valeur actuelle Dignan

jour du DataTable : = 1 : valeur originale Joe Rattz, valeur actuelle Joe Rattz = 7 : valeur originale Anthony Adams, valeur actuelle Anthony Adams = 13 : valeur originale Stacy Sinclair, valeur actuelle Stacy = 72 : valeur originale Dignan Stephens, valeur actuelle Dignan = 1 : valeur originale -inexistante-, valeur actuelle Joe Rattz = 7 : valeur originale -inexistante-, valeur actuelle George Oscar = 13 : valeur originale -inexistante-, valeur actuelle Stacy = 72 : valeur originale -inexistante-, valeur actuelle Dignan

Comme vous pouvez le voir, plusieurs enregistrements apparaissent en double. Ceci est d au fait quaucune cl primaire na t dnie dans le DataTable de destination. Lenregistrement mis jour apparat galement en double. La mthode AcceptChanges nayant pas t automatiquement appele par le second prototype de loprateur CopyToDataTable, nous avons test lexistence des valeurs initiales avec la mthode HasVersion. Vous vous demandez peut-tre pourquoi nous navons pas simplement appel la mthode AcceptChanges. Si nous lavions fait, toutes les valeurs actuelles des champs seraient devenues des valeurs originales, et il aurait t impossible de dterminer quel enregistrement avait t modi. Pour solutionner ce problme, il suft de dnir une cl primaire dans le DataTable de destination (voir Listing 10.19).
Listing 10.19 : Appel du second prototype en dfinissant une cl primaire dans le DataTable de destination.
Student[] students new Student { Id new Student { Id new Student { Id new Student { Id }; = = = = = { 1, Name = "Joe Rattz" }, 7, Name = "Anthony Adams" }, 13, Name = "Stacy Sinclair" }, 72, Name = "Dignan Stephens" }

DataTable dt1 = GetDataTable(students); DataTable newTable = dt1.AsEnumerable().CopyToDataTable(); newTable.PrimaryKey = new DataColumn[] { newTable.Columns[0] }; Console.WriteLine("Avant la mise jour du DataTable :");

Chapitre 10

LINQ to DataSet

365

foreach (DataRow dataRow in newTable.AsEnumerable()) { Console.WriteLine("Etudiant dId = {0} : valeur originale {1}, valeur actuelle {2}", dataRow.Field<int>("Id"), dataRow.Field<string>("Name", DataRowVersion.Original), dataRow.Field<string>("Name", DataRowVersion.Current)); } (from s in dt1.AsEnumerable() where s.Field<string>("Name") == "Anthony Adams" select s).Single<DataRow>().SetField("Name", "George Oscar Bluth"); dt1.AsEnumerable().CopyToDataTable(newTable, LoadOption.Upsert); Console.WriteLine("{0}Aprs la mise jour du DataTable :", System.Environment.NewLine); foreach (DataRow dataRow in newTable.AsEnumerable()) { Console.WriteLine("Etudiant dId = {0} : valeur originale {1}, valeur actuelle {2}", dataRow.Field<int>("Id"), dataRow.HasVersion(DataRowVersion.Original) ? dataRow.Field<string>("Name", DataRowVersion.Original) : "-does not exist-", dataRow.Field<string>("Name", DataRowVersion.Current)); }

La seule diffrence entre cet exemple et le prcdent rside dans la dnition dune cl primaire dans le DataTable newTable. Voici les rsultats :
Avant la mise Ltudiant dId Ltudiant dId Ltudiant dId Sinclair Ltudiant dId Stephens Aprs la mise Ltudiant dId Ltudiant dId Bluth Ltudiant dId Sinclair Ltudiant dId Stephens jour du DataTable : = 1 : valeur originale Joe Rattz, valeur actuelle Joe Rattz = 7 : valeur originale Anthony Adams, valeur actuelle Anthony Adams = 13 : valeur originale Stacy Sinclair, valeur actuelle Stacy = 72 : valeur originale Dignan Stephens, valeur actuelle Dignan

jour du DataTable : = 1 : valeur originale Joe Rattz, valeur actuelle Joe Rattz = 7 : valeur originale Anthony Adams, valeur actuelle George Oscar = 13 : valeur originale Stacy Sinclair, valeur actuelle Stacy = 72 : valeur originale Dignan Stephens, valeur actuelle Dignan

Tout ceci est bien plus convenable : le champ Name de ltudiant dont la colonne Id vaut 7 avait pour valeur "Anthony Adams", mais il est maintenant gal "George Oscar Bluth", et les enregistrements ne sont pas dupliqus.

Rsum
Ce chapitre vous a montr comment utiliser les oprateurs IEnumerable<T> pour initialiser des objets DataRow et les oprateurs Field<T> et SetField<T> pour initialiser et lire les valeurs stockes dans des champs. Vous avez galement vu quil tait impratif dutiliser les oprateurs spciques DataSet pour obtenir les rsultats escompts.

366

LINQ to DataSet

Partie IV

Enn, vous avez vu quen les combinant avec les oprateurs de requte standard de LINQ to Objects vous pouviez dnir des requtes LINQ puissantes sur des objets DataSet. Dans le chapitre suivant, nous terminerons la partie ddie LINQ to DataSet en montrant comment effectuer des requtes sur des DataSet typs. Vous dcouvrirez galement un exemple de requte LINQ to DataSet portant sur une base de donnes relle.

11
Possibilits complmentaires des DataSet
Le chapitre prcdent a donn de nombreux exemples dinterrogation dobjets DataTable. Dans un environnement de dveloppement rel, ces objets proviendront de DataSets. Cependant, dans un souci de simplicit, ils ont t crs par programme, en utilisant des tableaux statiques. Nayez crainte, comme vous le verrez dans ce chapitre, cette technique nest nullement limitative. Les exemples du chapitre prcdent taient tous bass sur des DataSets non typs. Il est parfois ncessaire dexcuter une requte sur un DataSet typ en utilisant LINQ to DataSet. Dans ce chapitre, nous examinerons ces nouvelles possibilits et vous montrerons comment tirer le meilleur de LINQ to DataSet. Nous commencerons par linterrogation de DataSets typs. Et nous poursuivrons par linterrogation dune base de donnes relle.

Espaces de noms rfrencs


Les exemples de ce chapitre utilisent les classes des espaces de noms System.Data, System.Data.SqlClient et System.Linq. Si les directives using correspondantes nexistent pas dans votre code, vous devez les dnir comme suit :
using System.Data; using System.Data.SqlClient; using System.Linq;

DataSets typs
LINQ est en mesure dexcuter des requtes sur des DataSets non typs et typs. Dans le second cas, le code dinterrogation sera trs simple crire et lire. tant donn quil

368

LINQ to DataSet

Partie IV

existe une classe ddie aux DataSets, les requtes peuvent accder aux noms des tables et aux colonnes en utilisant les proprits de classe des objets DataSet typs. Cela est plus pratique quindexer la collection de Tables ou utiliser les oprateurs Field<T> et SetField<T>. Plutt quaccder la table dobjets DataSet Students en utilisant cette instruction :
DataTable Students = dataSet.Tables["Students"];

vous utiliserez linstruction suivante :


DataTable Students = dataSet.Students;

De la mme manire, plutt quobtenir la valeur dun champ avec cette instruction :
dataRow.Field<string>("Name")

vous utiliserez linstruction suivante :


dataRow.Name

Ces facilits dcriture rendent le code bien plus facile lire et maintenir. Avant de passer la pratique, nous avons besoin de dnir un DataSet typ. Voici comment procder : 1. Cliquez du bouton droit sur lentre correspondant au nom de votre projet dans la fentre Explorateur de solutions. 2. Slectionnez Ajouter/Nouvel lment dans le menu contextuel. 3. Si ncessaire, dveloppez larbre des catgories et slectionnez Donnes dans la liste. Sous Modles Visual Studio installs, slectionnez DataSet. Donnez le nom StudentsDataSet.xsd au chier DataSet et cliquez sur le bouton Ajouter. 4. Quelques instants plus tard, lespace de travail afche un concepteur de DataSet. Placez le pointeur sur la Bote outils, cliquez et glissez-dposez un DataTable sur le concepteur de DataSet. 5. Cliquez du bouton droit sur la barre de titre du DataSet que vous venez dajouter et slectionnez Proprits dans le menu contextuel. 6. Dans la fentre Proprits, donnez le nom Students au DataTable. 7. Cliquez du bouton droit sur la barre de titre du DataSet et slectionnez Ajouter/ Colonne dans le menu contextuel. 8. Affectez la valeur "Id" la proprit Name et la valeur "System.Int32" la proprit DataType. 9. Cliquez du bouton droit sur la barre de titre du DataSet et slectionnez Ajouter/ Colonne dans le menu contextuel. 10. Affectez la valeur "Name" la proprit Caption de ce DataColumn. 11. Sauvegardez le chier.

Chapitre 11

Possibilits complmentaires des DataSet

369

Vous venez de crer le DataSet typ StudentsDataSet. Ce DataSet contient le DataTable Student, qui contient lui-mme deux colonnes de type DataColumn. La premire a pour nom Id et pour type Int32. La seconde a pour nom Name et pour type string. Nous allons utiliser ce DataSet pour effectuer des requtes LINQ. tant donn que ce DataSet est typ, nous pourrons accder aux champs DataRow directement (voir Listing 11.1).
Listing 11.1 : Un exemple de requte sur un DataSet typ.
StudentsDataSet studentsDataSet = new StudentsDataSet(); studentsDataSet.Students.AddStudentsRow(1, "Joe Rattz"); studentsDataSet.Students.AddStudentsRow(7, "Anthony Adams"); studentsDataSet.Students.AddStudentsRow(13, "Stacy Sinclair"); studentsDataSet.Students.AddStudentsRow(72, "Dignan Stephens"); string name = studentsDataSet.Students.Where(student => student.Id == 7).Single().Name; Console.WriteLine(name);

Dans cet exemple, un objet StudentsDataSet est instanci et quatre enregistrements Students y sont ajouts. Tout comme dans les exemples du chapitre prcdent, chaque enregistrement correspond un tudiant. Dans la plupart des codes de production rels, cette tape ne sera pas ncessaire, car les donnes proviendront dune base de donnes. Une fois le DataSet typ initialis, une requte LINQ lui est applique. Remarquez quon accde la DataTable Students en tant que proprit de lobjet StudentsDataSet. Remarquez galement que, dans lexpression lambda de la clause Where, on accde la proprit Id directement partir de llment. Ici, il est inutile dappeler la proprit Field du DataRow. Cette facilit dcriture vient du fait que le DataSet est typ. Notez enn quil est possible daccder la proprit Name du rsultat renvoy par loprateur Single. Une fois encore, cette facilit dcriture vient du fait que le DataSet est typ. Voici le rsultat :
Anthony Adams

Tout ceci est bien agrable : la manipulation de DataSets typs sapparente au travail avec des objets et proprits de classes.

Un exemple plus proche de la ralit


Les exemples du chapitre prcdent ont t intentionnellement simplis pour faciliter lapprentissage de lAPI LINQ to DataSet. Nous avons fait en sorte qu travers les diffrents exemples vous vous concentriez essentiellement sur LINQ. En particulier, nous avons vit de prsenter le code ncessaire la connexion sur une base de donnes. Avant de terminer ce chapitre, je voudrais nanmoins vous donner un exemple

370

LINQ to DataSet

Partie IV

plus complet et plus proche de la ralit, dans lequel le DataSet est dni partir dune base de donnes. Je dois avouer que la mise au point dun exemple de taille raisonnable qui lit des donnes dans une base de donnes et utilise lAPI LINQ to DataSet pour les interroger est un peu tir par les cheveux. En effet, nous allons excuter une requte SQL sur les donnes dune base de donnes en utilisant ADO.NET pour obtenir un DataSet. Aprs quoi nous interrogerons ce DataSet avec LINQ to DataSet pour obtenir les donnes recherches. Pourquoi ne pas modier la requte SQL pour obtenir directement les informations recherches ? Eh bien tout simplement dans un but pdagogique ! Dans cet exemple, nous allons travailler avec la base de donnes de la socit Northwind. Cette socit utilise une application qui effectue des requtes sur les commandes. Cette application effectue diffrentes analyses sur les relations entre employs et clients et sur les pays dexpdition des diffrentes commandes. Cette application place les employs, les clients et les pays de destination dans un DataSet. Notre tche va consister effectuer une analyse complmentaire sur ces donnes. titre dexemple, nous allons tablir la liste de toutes les ventes destination de lAllemagne effectues par chacun des employs. Dans cet exemple, nous instancions un SqlDataAdapter puis un DataSet et appelons la mthode Fill du SqlDataAdapter pour remplir le DataSet. Cette tape aurait dj d tre faite par lapplication dont nous venons de parler. Mais, tant donn que nous ne travaillons pas dans un environnement rel, elle sera effectue dans notre code. Une fois lobjet DataSet initialis par la requte SQL, nous lancerons une requte LINQ to DataSet et afcherons le rsultat (voir Listing 11.2).
Listing 11.2 : Un exemple plus proche de la ralit.
string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"; SqlDataAdapter dataAdapter = new SqlDataAdapter( @"SELECT O.EmployeeID, E.FirstName + + E.LastName as EmployeeName, O.CustomerID, C.CompanyName, O.ShipCountry FROM Orders O JOIN Employees E on O.EmployeeID = E.EmployeeID JOIN Customers C on O.CustomerID = C.CustomerID", connectionString); DataSet dataSet = new DataSet(); dataAdapter.Fill(dataSet, "EmpCustShip"); // Ici se termine le code hrit var ordersQuery = dataSet.Tables["EmpCustShip"].AsEnumerable() .Where(r => r.Field<string>("ShipCountry").Equals("Germany")) .Distinct(System.Data.DataRowComparer.Default) .OrderBy(r => r.Field<string>("EmployeeName")) .ThenBy(r => r.Field<string>("CompanyName")); foreach(var dataRow in ordersQuery)

Chapitre 11

Possibilits complmentaires des DataSet

371

{ Console.WriteLine("{0,-20} {1,-20}", dataRow.Field<string>("EmployeeName"), dataRow.Field<string>("CompanyName")); }

Les premires lignes tablissent la connexion avec la base de donnes Northwind. Il se peut que vous ayez modier les paramtres de la connexion pour quils sadaptent votre propre base de donnes. Dans la requte LINQ, nous utilisons les oprateurs AsEnumerable, Distinct et Field<T> (voir chapitre prcdent) et les oprateurs Where, OrderBy et ThenBy de lAPI LINQ to Objects pour crer la requte approprie nos besoins. Jespre que vous apprciez sa juste valeur la facilit avec laquelle tous ces oprateurs dialoguent entre eux. Si la requte fonctionne, nous devrions obtenir la liste de tous les employs qui ont effectu au moins une vente une socit allemande. Cette liste devrait tre classe par ordre alphabtique sur les noms demploys puis sur les socits et ne devrait comprendre aucun doublon. Voici les rsultats :
Andrew FullerDie Wandernde Kuh Andrew FullerKniglich Essen Andrew FullerLehmanns Marktstand Andrew FullerMorgenstern Gesundkost Andrew FullerOttilies Kseladen Andrew FullerQUICK-Stop Andrew FullerToms Spezialitten Anne DodsworthBlauer See Delikatessen Anne DodsworthKniglich Essen Anne DodsworthLehmanns Marktstand Anne DodsworthQUICK-Stop Steven BuchananFrankenversand Steven BuchananMorgenstern Gesundkost Steven BuchananQUICK-Stop

Vous pouvez remarquer que les rsultats ne comprennent aucun doublon. Cela montre une fois de plus lintrt des oprateurs dinitialisation de lAPI LINQ to DataSet. titre dinformation, si vous supprimez largument DataRowComparer.Default dans loprateur Distinct, vous verrez que plusieurs doublons apparaissent dans les rsultats. Le Listing 11.3 donne un exemple utilisant la syntaxe dexpression de requte.
Listing 11.3 : Un exemple plus proche de la ralit utilisant la syntaxe dexpression de requte.
string connectionString = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"; SqlDataAdapter dataAdapter = new SqlDataAdapter( @"SELECT O.EmployeeID, E.FirstName + + E.LastName as EmployeeName, O.CustomerID, C.CompanyName, O.ShipCountry FROM Orders O JOIN Employees E on O.EmployeeID = E.EmployeeID JOIN Customers C on O.CustomerID = C.CustomerID", connectionString);

372

LINQ to DataSet

Partie IV

DataSet dataSet = new DataSet(); dataAdapter.Fill(dataSet, "EmpCustShip"); // All code prior to this comment is legacy code. var ordersQuery = (from r in dataSet.Tables["EmpCustShip"].AsEnumerable() where r.Field<string>("ShipCountry").Equals("Germany") orderby r.Field<string>("EmployeeName"), r.Field<string>("CompanyName") select r) .Distinct(System.Data.DataRowComparer.Default); *foreach (var dataRow in ordersQuery) { Console.WriteLine("{0,-20} {1,-20}", dataRow.Field<string>("EmployeeName"), dataRow.Field<string>("CompanyName")); }

Cette fois-ci, la requte utilise la syntaxe dexpression de requte. Nous avons essay de lui donner la mme allure que dans lexemple prcdent, mais cela na pas t possible. Remarquez la position de loprateur Distinct, en n de la requte. Rappelez-vous que le compilateur nest en mesure de traduire que les oprateurs les plus courants dune requte exprime avec la syntaxe dexpression de requte. Dans cet exemple, il ne sait pas comment traduire loprateur Distinct. Cest la raison pour laquelle cet oprateur ne peut pas tre utilis dans la portion "syntaxe dexpression de requte" de la requte et a t dport la n de la requte. Les rsultats naux des requtes des Listings 11.2 et 11.3 sont identiques, mais ils prsentent une diffrence au niveau des performances. Dans le Listing 11.2, loprateur Distinct est appel juste aprs loprateur Where. Les doublons sont donc limins avant deffectuer le classement. Dans le Listing 11.3, loprateur Distinct est appel la n de la requte. Les doublons ont donc t pris en compte pendant ltape de classement. Cela engendre une charge supplmentaire qui se rvle toutefois indispensable si vous voulez utiliser la syntaxe dexpression de requte.

Rsum
Ce chapitre vous a montr que vous pouviez effectuer des requtes LINQ to DataSet sur des DataSets typs. Sur ce type de DataSets, le code dinterrogation est plus simple maintenir et plus lisible. Vous avez galement pu dcouvrir un exemple dinterrogation LINQ to DataSets plus raliste, fond sur la base de donnes Northwind. LAPI LINQ to DataSet ajoute un autre domaine dutilisation aux requtes LINQ. Par son intermdiaire, linterrogation de DataSets na jamais t aussi simple, et les nombreux codes qui utilisent ces objets ont tout intrt tre remis au got du jour. LAPI LINQ to DataSet a un avantage par rapport lAPI LINQ to SQL : aucun code de classe de base de donnes ne doit tre gnr et compil avant de pouvoir effectuer des requtes. Ceci rend LINQ to DataSet plus dynamique et mieux adapt aux programmes qui ne connaissent la base de donnes quils vont utiliser quau moment de lexcution (les utilitaires de bases de donnes, par exemple).

Chapitre 11

Possibilits complmentaires des DataSet

373

Grce loprateur AsEnumerable, qui permet de crer des squences partir dobjets DataTable, les oprateurs de requte standard LINQ to Objects viennent complter larsenal de LINQ to DataSet, augmentant encore ses possibilits dj immenses. Des oprateurs ont t ajouts dans les classes cls de LINQ to DataSet : DataTable, DataRow et DataColumn. Noubliez pas que de nouveaux prototypes ont t ajouts aux oprateurs Distinct, Union, Intersect, Except et SequentialEqual. Ils sont indispensables pour liminer le problme li la comparaison des DataRows. Chaque fois que vous travaillerez avec des DataSets, DataTables et DataRows, utilisez les prototypes LINQ to DataSet des oprateurs dinitialisation Distinct, Union, Intersect, Except et SequentialEqual dans lesquels un comparateur dgalit est spci en argument. Lorsque vous travaillez avec des valeurs de colonnes, prenez le soin dutiliser les oprateurs Field<T> et SetField<T> pour viter les problmes lis la comparaison et aux valeurs null. En travaillant avec LINQ to DataSet, je me suis rendu compte que javais totalement sous-estim la puissance et lutilit des DataSets. Ils offrent des caractristiques trs intressantes en matire de manipulation et de stockage de donnes. Leurs possibilits de recherche quelque peu limites disparaissent totalement lorsquils sont pauls par LINQ. Dsormais, vous pourrez donc compter avec LINQ pour effectuer des requtes sur vos DataSets. Vous verrez, le codage sera bien plus simple quavant

V
LINQ to SQL

12
Introduction LINQ to SQL
Listing 12.1 : Un exemple lmentaire de mise jour du champ ContactName dun client dans la base de donnes Northwind.
// Cration dun DataContext Northwind db = new Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); // Accs au client LAZYK Customer cust = (from c in db.Customers where c.CustomerID == "LAZYK" select c).Single<Customer>(); // Mise jour du nom du contact cust.ContactName = "Ned Plimpton"; try { // Sauvegarde des modifications db.SubmitChanges(); } // Dtection des conflits daccs concurrentiels catch (ChangeConflictException) { // Rsolution des conflits db.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges); }

INFO Cet exemple ncessite la gnration de classes dentits. Vous trouverez tous les renseignements ncessaires un peu plus loin dans ce chapitre.

Le Listing 12.1 travaille sur la table Customers de la base de donnes Northwind. Il utilise une requte LINQ to SQL pour obtenir lenregistrement dont le champ CustomerID vaut "LAZYK" et pour retourner un objet Customer qui reprsente cet enregistrement. La proprit ContactName de lobjet Customer est alors mise jour et lenregistrement

378

LINQ to SQL

Partie V

est sauvegard dans la base de donnes par lintermdiaire de la mthode SubmitChanges. Ce code nest pas trs long si lon considre quil dtecte les ventuels conits et, le cas chant, les rsout. Appuyez sur Ctrl+F5 pour excuter ce code. Vous nobtenez aucune sortie console mais, si vous vriez le contenu de la base de donnes, vous verrez que le champ ContactName du client LAZYK vaut maintenant "Ned Plimpton".
INFO Cet exemple modie la base de donnes mais ne rtablit pas les donnes originales. Pour que les exemples donns dans ce chapitre fonctionnent correctement, vous devez affecter la valeur "John Steel" au champ ContactName du client LAZYK. Cette modication peut se faire " la main" ou en ajustant le code du Listing 12.1 en consquence.

INFO Cet ouvrage utilise une version tendue de la base de donnes Northwind. Reportez-vous la section "Comment obtenir la version approprie de la base de donnes Northwind" pour avoir toutes les informations ncessaires ce sujet.

Introduction LINQ to SQL


Arriv ce point dans la lecture de cet ouvrage, vous savez comment utiliser LINQ sur des collections de donnes et des tableaux en mmoire, des chiers XML et des DataSets. Nous allons nous intresser ce que beaucoup considrent comme la partie la plus importante de LINQ : LINQ to SQL. Je dis cela parce que la plupart des billets relatifs LINQ dans le forum MSDN sintressent essentiellement LINQ to XML. Je pense que beaucoup de dveloppeurs ne sont pas pleinement conscients que le langage de requte LINQ peut "jouer dans plusieurs cours". Jespre que les chapitres prcdents vous ont convaincu de son clectisme. LAPI (Application Programming Interface) LINQ to SQL est faite pour interfacer les bases de donnes SQL Server. Dans le monde des langages de programmation orients objets, linterfaage dune base de donnes est souvent considr comme le point le plus pineux. Lorsque nous crivons une application, nous modelons les classes pour reprsenter des objets du monde rel : des clients, des comptes, des stocks, etc. Nous avons besoin de rendre ces objets persistants, de telle sorte que, lorsque lapplication est lance une nouvelle fois, ces objets et leurs valeurs ne sont pas perdus. La plupart des bases de donnes utilises en production sont toujours relationnelles. Elles stockent les donnes dans des tables, en tant quenregistrements et non en tant quobjets. Une classe client peut ainsi contenir des adresses et des tlphones stocks dans des collections qui sont des proprits enfants de cette classe. Une fois rendues persistantes, ces donnes seront certainement stockes dans diffrentes tables : une table de clients, une table dadresses et une table de tlphones.

Chapitre 12

Introduction LINQ to SQL

379

Par ailleurs, les types de donnes supports par le langage de programmation diffrent souvent de ceux de la base de donnes. Les dveloppeurs doivent alors crire du code qui sait comment initialiser un objet client partir des tables appropries et comment sauvegarder un objet Customer dans ces mmes tables, en effectuant les conversions de types ncessaires. Cette tape est souvent ennuyeuse et propice aux erreurs. Pour contrer ce problme, li au mappage des donnes relationnelles, de nombreux logiciels ORM (Object-Relational Mapping) ont t crits. LINQ to SQL est lORM dentre de gamme compatible LINQ de Microsoft ddi aux bases de donnes SQL Server. Les autres fabricants de bases de donnes sont dj (ou vont se mettre) au travail pour implmenter leur propre API LINQ. Personnellement, jaimerais bien quune API LINQ to DB2 voie le jour. Je suis sr que beaucoup dentre vous apprcieraient des API LINQ to Oracle, LINQ to MySQL, LINQ to Sybase, etc.
INFO LINQ to SQL ne peut tre utilis quavec SQL Server et SQL Express. Pour utiliser LINQ avec dautres types de bases de donnes, vous devez utiliser des API additionnelles, mises au point par les diffrents fabricants des bases de donnes. Si ces API nexistent pas encore, vous pouvez toujours utiliser LINQ to DataSet.

Quelques lignes auparavant, jai dit que LINQ to SQL tait une implmentation ORM dentre de gamme. Si sa puissance et/ou sa exibilit ne vous sufsent pas, vous pouvez vous tourner vers LINQ to Entities. Cette partie de LINQ ne sera pas traite dans cet ouvrage. Si elle procure plus de puissance et de exibilit, elle complexie galement lcriture. Par ailleurs, elle nest pas aussi mature que LINQ to SQL La plupart des outils ORM limitent la manipulation des bases de donnes celle des objets mtier (entits) correspondants. Cette limitation interdit lutilisation de requtes SQL, pourtant si importantes dans les bases de donnes relationnelles. LINQ to SQL se diffrencie de beaucoup de ses contemporains : il sait en effet tirer parti des objets mapps la base de donnes et offre un langage de requte semblable au SQL.
INFO LINQ to SQL est un ORM dentre de gamme qui permet lutilisation de requtes SQL puissantes.

tant donn que les requtes LINQ to SQL retournent des objets entit (et non de simples champs, des classes de nonentits nommes ou des classes anonymes), vous avez accs toute la puissance de LINQ. Par ailleurs, LINQ to SQL vous permet galement de rechercher les modications effectues sur les enregistrements et de mettre jour la base de donnes, tout en dtectant et en rsolvant dventuels conits daccs concurrentiels et en assurant lintgrit transactionnelle.

380

LINQ to SQL

Partie V

Les premires lignes du Listing 12.1 ont dni une instance de la classe Northwind. Cette classe est drive de la classe DataContext (reportez-vous au Chapitre 16 pour avoir de plus amples informations). Pour linstant, considrez cette instance comme une connexion surcharge la base de donnes. La mise jour de la base de donnes est galement supporte via la mthode SubmitChanges. Quelques lignes plus bas, un des clients de la base de donnes Northwind a t plac dans un objet Customer. Cet objet a t obtenu en instanciant la classe dentit Customer. Cette dernire doit tre crite ou gnre. Dans cet exemple, la classe Customer (tout comme la classe Northwind) a t gnre par lutilitaire SQLMetal. Aprs avoir rcupr le client, la proprit ContactName de lobjet Customer a t mise jour, et la mthode SubmitChanges a t appele pour stocker la modication dans la base de donnes, et ainsi la rendre persistante. Lappel la mthode SubmitChanges a t plac dans un bloc try/catch et nous avons crit un code de traitement pour lexception ChangeConflictException. Cette exception se produit lorsquun conit daccs concurrentiel est dtect. Vous en saurez plus ce sujet en consultant le Chapitre 17. Avant de pouvoir excuter cet exemple ou un des autres de ce chapitre, vous devez crer des classes dentit pour la base de donnes Northwind. Reportez-vous la section intitule "Prrequis pour excuter les exemples" de ce chapitre pour savoir comment procder. LINQ to SQL est un sujet complexe. Pour mettre au point un exemple, de nombreux lments LINQ to SQL sont ncessaires. Dans le premier exemple, au dbut de ce chapitre, nous utilisons une classe drive de DataContext (Northwind) et une classe dentits (Customer). La dtection et la rsolution de conits de concurrence ainsi que la mise jour de la base de donnes sont effectues via la mthode SubmitChanges. Avant de pouvoir expliquer ces diffrents concepts, nous allons vous inculquer quelques connaissances de base qui vous permettront de comprendre les fondements de LINQ to SQL. Rassurez-vous, tous ces concepts seront traits en dtail dans les chapitres suivants. La classe DataContext
DataContext est la classe qui permet dtablir une connexion avec la base de donnes. Elle fournit galement plusieurs services annexes, tels que le contrle didentit, la dtection de modications et le processus de sauvegarde des modications. Tout ceci sera trait en dtail au Chapitre 16. Pour linstant, il vous suft de savoir que cest la classe DataContext qui tablit la connexion avec la base de donnes, qui contrle les modications et met jour la base de donnes lorsque la mthode SubmitChanges est appele.

Lutilisation dune classe drive de DataContext est trs classique en LINQ to SQL. Le nom de la classe drive est gnralement le mme que celui de la base de donnes

Chapitre 12

Introduction LINQ to SQL

381

laquelle elle est relie. Nous y ferons parfois rfrence sous la forme [Your]DataContext, car son nom est li celui de la base de donnes pour laquelle elle a t cre. Dans les exemples de ce chapitre, la classe drive de DataContext appelle "Northwind". Cette classe a en effet t gnre avec loutil en ligne de commande SQLMetal, qui donne automatiquement le nom de la base de donnes la classe DataContext drive. La classe [Your]DataContext, drive de DataContext, aura gnralement une proprit publique Table<T> pour chaque table mappe la base de donnes (o T est le type de la classe dentit instancie pour chaque enregistrement obtenu partir de cette table, et Table<T> est une collection spcialise). Par exemple, tant donn que la base Northwind contient une table Customers, la classe Northwind, drive de la classe DataContext, aura un Table<Customer> nomm Customers. Il est donc possible daccder aux enregistrements de la base de donnes Customers par lintermdiaire des proprits Customers de type Table<Customer> dans la classe Northwind. Le Listing 12.1 en est un exemple : le raccourci dcriture db.Customers donne accs aux enregistrements de la table Customers de la base de donnes Northwind. Classes dentits LINQ to SQL utilise des classes dentits. Chaque classe dentit est gnralement lie une seule table de la base de donnes. Cependant, il est possible, sous certaines circonstances spciques, de mapper toute la hirarchie dune classe dans une simple table. Vous en apprendrez plus ce sujet en vous reportant au Chapitre 18. Nous avons donc des classes dentits lies aux tables dune base de donnes, et les proprits des classes dentit lies aux colonnes des tables. Ces relations classe/table et proprit/colonne sont lessence mme de LINQ to SQL.
INFO Le fondement de LINQ to SQL consiste relier les classes dentit aux tables dune base de donnes et les proprits des classes dentit aux colonnes des tables de la base de donnes.

Ces liaisons peuvent se faire directement dans les chiers de la classe source, en utilisant les bons attributs, ou dans un chier de mappage XML externe. En utilisant un chier de mappage externe, les lments spciques LINQ to SQL peuvent tre maintenus lextrieur du code source. Ceci est trs pratique si vous navez pas accs au code source ou si vous voulez le sparer de LINQ to SQL. Dans la plupart des exemples de ce chapitre, nous utiliserons des classes dentit gnres par loutil en ligne de commande SQLMetal. Dans ces classes dentit gnres, le mappage LINQ to SQL est intgr dans le module source, sous la forme dattributs et de proprits.

382

LINQ to SQL

Partie V

Vous dtecterez sans peine les classes dentit dans les exemples : vous verrez des classes ou des objets dont le nom est le singulier dun nom de table de la base de donnes Northwind. titre dexemple, dans le Listing 12.1 nous utilisons la classe Customer. Ce nom tant le singulier de Customers, et la base de donnes Northwind ayant une table nomme Customers, nous pouvons en dduire que la classe Customer est une classe dentit de la table Customers de la base de donnes Northwind. Loption /pluralize de loutil en ligne de commande SQLMetal est le responsable de cette "singularisation" des classes dentit. Si cette option navait pas t spcie lors de la gnration des classes dentit, la classe dentit de la table Customers aurait t nomme Customers (et non Customer). Cette distinction est importante, pour le cas o vous vous sentiriez confus en lisant dautres crits relatifs LINQ to SQL : en fonction de la faon dont loutil SQLMetal a t utilis, les noms des classes dentit peuvent tre au pluriel ou au singulier. Associations Le terme "association" dsigne la relation entre une cl primaire et une cl trangre, utilises pour relier deux classes dentit. Dans une relation un--plusieurs, par exemple, une association consiste en une classe parent dote dune cl primaire et une collection de classes enfants contenant des cls trangres. Cette collection est stocke dans une variable membre prive de type EntitySet<T>, o T est le type de la classe dentit enfant. titre dexemple, dans la classe dentit Customer, gnre par loutil en ligne de commande SQLMetal pour la base de donnes Northwind, le membre priv _Orders, de type EntitySet<Order>, contient tous les objets Order pour un objet Customer spcique :
private EntitySet<Order> _Orders;

SQLMetal gnre galement la proprit publique Orders, an daccder la collection prive _Orders. De lautre ct de la relation, la classe enfant (celle dans laquelle se trouve la cl trangre) contient une rfrence vers la classe parent, puisquil sagit dune relation un-plusieurs. Cette rfrence est mmorise dans une variable membre prive de type EntityRef<T>, o T est le type de la classe parent. La classe dentit Order contient la variable membre prive _Customer de type EntityRef<Customer> :
private EntityRef<Customer> _Customer;

Ici encore, loutil SQLMetal a gnr la proprit Customer pour donner accs au parent.

Chapitre 12

Introduction LINQ to SQL

383

Lassociation entre les cls primaire et trangre ainsi que la direction de la relation sont dnies par des attributs et des proprits dattributs dans le module source des classes dentit gnres. Cette association permet daccder aux classes enfants du parent et donc aux enregistrements de la base de donnes aussi simplement que sil sagissait de proprits de la classe parent. De la mme faon, laccs la classe parent dun enfant est aussi simple quaccder une proprit dune classe enfant. Dtection de conit daccs concurrentiel Un des services apprciables du DataContext est le traitement associ aux modications : lorsque vous essayez de mettre jour votre base de donnes en appelant la mthode SubmitChanges de lobjet DataContext, une dtection de conit daccs concurrentiels est automatiquement lance. Si un conit est dtect, une exception ChangeConflictException est leve. Chaque fois que vous appelez la mthode SubmitChanges, vous devez donc linclure dans un bloc try/catch an de traiter une ventuelle exception ChangeConflictException. Reportez-vous au Listing 12.2 pour avoir un exemple de dtection de conit. Nous entrerons bien plus dans les dtails sur la dtection et la rsolution des conits au Chapitre 17. Dans un but de concision et de clart, la plupart des exemples des chapitres ddis LINQ to SQL nincluront aucun code de dtection et de rsolution derreur. Cependant, dans un code de production rel, ce code devrait tre systmatiquement mis en place Rsolution de conit daccs concurrentiel Une fois quun conit a t dtect, vous devez le rsoudre. Plusieurs techniques peuvent tre utilises. Le Listing 12.1 utilise la technique la plus lmentaire. Ici, nous nous contentons dappeler la mthode ResolveAll (collection ChangeConflicts de la classe drive de DataContext) lorsquune exception ChangeConflictException est leve. Rappelons une fois encore que, dans un but de concision et de clart, la plupart des exemples des chapitres ddis LINQ to SQL nincluront aucun code de dtection et de rsolution derreur. Cependant, dans un code de production rel, ce code devrait tre systmatiquement mis en place. Le cas chant, reportez-vous au Chapitre 17 pour avoir de plus amples dtails sur la rsolution de conits.

Prrequis pour excuter les exemples


La plupart des exemples des chapitres ddis LINQ to SQL utilisant la base de donnes Northwind, fournie en exemple par Microsoft, nous avons besoin de classes dentit et de chiers de mappage pour cette base de donnes.

384

LINQ to SQL

Partie V

Obtenir la version approprie de la base de donnes Northwind Plusieurs petites choses manquent dans la version originale de la base de donnes Northwind de Microsoft (les fonctions table-valued et scalar-valued, par exemple) pour que nous puissions montrer toutes les facettes de LINQ to SQL. Nous allons donc utiliser une version tendue de cette base de donnes. Vous pouvez tlcharger la version approprie de la base de donnes Northwind dans la section "Book Extras" de la page suivante, sur le site dApress :
http://www.apress.com/book/bookDisplay.html?bID=10241

Vous pouvez galement vous rendre sur le site LINQDev.com et lancer le tlchargement depuis la section "Obtain the Northwind Database" :
http://www.linqdev.com

Si vous tlchargez la base de donnes depuis LINQDev.com, assurez-vous que vous tlchargez la version tendue et non la version originale de la base de donnes. Gnration des classes dentit de la base de donnes Northwind La gnration de classes dentit na pas encore t tudie. Je vais donc vous dire comment procder, sans toutefois entrer dans les dtails. Reportez-vous au Chapitre 13 pour en savoir plus ce sujet. Pour commencer, assurez-vous que vous avez tlcharg la version tendue de la base de donnes Northwind. Ouvrez une fentre Invite de commandes de Visual Studio. Pour ce faire, cliquez successivement sur le bouton Dmarrer, Tous les programmes, Microsoft Visual Studio 2008, Visual Studio Tools puis Invite de commandes de Visual Studio 2008. Dplacezvous dans le dossier o les classes dentit et le chier de mappage doivent tre gnrs. Nous allons par exemple nous dplacer dans la racine du disque C en tapant :
cd \

Si vous voulez gnrer les classes dentit de la base de donnes Northwind sans les attacher au pralable la base, utilisez la commande suivante :
sqlmetal /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views <chemin vers le fichier Northwind.mdf>

ATTENTION Faites particulirement attention au nom et la casse du chier MDF spci dans la ligne de commande. Le nom et la casse de la classe gnre par SQLMetal seront en effet identiques ceux passs dans la ligne de commande. Si vous choisissez un autre nom que [chemin]\Northwind.mdf ([chemin]\northwind.mdf ou [chemin]\NorthWind.mdf, par exemple), aucun des exemples ne fonctionnera !

Chapitre 12

Introduction LINQ to SQL

385

Pour crer des classes dentit partir du chier Northwind.mdf, situ dans la racine du disque C, entrez la commande suivante :
sqlmetal /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views "C:\Northwind.mdf"

Lexcution de cette commande fabriquera le module de classe dentit Northwind.cs dans le dossier courant. Si vous voulez gnrer les classes dentit de la base de donnes Northwind, dj attache SQL Server, utilisez la commande suivante :
sqlmetal /server:<server> /user:<user> /password:<password> /database:Northwind /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views

Pour crer les classes dentit de la base de donnes Northwind attache SQLExpress, utilisez la commande suivante :
sqlmetal /server:.\SQLExpress /database:Northwind /namespace:nwind /code:Northwind.cs /pluralize /functions /sprocs /views

INFO En fonction de votre environnement de travail, il se peut que vous deviez spcier un nom dutilisateur (option /user:[username]) et un mot de passe (option password:[password]) dans la ligne de commande. Reportez-vous la section intitule "SQLMetal" du Chapitre 13 pour avoir plus de dtails ce sujet.

Aprs avoir tap une de ces commandes, SQLMetal gnre le code source dans le dossier courant, dans un chier nomm Northwind.cs. Toutes les options de ce programme seront commentes au chapitre suivant. Insrez le chier Northwind.cs ainsi gnr dans votre projet en lajoutant en tant que "nouvel lment". Vous pouvez maintenant vous servir de LINQ to SQL sur la base de donnes Northwind en utilisant les classes dentit du chier Northwind.cs.
ASTUCE Vous pouvez faire des modications dans le chier dentit, mais sachez quelles seront perdues si vous devez le gnrer une nouvelle fois. Vous pourriez par exemple vouloir ajouter une logique mtier en dnissant de nouvelles mthodes dans les classes dentit. Mais, plutt que modier le chier gnr, pensez tirer prot des classes partielles de C# 2.0 en plaant les nouvelles proprits et mthodes dans un module source annexe.

Gnration du chier de mappage XML de la base de donnes Northwind Certains exemples ont galement besoin dun chier de mappage. Ici encore, nous allons utiliser SQLMetal. Dans la mme fentre Invite de commandes et partir du mme dossier, excutez la commande suivante :
sqlmetal /map:northwindmap.xml "C:\Northwind.mdf" /pluralize /functions /sprocs / views /namespace:nwind

386

LINQ to SQL

Partie V

Comme prcdemment, faites bien attention la casse du chier MDF. Cette commande gnre le chier northwindmap.xml dans le dossier courant.
INFO Cette commande afche sur lcran le code insr dans le chier de mappage XML. Toutes ces lignes de code afches sur votre cran sont donc tout fait normales.

Utilisation de lAPI LINQ to SQL


Pour pouvoir utiliser lAPI LINQ to SQL, vous devez ajouter lassembly System.Data.Linq.Dll dans votre projet, si elle ne sy trouve pas dj. De mme, si les directives using suivantes ne sont pas dj prsentes, vous devez les ajouter dans votre module source :
using System.Data.Linq; using System.Linq;

Enn, vous devez ajouter une clause using concernant lespace de noms dans lequel les classes dentit ont t gnres :
using nwind;

IQueryable<T>
Dans la plupart des exemples des chapitres ddis LINQ to SQL, nous travaillerons avec des squences de type IQueryable<T>, o T est le type dune classe dentit. Ces squences sont gnralement retournes par les requtes LINQ to SQL. Elles fonctionnent souvent comme les squences IEnumerable<T>, et, cela nest pas une concidence : linterface IQueryable<T> implmente linterface IEnumerable<T>. Voici la dnition de linterface IQueryable<T> :
interface IQueryable<T>: IEnumerable<T>, IQueryable

Grce cet hritage, les squences IQueryable<T> peuvent tre traites comme des squences IEnumerable<T>.

Quelques mthodes communes


Un grand nombre dexemples des chapitres ddis LINQ to SQL ont tendance devenir rapidement complexes. Pour dmontrer un conit, il est ncessaire deffectuer des modications dans la base de donnes en dehors de LINQ to SQL. Parfois, il est galement ncessaire dextraire des donnes sans utiliser LINQ to SQL. Pour mettre en valeur le code LINQ to SQL et ne pas tre gn par des dtails annexes sans pour autant scarter de la ralit , nous avons dni quelques mthodes communes qui seront utilises dans les exemples.

Chapitre 12

Introduction LINQ to SQL

387

Assurez-vous que ces mthodes ont t ajoutes vos modules sources lorsque vous testerez les exemples des chapitres LINQ to SQL. La mthode GetStringFromDb() Cette mthode se rvlera bien pratique par la suite. Elle permet dextraire une chane dune base de donnes en utilisant ADO.NET. Cela nous permettra dexaminer ce qui se trouve dans la base de donnes et de le comparer ce que LINQ to SQL afche : La mthode GetStringFromDb permet dextraire une chane en utilisant ADO.NET
static private string GetStringFromDb( System.Data.SqlClient.SqlConnection sqlConnection, string sqlQuery) { if (sqlConnection.State != System.Data.ConnectionState.Open) { sqlConnection.Open(); } System.Data.SqlClient.SqlCommand sqlCommand = new System.Data.SqlClient.SqlCommand(sqlQuery, sqlConnection); System.Data.SqlClient.SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(); string result = null; try { if (!sqlDataReader.Read()) { throw (new Exception( String.Format("Exception inattendue pendant lexcution de la requte [{0}].", sqlQuery))); } else { if (!sqlDataReader.IsDBNull(0)) { result = sqlDataReader.GetString(0); } } } finally { // Toujours appeler Close quand la lecture est faite sqlDataReader.Close(); } return (result); }

La mthode GetStringFromDb demande deux arguments : un objet SqlConnection et une chane qui contient une requte SQL. La mthode vrie que la connexion est ouverte. Dans le cas contraire, elle louvre. Ensuite, un objet SqlCommand est cr en passant la requte et la connexion dans le constructeur. Un objet SqlDataReader est alors obtenu en appelant la mthode ExecuteReader sur lobjet SqlCommand. Le SqlDataReader est lu en appelant la mthode Read.

388

LINQ to SQL

Partie V

Si une donne a t lue et si la premire valeur de la colonne est diffrente de null, cette valeur est lue avec la mthode GetString. Enn, le SqlDataReader est ferm et la premire valeur de la colonne est retourne lappelant. La mthode ExecuteStatementInDb() De temps autre, il sera ncessaire dexcuter des commandes insert, update et delete en ADO.NET pour modier ltat de la base de donnes sans utiliser LINQ to SQL. Pour ce faire, nous utiliserons la mthode ExecuteStatementInDb : La mthode ExecuteStatementInDb excute des commandes Insert, Update et Delete en ADO.NET
static private void ExecuteStatementInDb(string cmd) { string connection = @"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"; System.Data.SqlClient.SqlConnection sqlConn = new System.Data.SqlClient.SqlConnection(connection); System.Data.SqlClient.SqlCommand sqlComm = new System.Data.SqlClient.SqlCommand(cmd); sqlComm.Connection = sqlConn; try { sqlConn.Open(); Console.WriteLine("Excution de la commande SQL sur la base de donnes avec ADO.NET ..."); sqlComm.ExecuteNonQuery(); Console.WriteLine("Base de donnes mise jour"); } finally { // Fermeture de la connexion sqlComm.Connection.Close(); } }

La mthode ExecuteStatementInDb demande un argument : une chane contenant une commande SQL. Un objet SqlConnection est cr, suivi par un objet SqlCommand. Le premier est affect au second. Lobjet SqlConnection est alors ouvert et la commande SQL, excute en appelant la mthode ExecuteNonQuery de lobjet SqlCommand. Enn, lobjet SqlConnection est ferm.

Rsum
Ce chapitre constitue une introduction LINQ to SQL et un certain nombre de termes qui y sont relatifs. Par exemple, les objets DataContext, les classes dentit, les associations, la dtection et la rsolution des conits daccs concurrentiel.

Chapitre 12

Introduction LINQ to SQL

389

Vous y avez galement appris gnrer les classes dentit et le chier de mappage pour la version tendue de la base de donnes Northwind. Les classes dentit seront abondamment utilises dans les exemples LINQ to SQL. Enn, vous avez pu dcouvrir deux mthodes communes qui viendront en complment des instructions LINQ to SQL. Au chapitre suivant, vous allez dcouvrir quelques astuces et voir comment utiliser des outils ddis LINQ to SQL.

13
Astuces et outils pour LINQ to SQL
Le chapitre prcdent a introduit LINQ to SQL et la terminologie qui lui est propre. Vous y avez appris gnrer les classes dentits ncessaires la plupart des exemples relatifs LINQ to SQL. Vous avez galement dcouvert plusieurs mthodes communes qui seront utiles de nombreux exemples des Chapitres 12 17. Dans ce chapitre, vous allez dcouvrir des astuces qui, je lespre, vous seront utiles lorsque vous utiliserez LINQ to SQL. Vous ferez galement connaissance avec quelquesuns des outils qui rendent LINQ to SQL si agrable utiliser.

Introduction aux astuces et aux outils pour LINQ to SQL


Je tiens rappeler ici que, pour pouvoir excuter les exemples de ce chapitre, vous devez au pralable satisfaire les conditions exposes dans la section "Prrequis pour excuter les exemples" du chapitre prcdent. En particulier, vous devez avoir tlcharg la version tendue de la base de donnes Northwind et avoir gnr les classes dentit correspondantes. Dans ce chapitre, tant donn que nous allons mettre en uvre du code qui utilise les classes dentit gnres par SQLMetal et par le Concepteur Objet/Relationnel, nous nindiquerons pas la directive using nwind dans le code des exemples. Cet espace de noms sera spci explicitement chaque fois que cela se rvlera ncessaire. Cette dmarche est ncessaire, car nous voulons contrler quelle classe dentit Customer est rfrence dans chacun des exemples. Par dfaut, le Concepteur Objet/Relationnel dnit une classe qui porte le nom du projet. tant donn que les exemples existent dj dans lespace de noms du projet, il ne sera pas ncessaire de le spcier nouveau. En revanche, ceci nest plus vrai lorsquun exemple utilise les classes dentit gnres par SQLMetal.

392

LINQ to SQL

Partie V

INFO Dans les exemples de ce chapitre, il ne sera pas ncessaire de dclarer une directive using nwind;.

Astuces
Pour ne pas droger ce qui a t fait dans les chapitres prcdents, nous allons vous prsenter quelques astuces qui mettent en uvre des concepts qui nont pas encore t abords. Vous devez en effet connatre ces astuces avant den avoir besoin, et pas aprs avoir dcortiqu les thories qui les animent. La proprit DataContext.Log Nous allons rappeler quelques-unes des astuces relatives LINQ to SQL prsentes au Chapitre 1. Une de ces astuces a t prsente dans la section "Utiliser le Log du DataContext". Elle vous a montr comment utiliser la proprit Log dun objet DataContext pour avoir un aperu des requtes traduites en SQL. Ceci peut tre trs utile, non seulement des ns de dbogage, mais galement pour analyser les performances. Vous pouvez par exemple dcouvrir que vos requtes LINQ to SQL vont tre traduites en des requtes SQL peu efcaces. Ou encore quen raison du chargement diffr des classes dentit associes vous effectuez bien plus de requtes SQL que le strict ncessaire. Le cas chant, la proprit DataContext.Log vous rvlera ce type dinformation. Pour pouvoir tirer parti de cette fonctionnalit, il vous suft daffecter la proprit DataContext.Log un objet System.IO.TextWriter : Console.Out, par exemple (voir Listing 13.1).
Listing 13.1 : Un exemple dutilisation de la proprit DataContext.Log.
nwind.Northwind db = new nwind.Northwind(@"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind"); db.Log = Console.Out; var custs = from c in db.Customers where c.Region == "WA" select new { Id = c.CustomerID, Name = c.ContactName }; foreach (var cust in custs) { Console.WriteLine("{0} - {1}", cust.Id, cust.Name); }

tant donn que nous utiliserons des classes dentit fabriques par SQLMetal et par le Concepteur Objet/Relationnel, nous aurons affaire deux classes Customer diffrentes. Comme il a t dit prcdemment, aucune directive using ne sera ajoute dans les exemples de ce chapitre, an dter toute ambigut en ce qui concerne les classes

Chapitre 13

Astuces et outils pour LINQ to SQL

393

dentit utilises. Dans le cas du Listing 13.1, nous devons cependant spcier lespace de noms nwind de la classe Northwind, car nous utiliserons le code de la classe dentit gnre par SQLMetal. Comme vous avez pu le voir, le Listing 13.1 se contente daffecter lobjet Console.Out la proprit Log de lobjet NorthwindDataContext. Voici les rsultats de ce code :
SELECT [t0].[CustomerID], [t0].[ContactName] FROM [dbo].[Customers] AS [t0] WHERE [t0].[Region] = @p0 -- @p0: Input String (Size = 2; Prec = 0; Scale = 0) [WA] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1 LAZYK - John Steel TRAIH - Helvetius Nagy WHITC - Karl Jablonski

Ces rsultats contiennent le code SQL gnr par la requte LINQ to SQL. Remarquez que ce code utilise des paramtres. En utilisant LINQ to SQL, vous tes donc automatiquement protg des attaques de type "injection de commandes SQL".
ATTENTION Si vos rsultats laissent apparatre que le nom associ au client LAZYK est "Ned Plimpton" et non "John Steel", vous avez certainement excut le code du Listing 12.1 sans restaurer la donne qui a t affecte. Vous devriez rgler ce problme avant dexcuter les exemples suivants. Reportez-vous si ncessaire au Chapitre 12 pour savoir comment procder.

Dans les chapitres suivants, vous verrez comment utiliser le Log du DataContext pour dtecter et rsoudre des problmes de performances potentiels. La mthode GetChangeSet() La mthode GetChangeSet de lobjet DataContext permet de connatre tous les objets entit qui ont t modis et qui doivent tre mmoriss dans la base de donnes lorsque la mthode SubmitChanges est appele. Cette mthode est utile en ce qui concerne le Log du DataContext et le dbogage. Vous en saurez plus son sujet en vous reportant au Chapitre 16. Utilisation de classes partielles ou de chiers de mappage Lors de lutilisation dun outil ORM, une des principales difcults consiste en la gestion des modications dans la base de donnes. Si vous conservez la logique de vos classes mtier et de LINQ to SQL dans les mmes modules, vous aurez beaucoup de mal les maintenir lorsque la base de donnes est modie. Pensez placer votre logique mtier dans un module diffrent de celui des classes dentit. En utilisant des classes partielles pour sparer vos attributs de base de donnes LINQ to SQL de votre

394

LINQ to SQL

Partie V

logique mtier, vous minimiserez la ncessit dajouter du code dans les classes dentit. Une autre solution consisterait utiliser des chiers de mappage XML externes pour dcoupler les classes mtier et le mappage LINQ to SQL. Ce chier XML relierait les objets mtier la base de donnes sans compter sur les attributs LINQ to SQL. Vous en saurez plus au sujet des chiers de mappage dans la section intitule "Schma de chier de mappage externe XML" du Chapitre 15 et dans la section "La classe DataContext" du Chapitre 16. Utilisation de mthodes partielles Si les mthodes partielles sont apparues assez tardivement dans le langage C#, vous ne devez pas pour autant les ignorer. Vous les utiliserez pour traiter certains vnements qui ont lieu dans les classes dentit. Si vous nimplmentez aucune mthode partielle (et cest l toute leur "beaut"), le compilateur nmet aucun code pour les activer. Reportez-vous la section "Appel des mthodes partielles appropries" du Chapitre 15 pour en savoir plus sur lutilisation des mthodes partielles dans les classes dentit.

Outils
Cette section va vous prsenter plusieurs outils qui vous faciliteront la vie et acclreront votre adoption de LINQ to SQL. Bien quun peu prmature, cette tape me semble ncessaire, tout au moins pour que vous sachiez que ces outils existent, mme si vous ne les utilisez pas encore. SQLMetal Si vous navez pas encore de classes mtier, la faon la plus simple de crer les classes dentit dune base de donnes consiste utiliser loutil SQLMetal. Vous le trouverez dans le dossier %windir%\Microsoft.NET\Framework\v3.5. Il suft dindiquer le nom dune base de donnes SQLMetal pour quil gnre toutes les classes dentit ncessaires LINQ to SQL. SQLMetal fonctionne en ligne de commande et ne dispose daucune interface utilisateur. Pour avoir une ide des options utilisables, commencez par ouvrir une fentre Invite de commandes Visual Studio. Pour ce faire, cliquez successivement sur Dmarrer, Tous les programmes, Microsoft Visual Studio 2008, Visual Studio Tools puis Invite de commandes de Visual Studio 2008. Dans la fentre Invite de commandes, tapez sqlmetal et appuyez sur la touche Entre du clavier :
sqlmetal

Chapitre 13

Astuces et outils pour LINQ to SQL

395

Cette commande provoque lafchage suivant dans la fentre Invite de commandes :


Microsoft (R) Database Mappage Generator 2008 version 1.00.21022 pour Microsoft (R) .NET Framework version 3.5 Copyright (C) Microsoft Corporation.Tous droits rservs. SqlMetal [options] [<fichier_entre>] Gnre un code et un mappage pour le composant LINQ to SQL du .NET Framework. SqlMetal peut effectuer les oprations suivantes : Gnrer des attributs de code source et de mappage ou un fichier de mappage partir dune base de donnes. Gnrer un fichier dbml intermdiaire pour le personnaliser partir de la base de donnes. Gnrer des attributs de code et de mappage ou un fichier de mappage partir dun fichier dbml. Options: /server:<name> /database:<name> /user:<name> /password:<password> /conn:<connection string>

/timeout:<seconds>

Nom du serveur de base de donnes. Catalogue de bases de donnes sur le serveur. ID utilisateur de connexion (valeur par dfaut : utilisation de lauthentification Windows). Mot de passe de connexion (par dfaut : utilisation de lauthentification Windows). Chane de connexion de base de donnes. Ne peut pas tre utilise avec les options /server, /database, /user et /password. Valeur de dlai dattente utiliser lorsque SqlMetal accde la base de donnes (valeur par dfaut : 0, soit linfini). Extraire des vues de base de donnes. Extraire des fonctions de base de donnes. Extraire des procdures stockes. Sortie en dbml. Ne peut tre utilis avec loption /map. Sortie en tant que code source. Ne peut tre utilise avec loption /dbml. Gnrer un fichier de mappage mais pas des attributs. Ne peut tre utilis avec loption /dbml. Langage du code source : VB ou C# (provient par dfaut de lextension du nom de fichier du code). Espace de noms du code gnr (valeur par dfaut : aucun espace de noms). Nom de la classe du contexte de donnes (provient par dfaut du nom de la base de donnes). Classe de base des classes dentit dans le code gnr (valeur par dfaut : les entits nont aucune classe de base). Mettez automatiquement au pluriel ou au singulier des noms de membres et de classes daprs des rgles de langue anglaise. Gnrer des classes srialisables : None ou Unidirectional (valeur par dfaut : None). Type de fournisseur: SQLCompact, SQL2000 ou SQL2005. (valeur par dfaut : le fournisseur est dtermin au moment de lexcution). Peut tre un fichier mdf SqlExpress, un fichier sdf SqlCE ou un fichier dbml intermdiaire.

/views /functions /sprocs /dbml[:file] /code[:file] /map[:file]

/language:<language> /namespace:<name> /context:<type> /entitybase:<type>

/pluralize

/serialization:<option> /provider:<type>

<fichier_entre>

Crer du code partir de SqlServer : SqlMetal /server:myserver /database:northwind /code:nwind.cs /namespace:nwind

396

LINQ to SQL

Partie V

Gnrer un fichier dbml intermdiaire partir de SqlServer : SqlMetal /server:myserver /database:northwind /dbml:northwind.dbml /namespace:nwind Gnrer du code avec du mappage externe partir dun fichier dbml : SqlMetal /code:nwind.cs /map:nwind.map northwind.dbml Gnrer un fichier dbml partir dun fichier sdf SqlCE : SqlMetal /dbml:northwind.dbml northwind.sdf Gnrer un fichier dbml partir dun serveur local SqlExpress : SqlMetal /server:.\sqlexpress /database:northwind /dbml:northwind.dbml Gnrer un fichier dbml laide dune chane de connexion dans la ligne de commande : SqlMetal /conn:"server=myserver; database=northwind" /dbml:northwind.dbml

Comme vous pouvez le voir, plusieurs exemples sont donns dans la fentre Invite de commandes. La plupart des options se comprennent delles-mmes. Le Tableau 13.1 donne quelques explications complmentaires pour les options les plus complexes.
Tableau 13.1 : Les options de loutil SQLMetal

Option/Exemple /server:<name> /server:.\SQLExpress

Description Nom du serveur sur lequel se trouve la base de donnes utiliser. Si cette option nest pas prsente, SQLMetal utilise la valeur localhost/sqlexpress par dfaut. Pour que SQLMetal gnre les classes dentit partir dun chier MDF, omettez cette option ainsi que / database, et spciez le nom complet du chier MDF la n de la commande.

/database:<name> /database:Northwind

Nom de la base de donnes pour lequel vous voulez gnrer les classes dentit. Pour que SQLMetal gnre les classes dentit partir dun chier MDF, omettez cette option ainsi que / server, et spciez le nom complet du chier MDF la n de la commande.

/user:<name> /user:sa /password:<password> /password:1590597893 /conn:<chaine de connexion> /conn:"Data Source=.\SQLEXPRESS;Initial Catalog=Northwind;Integrated Security=SSPI;"

Nom dutilisateur permettant de se connecter la base de donnes. Mot de passe permettant de se connecter la base de donnes. Chane de connexion la base de donnes. Vous pouvez utiliser cette option pour regrouper le nom du serveur, le nom de la base de donnes, le nom dutilisateur et le mot de passe.

Chapitre 13

Astuces et outils pour LINQ to SQL

397

Tableau 13.1 : Les options de loutil SQLMetal (suite)

Option/Exemple /timeout:<seconds> /timeout:120

Description Dlai dattente en secondes pour accder la base de donnes. Si cette option nest pas spcie, aucun dlai dattente nest utilis. Alors que nous crivons ces lignes, cette option nest pas encore supporte. Quoi quil en soit, le code gnr ne dpend aucunement de cette option. Vous pouvez vrier si elle fonctionne dans votre Visual Studio 2008. Dans la ngative, vous pouvez toujours utiliser la proprit CommandTimeout de la classe DataContext, ou encore appeler la mthode DataContext.GetCommand et dnir un dlai dattente pour une requte particulire. Consultez le Listing 16.29, au Chapitre 16, pour en savoir plus ce sujet.

/views /views

Spciez cette option si vous voulez que SQLMetal gnre le code ncessaire an que les proprits Table<T> et les classes dentit supportent les vues de la base de donnes. Spciez cette option pour que SQLMetal gnre des mthodes qui permettront dappeler les fonctions de base de donnes dnies par lutilisateur. Spciez cette option pour que SQLMetal gnre des mthodes qui permettront dappeler les procdures stockes. Cette option spcie le nom dun chier intermdiaire DBML. Par son intermdiaire, vous pourrez contrler les noms des classes et des proprits des classes dentit gnres. Si vous utilisez cette option, ditez le chier DBML gnr, faites les modications ncessaires et demandez la dnition du module de code source en appelant SQLMetal sur le chier DBML modi et en spciant loption /code. Vous pouvez galement ouvrir le chier DBML gnr dans le Concepteur Objet/Relationnel, faire les modications ncessaires par son intermdiaire et demander au Concepteur de gnrer le code source correspondant. Cette option ne doit pas tre utilise conjointement avec /map.

/functions /functions /sprocs /sprocs /dbml[:file] /dbml:Northwind.dbml

398

LINQ to SQL

Partie V

Tableau 13.1 : Les options de loutil SQLMetal (suite)

Option/Exemple /code[:file] /code:Northwind.cs

Description Nom du chier cr par SQLMetal et contenant le DataContext driv et les classes dentit dans le langage de programmation spci. Cette option ne peut tre utilise conjointement /dbml. Si vous spciez les options /code et /map dans une mme invocation SQLMetal, le code gnr ne contiendra pas les attributs LINQ to SQL. Bien entendu, vous utiliserez le chier de mappage et le chier de code gnrs pour tre en mesure dutiliser LINQ to SQL.

/map[:file] /map:northwindmap.xml

Demande la gnration dun chier de mappage XML ( opposer loption /code, qui demande la cration dun chier de code). Ce chier de mappage XML externe peut tre charg lors de linstanciation du DataContext. Cela permet dutiliser LINQ to SQL sans quaucun code source LINQ to SQL ne doive tre compil avec votre code. Si vous spciez les options /code et /map dans une mme invocation SQLMetal, le code gnr ne contiendra pas les attributs LINQ to SQL. Bien entendu, vous utiliserez le chier de mappage et le chier de code gnrs pour tre en mesure dutiliser LINQ to SQL.

/language:<langage> /language:C#

Cette option dnit le langage dans lequel SQLMetal doit crire le code source. Les valeurs possibles sont csharp, C# et VB. Si vous omettez cette option, SQLMetal dduira le langage de lextension du chier de code source.

/namespace:<nom> /namespace:nwind /context:<type> /context:Northwind

Indique lespace de noms duquel la classe drive de DataContext et les classes dentit dpendront. Nom de la classe gnre, drive de la classe DataContext. Si cette option est omise, le nom de la classe sera le mme que celui de la base de donnes.

/entitybase:<type> Nom de la classe de base pour toutes les classes dentit /entitybase:MyEntityClassBase gnres. Si cette option est omise, les classes dentit nauront aucune classe de base.

Chapitre 13

Astuces et outils pour LINQ to SQL

399

Tableau 13.1 : Les options de loutil SQLMetal (suite)

Option/Exemple /pluralize /pluralize

Description Lorsque cette option est spcie, SQLMetal met le nom des tables au pluriel et le nom des classes dentit mappes au singulier. Par exemple, pour une table Customers, la classe dentit gnre aura pour nom Customer, et un Table<Customer> nomm Customers sera gnr. Ainsi, la table Customers contiendra des objets Customer, ce qui, grammaticalement parlant, est tout fait correct. Si cette option nest pas spcie, la classe dentit et le Table<Customers> porteront le nom Customers. La table Customers contiendra des objets Customers, ce qui, grammaticalement parlant, est incorrect.

/serialization:<option> /serialization:none

Indique si les attributs de srialisation doivent tre gnrs dans les classes. Les valeurs possibles sont None et Unidirectional. Si cette option nest pas spcie, les attributs de srialisation ne seront pas inclus. Indique la classe du fournisseur de la base de donnes. Les valeurs possibles sont SQLCompact, SQL2000 et SQL2005. SQLMetal gnre un attribut Provider qui indique la valeur spcie dans cette option. Les classes des fournisseurs se trouvent dans lespace de noms System.Data.Linq.SqlClient. Noubliez pas de spcier cet espace de noms si vous utilisez cette option.

/provider:<type> /provider:SQL2005

titre dinformation, sachez que les options /dbml, /code et /map peuvent tre spcies sans aucun nom de chier. Dans ce cas, le code ou XML gnr sera afch dans la console.
Fichier de mappage XML ou chier intermdiaire DBML ? SQLMetal vous permet de spcier deux diffrents types de chiers XML, ce qui peut se rvler assez droutant. Le premier correspond loption /map et le second, loption /dbml.

Loption /map cre un chier de mappage externe XML destin tre charg linstanciation du DataContext. Cette option est une alternative la gnration ou lcriture manuelle dun module source qui contient les attributs LINQ to SQL compiler. Avec cette approche, le code source ne comprend et ne fait rfrence aucun code LINQ to SQL spcique la base de donnes. Cela autorise une consommation "quelque peu dynamique" de la base de donnes, puisque vous navez besoin daucun code prgnr

400

LINQ to SQL

Partie V

et compil. Jai bien dit "quelque peu dynamique". En effet, le code doit connatre le nom des tables et des champs, sans quoi il ne serait pas en mesure deffectuer des requtes. Le chier de mappage externe indique LINQ to SQL le nom des tables, colonnes et procdures stockes aves lesquelles il peut interagir, ainsi que le nom des classes, des proprits et des mthodes auxquelles elles sont mappes. Loption /dbml cre un chier intermdiaire DBML (XML). Vous pouvez diter ce chier an de choisir le nom des classes et proprits pour les classes dentit gnrer. Vous devez alors excuter une nouvelle fois SQLMetal en lui indiquant non pas le nom de la base de donnes, mais le nom du chier DBML, et en utilisant loption /code. Vous pouvez galement ouvrir le chier DBML gnr dans le Concepteur Objet/Relationnel, faire les modications ncessaires par son intermdiaire et demander au Concepteur de gnrer le code source correspondant. Pour ajouter la confusion, les schmas des deux types de chiers XML gnrs par SQLMetal sont assez proches. Si ncessaire, reportez-vous au Chapitre 15 pour avoir des informations complmentaires sur les chiers de mappage XML.
Travailler avec des chiers intermdiaires DBML Comme il a t dit dans la section prcdente, le chier intermdiaire DBML permet de contrler le nom des classes et des proprits, en intervenant manuellement entre lextraction du schma de la base de donnes et la gnration de la classe dentit. Si vous navez que faire de cette possibilit, les chiers DBML ne sont pas pour vous. Dans la suite, nous allons supposer que vous avez besoin de choisir le nom des classes et des proprits des classes dentit.

Supposons que vous ayez attach la base de donnes tendue Northwind votre serveur SQL. Vous dnirez le chier intermdiaire DBML avec la commande suivante :
sqlmetal /server:.\SQLExpress /database:Northwind /pluralize /sprocs /functions / views /dbml:Northwind.dbml

INFO Lutilisation des options /server et /database dans la commande sqlmetal ncessite que la base de donnes soit attache au serveur SQL.

Il se peut galement que vous ayez spcier les options /user et /password pour que SQLMetal soit en mesure de se connecter la base de donnes. Si vous prfrez, le chier intermdiaire DBML peut tre gnr partir dun chier MDF :
sqlmetal /pluralize /sprocs /functions /views /dbml:Northwind.dbml "C:\Northwind.mdf"

Chapitre 13

Astuces et outils pour LINQ to SQL

401

INFO La gnration du chier intermdiaire DBMF partir dun chier MDF peut engendrer lattachement du chier MDF au serveur SQL sous le nom C:\NORTHWIND.MDF, ou quelque chose de similaire. Le cas chant, vous devriez donner le nom Northwind la base de donnes dans SQL Server Enterprise Manager ou SQL Server Management Studio pour que les exemples de ce chapitre soient en mesure de fonctionner.

Ces deux approches devraient produire le mme chier DBML. Dans les deux lignes de commande prcdentes, seules les options ncessaires la lecture de la base de donnes et la cration du chier DBML ont t spcies. Des options telles que /language et /code nont dintrt que pour la gnration dun module de code source. Une fois le chier XML intermdiaire modi, vous obtiendrez le module de code source en excutant cette commande :
sqlmetal /namespace:nwind /code:Northwind.cs Northwind.dbml

Les options spcies dans cette commande sont appropries la gnration de code source. Schma de chier intermdiaire DBML Si vous choisissez de crer un chier intermdiaire DBML, de lditer et de gnrer des classes dentit par cet intermdiaire, vous devez savoir ce quest un schma et ce que signient les noms dlments et dattributs. Le fonctionnement des schmas tant sujet modications, consultez la documentation Microsoft pour prendre connaissance des dernires informations leur sujet. Une fois le concept de schma compris, vous pourrez choisir dditer manuellement le chier DBML pour contrler les noms des classes dentit et des proprits, puis gnrer le code source de la classe dentit en indiquant SQLMetal le nom du chier DBML modi. Encore mieux, vous pourrez ouvrir le chier DBML gnr dans le Concepteur Objet/ Relationnel et y faire les modications ncessaires. En utilisant son interface graphique, et mme si vous ne connaissez ni ne comprenez ce quest un schma, vous pourrez modier le mappage relationnel. Le Concepteur Objet/Relationnel Outre SQLMetal, il existe galement un outil graphique permettant de gnrer des classes dentit. Cet outil fait partie intgrante de Visual Studio. Il est connu sous les noms "Concepteur Objet/Relationnel", "Concepteur LINQ to SQL", "Concepteur O/R" (Object-to-Relational) ou encore "Concepteur DLinq". Loutil en ligne de commande SQLMetal a t conu pour fabriquer des classes dentit pour toutes les tables de la base de donnes. Vous avez cependant la possibilit de limiter son action certaines tables de la base en crant un chier DBML, en le modiant et en gnrant des

402

LINQ to SQL

Partie V

classes dentit par son intermdiaire. Le Concepteur Objet/Relationnel permet une approche plus slective et entirement graphique. Dans la suite de ce chapitre, nous utiliserons le terme "Concepteur" pour dsigner le Concepteur Objet/Relationnel. Le Concepteur permet de modier la classe dentit par de simples glisser-dposer. Nayez crainte, le Concepteur fait toute la partie ingrate du travail. Votre part ne consiste qu slectionner les tables modeler et, si vous le souhaitez, modier les noms et les proprits des classes dentit. Il est bien entendu toujours possible de crer le modle la main dans le Concepteur pour avoir un contrle total
Cration du chier des classes LINQ to SQL La premire chose faire concernant le Concepteur consiste crer les classes LINQ to SQL en cliquant du bouton droit sur le projet et en slectionnant Ajouter/Nouvel lment dans le menu contextuel. Cette action provoque lafchage de la bote de dialogue Ajouter un nouvel lment. Slectionnez le modle Classes LINQ to SQL dans la liste. Choisissez un nom pour la nouvelle classe : le nom de la base de donnes est un bon choix, et lextension est .dbml. Dans cet exemple, nous utiliserons le nom Northwind.dbml.
ATTENTION Si vous avez dj dni un chier Northwind.dbml dans un autre projet cr partir des exemples de cet ouvrage, faites attention ne pas craser le code existant.

Cliquez sur Ajouter. Aprs quelques instants, un espace blanc occupe le centre de la fentre. Il sagit de lespace de travail (aussi appel canevas) du Concepteur. La Figure 13.1 donne un aperu du canevas.
Figure 13.1 : Le canevas du Concepteur Objet/Relationnel.

Chapitre 13

Astuces et outils pour LINQ to SQL

403

Cliquez du bouton droit sur le canevas et slectionnez Proprits dans le menu. Les proprits apparaissent dans la partie droite de la fentre. La proprit Name reprsente le nom de la classe DataContext gnre. Le chier de classes LINQ to SQL ayant pour nom "Northwind.dbml", le nom par dfaut de la classe DataContext sera NorthwindDataContext. Ce nom peut tre modi mais, ici, nous allons le laisser intact. Examinez lExplorateur de solutions. Vous verrez que le chier Northwind.designer.cs a t insr dans le dossier Northwind.dbml. Si vous ouvrez ce chier, vous verrez quil ne contient que trs peu de code ce niveau. En fait, il contient les constructeurs de la nouvelle classe DataContext NorthwindDataContext dont il est driv.
Connexion du DataContext la base de donnes La prochaine tape va consister ajouter (si elle nexiste pas dj) une connexion au serveur de base de donnes contenant la base de donnes Northwind dans la fentre Explorateur de serveurs.
ASTUCE Si la fentre Explorateur de serveurs nest pas accessible, lancez la commande Explorateur de serveurs dans le menu Afchage de Visual Studio.

Pour ajouter une connexion la base de donnes, cliquez du bouton droit sur licne Connexion de donnes dans la fentre Explorateur de serveurs et choisissez Ajouter une connexion dans le menu contextuel. Cette action ouvre la bote de dialogue Ajouter une connexion (voir Figure 13.2). La zone de texte Source de donnes devrait laisser apparatre Microsoft SQL Server (SqlClient). Si ce nest pas le cas, cliquez sur Modier et choisissez lentre correspondante.
Figure 13.2 : La bote de dialogue Ajouter une connexion.

404

LINQ to SQL

Partie V

Congurez les paramtres ncessaires laccs de la base de donnes Northwind dans la bote de dialogue Ajouter une connexion. Pour vous assurer que la connexion est bien congure, cliquez sur Tester la connexion. Une fois que la connexion est congure, cliquez sur OK. Vous devriez avoir une entre qui reprsente la connexion avec la base de donnes Northwind sous le libell Connexions de donnes, dans lExplorateur de serveurs. Vous tes maintenant en mesure daccder la base de donnes Northwind dans le Concepteur. Avant de commencer, assurez-vous que vous tes en train de visualiser le chier Northwind.dbml dans lditeur de Visual Studio.
Ajout dune classe dentit Dans la fentre Explorateur de serveurs, sous Connexions de donnes, cliquez sur le signe "+" qui prcde lentre Northwind.mdf, puis sur le signe "+" qui prcde lentre "Tables" pour afcher la liste des tables de la base de donnes Northwind. Les classes dentit sont cres en dposant des tables de la fentre Explorateur de serveurs sur le canevas du Concepteur.

Dposez la table Customers sur le canevas du Concepteur. Cette simple opration demande au Concepteur de crer la classe dentit Customer pour la table Customers. Le canevas devrait maintenant ressembler la Figure 13.3.
Figure 13.3 : Le Concepteur, aprs avoir dpos la table Customers sur le canevas.

Il se peut que vous ayez oprer plusieurs redimensionnements pour que tout ce qui est afch dans Visual Studio apparaisse clairement. En dposant la table Customers sur le canevas du Concepteur, le code source de lentit Customer est ajout au chier source

Chapitre 13

Astuces et outils pour LINQ to SQL

405

Northwind.designer.cs. Une fois que vous aurez construit votre projet, vous pourrez commencer utiliser la classe dentit Customer pour accder aux donnes et les mettre jour dans la base de donnes Northwind. Cest aussi simple que cela !

Avant de construire le projet et dcrire le code qui utilise les classes dentit gnres, nous allons crer dautres petites choses ncessaires pour tirer parti de LINQ to SQL. Dposez la table Orders sur le canevas (aprs lavoir dpose, il se peut que vous ayez la dplacer pour lui donner une meilleure position). Ce faisant, vous avez demand au Concepteur de crer la classe dentit Order pour la table Orders. Votre canevas devrait maintenant avoir lallure de la Figure 13.4.
Figure 13.4 : Le Concepteur, aprs avoir dpos la table Orders sur le canevas.

Vous avez peut-tre remarqu que le volet qui apparaissait droite du canevas la Figure 13.3 a maintenant disparu. Il sagit du volet Mthodes. Pour le fermer, il suft de le pointer, de cliquer droite et de slectionner Masquer le volet Mthodes dans le menu contextuel. Pour louvrir nouveau, cliquez du bouton droit sur le canevas et slectionnez Afcher le volet Mthodes. Nous avons ferm ce volet pour laisser un plus grand espace dans le canevas. En observant le canevas, vous pouvez voir une ligne pointille qui relie la classe Customer la classe Order. Cette ligne reprsente la relation (connue sous le nom "association" dans LINQ to SQL) entre les tables Customers et Orders, telle quelle a t dnie par la cl trangre FK_Orders_Customers dnie dans la base de donnes Northwind. Cette ligne pointille indique galement que le Concepteur dnira une association entre les classes dentit pour supporter la relation qui lie les deux tables.

406

LINQ to SQL

Partie V

Cest par cette association que vous pourrez obtenir une rfrence vers une collection de commandes des clients en rfrenant une proprit dun objet Customer. De mme, vous pourrez obtenir une rfrence vers une commande dun client en rfrenant une proprit dans lobjet Order. Si vous ne voulez pas conserver lassociation, il vous suft de cliquer sur la ligne pointille qui relie les deux tables et dappuyer sur la touche Suppr du clavier. Vous pouvez galement cliquer du bouton droit sur la ligne pointille et slectionner Supprimer dans le menu contextuel. Utilisation des classes dentit gnres par le Concepteur Vous tes maintenant prt pour utiliser les classes dentit gnres par le Concepteur. titre dexemple, le Listing 13.2 effectue une requte sur la base de donnes Northwind pour connatre les clients qui habitent Londres.
Listing 13.2 : Un exemple dutilisation des classes dentit gnres par le Concepteur.
NorthwindDataContext db = new NorthwindDataContext(); IQueryable<Customer> custs = from c in db.Customers where c.City == "London" select c; foreach(Customer c in custs) { Console.WriteLine("{0} a pass {1} commandes.", c.CompanyName, c.Orders.Count); }

Ce code est assez proche de celui des autres exemples. Mais remarquez quaucune information de connexion na t spcie lors de linstanciation de lobjet NorthwindDataContext. Ceci vient du fait que le Concepteur a gnr la classe NorthwindDataContext avec un constructeur qui na pas besoin de paramtres, car les informations de connexion proviennent du chier de conguration du projet : app.cong. Ce chier contient le code suivant : Le constructeur DataContext gnr par le Concepteur
public NorthwindDataContext() : base(global::LINQChapter13.Properties.Settings.Default.NorthwindConnectionString, mappingSource) { OnCreated(); }

ATTENTION Si vous avez tlcharg le code source daccompagnement de cet ouvrage, assurez-vous que vous avez mis jour la chane de connexion connectionString dans le chier app.cong. En particulier, le code source doit contenir le nom de votre ordinateur, qui na que peu de chances dtre identique au mien.

Chapitre 13

Astuces et outils pour LINQ to SQL

407

Dans le code prcdent, remarquez quil nous a t possible daccder aux commandes des clients en faisant rfrence la proprit Orders dun objet Customer. Ceci vient du fait que le Concepteur a automatiquement dni une association entre ces deux tables. Voici les rsultats de cette requte :
Around the Horn a pass 13 commandes. Bs Beverages a pass 10 commandes. Consolidated Holdings a pass 3 commandes. Eastern Connection a pass 8 commandes. North/South a pass 3 commandes. Seven Seas Imports a pass 9 commandes.

dition du modle de classe dentit Il se peut que vous dsiriez modier les noms des classes dentit, les proprits des classes dentit (dans la fentre des proprits), le nom des proprits (des membres) des classes dentit, les proprits dune proprit dune classe dentit (dun membre dune classe dentit). Merci Microsoft ! Vous auriez certainement pu mieux faire au niveau du choix des termes. Pourquoi les membres des classes ont-ils t appels "proprits", alors que Visual Studio utilise galement ce terme pour parler des diffrents rglages accessibles dans la fentre des proprits ?

Si le Concepteur est si intressant, cest certainement cause de sa exibilit et de la facilit avec laquelle il est possible de contrler le nom des classes dentit et de leurs proprits : de simples glisser-dposer, pointer et cliquer sufsent ! Modication du nom dune classe dentit Pour modier le nom dune classe dentit, il suft de double-cliquer sur ce nom dans le canevas. Vous pouvez galement cliquer sur la classe dentit dans le canevas et modier la proprit Name dans la fentre des proprits. Modication des proprits dune classe dentit Pour modier les proprits dune classe dentit, il suft de cliquer sur cette classe dans le canevas et de faire la modication souhaite dans la fentre des proprits. Vous pouvez ainsi modier le nom de la table dans laquelle les entits sont stockes, le nom des mthodes surcharges Delete, Insert et Update, ainsi que dautres proprits. Modication du nom dune proprit dune classe dentit Pour modier le nom dune proprit dune classe dentit, il suft de triplecliquer dessus dans le canevas. Vous pouvez galement slectionner la proprit en cliquant dessus dans le canevas et modier la proprit Name dans la fentre des proprits.

408

LINQ to SQL

Partie V

Modication des proprits dune proprit dune classe dentit Pour modier les proprits dune proprit dune classe dentit, slectionnez la proprit dans le canevas et modiez les proprits souhaites dans la fentre des proprits : Name et UpdateCheck, par exemple. Nous discuterons en dtail des attributs des classes dentit au Chapitre 15.
Ajout dobjets dans le modle de classe dentit Glisser-dposer une classe dans le canevas est une technique simple condition que la table correspondante se trouve dans la fentre Explorateur de serveurs. Il existe plusieurs cas dans lesquels ce luxe ne vous sera pas permis. Par exemple, si vous dnissez la classe dentit en premier et prvoyez de gnrer la base de donnes en appelant la mthode CreateDatabase sur le DataContext. Ou encore si vous voulez vous servir de lhritage dune classe dentit et quil nexiste aucune table pour la mapper.

Ajout de nouvelles classes dentit Pour ajouter de nouvelles classes dentit dans le modle de classes dentit, une solution consiste les dplacer depuis les tables de la base de donnes (fentre Explorateur de serveurs) sur le canevas du Concepteur, comme cela a t fait dans la section prcdente. Une autre technique consiste dplacer lobjet Classe du Concepteur Objet/ Relationnel de la Bote outils sur le canevas. Modiez le nom et les proprits de la classe dentit comme il a t expos dans la section prcdente. Ajout de nouvelles proprits (membres) dans une classe dentit Pour ajouter une nouvelle proprit dans une classe dentit, cliquez du bouton droit sur la classe dentit, dans le Concepteur, pointez Ajouter et slectionnez Proprit dans le menu contextuel. Lorsque la proprit a t ajoute, procdez comme indiqu dans la section "Modication des proprits dune proprit dune classe dentit" prcdente pour modier les proprits de cette proprit. Ajout dune nouvelle association Plutt quutiliser un glisser-dposer pour crer une association, cliquez sur lobjet Association dans la Bote outils, sur la classe dentit parent (le ct "un" dune relation "un--plusieurs"), puis sur la classe dentit enfant (le ct "plusieurs" dune relation "un--plusieurs"). Avant de pouvoir dnir une association, chacune des deux classes doit possder une proprit approprie, de telle sorte que vous puissiez dnir la cl primaire du ct "un" et la cl trangre du ct "plusieurs". Aprs avoir cliqu sur la deuxime classe (celle qui correspond au ct "plusieurs" de lassociation), la bote de dialogue Editeur dassociations est afche. Il ne vous reste plus qu lutiliser pour relier une proprit du ct "un" une autre du ct "plusieurs".

Chapitre 13

Astuces et outils pour LINQ to SQL

409

Cette tape termine, cliquez sur OK. Une che pointille reliera la classe dentit parent la classe dentit enfant. Slectionnez lassociation en cliquant sur la ligne pointille et dnissez les proprits de lassociation dans la fentre des proprits. Si ncessaire, reportez-vous au Chapitre 15 pour avoir plus dinformations sur les proprits dune association. Ajout dun nouvel hritage Vous pouvez galement utiliser le Concepteur Objet/Relationnel pour modeler les relations dhritage : ajouter une relation dhritage revient ajouter une nouvelle association. Slectionnez lobjet Hritage dans la Bote outils de Visual Studio, cliquez sur la classe dentit qui sera la classe drive, puis sur la classe dentit qui sera la classe de base. Assurez-vous que vous avez dni correctement les proprits de la classe dentit, comme spci dans les attributs de classe dentit InheritanceMapping et Column. Si ncessaire, reportez-vous au Chapitre 15 pour en savoir plus ce sujet.
Ajout de procdures stockes et de fonctions dnies par lutilisateur Au Chapitre 14, vous apprendrez surcharger les mthodes insert, update et delete utilises par LINQ to SQL suite des modications effectues dans une classe dentit. Pour surcharger les mthodes par dfaut, il suft dajouter des mthodes spciques dans une classe dentit. Si vous adoptez cette approche, assurez-vous que vous utilisez des classes partielles. Ainsi, vous ne modierez aucun code gnr. Nous tudierons cette technique en dtail au Chapitre 14.

Sachez cependant que les mthodes insert, update et delete peuvent tre facilement surcharges en utilisant le Concepteur. Supposons que nous disposions de la procdure stocke InsertCustomer, qui insre un enregistrement dun nouveau client dans la table Customer de la base de donnes Northwind. Voici le code de la procdure stocke :
CREATE PROCEDURE dbo.InsertCustomer ( @CustomerID nchar(5), @CompanyName nvarchar(40), @ContactName nvarchar(30), @ContactTitle nvarchar(30), @Address nvarchar(60), @City nvarchar(15), @Region nvarchar(15), @PostalCode nvarchar(10), @Country nvarchar(15), @Phone nvarchar(24), @Fax nvarchar(24) ) AS INSERT INTO Customers ( CustomerID, CompanyName, ContactName, ContactTitle, Address,

410

LINQ to SQL

Partie V

City, Region, PostalCode, Country, Phone, Fax ) VALUES ( @CustomerID, @CompanyName, @ContactName, @ContactTitle, @Address, @City, @Region, @PostalCode, @Country, @Phone, @Fax )

INFO La procdure stocke InsertCustomer ne fait pas partie de la version tendue de la base de donnes Northwind. Elle a t ajoute manuellement pour cette dmonstration.

Pour surcharger la mthode insert de la classe dentit Customer, assurez-vous que le volet Mthodes est visible. Dans le cas contraire, cliquez du bouton droit sur le canevas et slectionnez Ajouter le volet Mthodes dans le menu contextuel. Si la fentre Explorateur de serveurs nest pas dj ouverte, ouvrez-la. Dveloppez le nud Procdures stockes. La fentre de Visual Studio doit maintenant ressembler la Figure 13.5.
Figure 13.5 : La procdure stocke InsertCustomer.

Chapitre 13

Astuces et outils pour LINQ to SQL

411

Faites glisser la procdure stocke InsertCustomer de lExplorateur de serveurs dans le volet Mthodes. La fentre de Visual Studio devrait maintenant ressembler la Figure 13.6.
Figure 13.6 : Glisser-dposer de la procdure stocke dans le volet Mthodes.

Le simple fait de dposer une mthode stocke depuis lExplorateur de serveurs dans le volet Mthodes demande au Concepteur de gnrer le code qui permettra dappeler la procdure stocke depuis LINQ to SQL. Vous utiliserez la mme mthode pour gnrer le code dune fonction dnie par lutilisateur. Pour quune opration Delete, Insert ou Update appelle une procdure stocke (et non la mthode par dfaut), la procdure doit tre accessible depuis LINQ to SQL. Cette premire tape effectue, il suft alors de surcharger lopration. Cliquez sur la classe Customers dans le Concepteur et observez la fentre des proprits. Les dernires lignes listent les mthodes utilises par dfaut pour les oprations Delete, Insert et Update. Cliquez sur lentre Insert. Un bouton contenant des points de suspension apparat dans la partie droite de lentre (voir Figure 13.7). Cliquez sur le bouton pour afcher la bote de dialogue Congurer le comportement. Slectionnez loption Personnaliser et la procdure stocke InsertCustomer dans la liste droulante. Reliez les arguments de mthode ( gauche) aux proprits de classe ( droite), comme illustr la Figure 13.8. Comme vous pouvez le constater, tous les arguments de mthode sont dj correctement relis aux proprits de classe. Cliquez sur OK pour fermer la bote de dialogue Congurer le comportement. Vous tes maintenant prt insrer des enregistrements Customer en utilisant la procdure stocke InsertCustomer (voir Listing 13.3).

412

LINQ to SQL

Partie V

Figure 13.7 : Slection de la mthode Insert dans la catgorie Mthodes par dfaut de la fentre des proprits.

Figure 13.8 : Liaison des arguments de mthode aux proprits de classe.

Listing 13.3 : Cration dun enregistrement Customer avec la surcharge de la mthode par dfaut Insert.
NorthwindDataContext db = new NorthwindDataContext(); db.Log = Console.Out; Customer cust = new Customer {

Chapitre 13

Astuces et outils pour LINQ to SQL

413

CustomerID = "EWICH", CompanyName = "Every Wich Way", ContactName = "Vickey Rattz", ContactTitle = "Owner", Address = "105 Chip Morrow Dr.", City = "Alligator Point", Region = "FL", PostalCode = "32346", Country = "USA", Phone = "(800) EAT-WICH", Fax = "(800) FAX-WICH" }; db.Customers.InsertOnSubmit(cust); db.SubmitChanges(); Customer customer = db.Customers.Where(c => c.CustomerID == "EWICH").First(); Console.WriteLine("{0} - {1}", customer.CompanyName, customer.ContactName); // Restauration de la base de donnes db.Customers.DeleteOnSubmit(cust); db.SubmitChanges();

Comme vous pouvez le voir, aucun espace de noms nest spci dans la classe Customer rfrence. Nous utiliserons en effet la classe Customer gnre par le Concepteur. Cette classe se trouve dans le mme espace de noms que le projet. Le Listing 13.3 est assez simple comprendre. Aprs avoir instanci le DataContext NorthwindDataContext, gnr par le Concepteur, un objet Customer est cr et initialis. Cet objet est insr dans la proprit Customers Table<T>. La mthode SubmitChanges est alors appele pour reporter la modication dans la base de donnes. Une requte retrouve cet enregistrement dans la base de donnes, puis une instruction Console.WriteLine lafche dans la console, pour mettre en vidence que lenregistrement a bien t insr dans la base de donnes. Enn, lenregistrement est supprim de la base de donnes avec la mthode DeleteOnSubmit, et la suppression est rendue permanente avec la mthode SubmitChanges. La base de donnes se trouve donc dans le mme tat aprs lexcution du code (voir Listing 13.3). Les exemples suivants ne seront donc pas affects par ce code, et ce quil soit excut une ou plusieurs fois.
EXEC @RETURN_VALUE = [dbo].[InsertCustomer] @CustomerID = @p0, @CompanyName = @p1, @ContactName = @p2, @ContactTitle = @p3, @Address = @p4, @City = @p5, @Region = @p6, @PostalCode = @p7, @Country = @p8, @Phone = @p9, @Fax = @p10 -- @p0: Input StringFixedLength (Size = 5; Prec = 0; Scale = 0) [EWICH] -- @p1: Input String (Size = 15; Prec = 0; Scale = 0) [Every Wich Way] -- @p2: Input String (Size = 12; Prec = 0; Scale = 0) [Vickey Rattz] -- @p3: Input String (Size = 5; Prec = 0; Scale = 0) [Owner] -- @p4: Input String (Size = 19; Prec = 0; Scale = 0) [105 Chip Morrow Dr.] -- @p5: Input String (Size = 15; Prec = 0; Scale = 0) [Alligator Point] -- @p6: Input String (Size = 2; Prec = 0; Scale = 0) [FL] -- @p7: Input String (Size = 5; Prec = 0; Scale = 0) [32346] -- @p8: Input String (Size = 3; Prec = 0; Scale = 0) [USA] -- @p9: Input String (Size = 14; Prec = 0; Scale = 0) [(800) EAT-WICH] -- @p10: Input String (Size = 14; Prec = 0; Scale = 0) [(800) FAX-WICH] -- @RETURN_VALUE: Output Int32 (Size = 0; Prec = 0; Scale = 0) [] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

414

LINQ to SQL

Partie V

SELECT TOP 1 [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax] FROM [dbo].[Customers] AS [t0] WHERE [t0].[CustomerID] = @p0 -- @p0: Input String (Size = 5; Prec = 0; Scale = 0) [EWICH] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1 Every Wich Way - Vickey Rattz DELETE FROM [dbo].[Customers] WHERE ([CustomerID] = @p0) AND ([CompanyName] = @p1) AND ([ContactName] = @p2) AND ([ContactTitle] = @p3) AND ([Address] = @p4) AND ([City] = @p5) AND ([Region] = @p6) AND ([PostalCode] = @p7) AND ([Country]