Etude exploratoire de Linq - CoDE - de l'Université libre de Bruxelles
Etude exploratoire de Linq - CoDE - de l'Université libre de Bruxelles
Etude exploratoire de Linq - CoDE - de l'Université libre de Bruxelles
Create successful ePaper yourself
Turn your PDF publications into a flip-book with our unique Google optimized e-Paper software.
Faculté <strong>de</strong>s Sciences appliquées<br />
Année académique 2009 - 2010<br />
<strong>Etu<strong>de</strong></strong> <strong>exploratoire</strong> <strong>de</strong> <strong>Linq</strong><br />
Directeur <strong>de</strong> mémoire : Mémoire présenté par<br />
Dr. Ir. Hugues Bersini Charles Schoonenbergh<br />
En vue <strong>de</strong> l’obtention du<br />
diplôme <strong>de</strong> Master en<br />
ingénieur civil informaticien,<br />
spécialité ingénierie<br />
informatique
Abstract<br />
<strong>Linq</strong> est une technologie apparue avec C# 3 et VB 2008 et a été présenté comme un gigantesque bon<br />
en avant dans le domaine <strong>de</strong> la gestion <strong>de</strong>s données. La présente étu<strong>de</strong> se propose d’explorer <strong>Linq</strong><br />
<strong>de</strong>puis ses fon<strong>de</strong>ments jusqu’à l’analyse <strong>de</strong> plusieurs mises en situation. L’étu<strong>de</strong> débutera par<br />
l’analyse <strong>de</strong>s enrichissements syntaxiques apportés avec C# 3 pour ensuite définir le formalisme <strong>de</strong><br />
requêtes utilisé par <strong>Linq</strong>. <strong>Linq</strong> se décline en une série d’implémentations, celles-ci seront examinées<br />
en détails en commençant par <strong>Linq</strong> to Object. Ensuite viendra le tour <strong>de</strong> <strong>Linq</strong> to Sql. Cette<br />
implémentation étant source <strong>de</strong> beaucoup <strong>de</strong> controverse, une attention toute particulière y sera<br />
apportée. Plusieurs tests <strong>de</strong> mise en œuvre seront appliqués, notamment en ce qui concerne les<br />
performances relatives à un co<strong>de</strong> équivalent rédigé avec la couche ADO.NET classique. D’autres<br />
implémentations relationnelles seront étudiées comme <strong>Linq</strong> to DataSet, <strong>Linq</strong> to Entities exploitant la<br />
toute nouvelle technologie Entity Framework et une brève analyse <strong>de</strong> l’implémentation Db <strong>Linq</strong> sera<br />
faite. L’implémentation <strong>Linq</strong> to Xml sera ensuite étudiée, présentant les nouveautés liées à la gestion<br />
du contenu Xml. Un bilan <strong>de</strong>s nouveautés proposées par <strong>Linq</strong> sera ensuite établi et l’étu<strong>de</strong> se<br />
terminera par l’analyse <strong>de</strong>s alternatives à <strong>Linq</strong>, que ce soit pour la plateforme .NET ou pour d’autres<br />
langages orientés objet, ainsi que Java, PHP ou Python.
Table <strong>de</strong>s matières<br />
Conventions d’écriture 1<br />
Introduction 2<br />
1. Vue d’ensemble 3<br />
2. Mécanismes sous-jacents 4<br />
2.1 Quelques rappels 4<br />
2.1.1 Généricité 4<br />
2.1.2 Délégués 4<br />
2.1.3 Métho<strong>de</strong>s anonymes 5<br />
2.2 Enrichissements syntaxiques 6<br />
2.2.1 Inférence sur le typage 6<br />
2.2.2 Expressions lambda 7<br />
2.2.3 Expressions arborescentes 7<br />
2.2.4 Extensions <strong>de</strong> métho<strong>de</strong> 11<br />
2.2.5 Formalisme d’une requête 11<br />
3. Implémentation Objets 13<br />
3.1 Champ d’application 13<br />
3.2 Quelques opérateurs à la loupe 14<br />
3.2.1 Projection groupée 14<br />
3.2.2 Inversion et in<strong>de</strong>xes 14<br />
3.2.3 Groupement 14<br />
3.2.4 Quantificateurs 15<br />
3.2.5 Conversion en énumérable 15<br />
3.3 Exemple illustratif 16<br />
4. Implémentations relationnelles 18<br />
4.1 Entités en mémoire 18<br />
4.2 <strong>Linq</strong> to SQL 18<br />
4.2.1 Mapping 19<br />
4.2.1.1 Syntaxe au niveau du co<strong>de</strong> 19<br />
4.2.1.2 Principe et fonctionnement 19<br />
4.2.1.3 Gestion par le DataContext 21
4.2.2 Opérations fondamentales 22<br />
4.2.3 Cas pratiques 23<br />
4.2.4 Erreurs et difficultés 26<br />
4.2.5 Performances 29<br />
4.2.5.1 Test d’insertion 29<br />
4.2.5.2 Test en situation concurrentielle 31<br />
4.2.6 Conclusion 32<br />
4.3 <strong>Linq</strong> to DataSet 33<br />
4.4 <strong>Linq</strong> to Entities 35<br />
4.4.1 Concept fondateur 35<br />
4.4.2 Fonctionnement 36<br />
4.4.3 Mapping 37<br />
4.4.4 Rôle<strong>de</strong> <strong>Linq</strong> 38<br />
4.5 Db <strong>Linq</strong> 38<br />
5. Implémentation XML 39<br />
6. En pratique 41<br />
6.1 Ressenti général 41<br />
6.2 Points forts, points faibles 41<br />
6.3 Quelles alternatives ? 43<br />
6.3.1 Java 43<br />
6.3.1.1 JDO 43<br />
6.3.1.2 Hibernate 44<br />
6.3.2 .NET 45<br />
6.3.2.1 NHibernate 45<br />
6.3.2.2 Entity Framework 45<br />
6.3.3 Autres langages 46<br />
6.3.3.1 Persistance personnalisée 46<br />
6.3.3.2 Bases <strong>de</strong> données objets 47<br />
6.4 Evolutions <strong>de</strong> <strong>Linq</strong> 48<br />
6.4.1 Version 4 48<br />
6.4.2 Ce qui reste à améliorer 48<br />
6.4.3 <strong>Linq</strong> et Mono 49<br />
Conclusion 50
Bibliographie 51<br />
Annexe 1 : Co<strong>de</strong> <strong>de</strong> l’exemple <strong>Linq</strong> to object 52<br />
Annexe 2 : Co<strong>de</strong> <strong>de</strong> test <strong>de</strong> performance <strong>Linq</strong> to Sql 55<br />
Annexe 3 : Utilisation d’une base <strong>de</strong> données MySql avec <strong>Linq</strong> to DataSet 65
Conventions d’écriture<br />
Afin <strong>de</strong> rendre plus aisée la lecture <strong>de</strong> ce document, <strong>de</strong>s conventions d’écriture ont été adoptées.<br />
Les termes faisant référence à une classe, un assemblage ou une métho<strong>de</strong> seront écrits en italique et<br />
dans le respect <strong>de</strong> la casse originale, comme ceci : Metho<strong>de</strong> définie pour telle Classe.<br />
Les termes faisant références à <strong>de</strong>s procédures ou à <strong>de</strong>s techniques bien connues seront écrit avec<br />
une majuscule pour mieux ressortir, comme ceci :<br />
La clause Select du langage Sql.<br />
Les extraits <strong>de</strong> co<strong>de</strong> seront présentés dans une police spéciale pour bien ressortir du texte principal.<br />
En outre, un alinéa supplémentaire sera observé, comme ceci :<br />
Ceci est un extrait <strong>de</strong> co<strong>de</strong>;<br />
Les références à <strong>de</strong>s sites internet se feront comme <strong>de</strong>s références classiques lorsque le site contient<br />
<strong>de</strong> l’information dont le présent écrit est inspiré. Lorsque du contenu est cité explicitement, une note<br />
<strong>de</strong> bas <strong>de</strong> page présentera l’adresse web exacte.
Introduction<br />
Depuis l’apparition <strong>de</strong>s langages orientés objet, le problème <strong>de</strong> la persistance <strong>de</strong>s données a toujours<br />
été crucial. Un programme incapable <strong>de</strong> retenir ce qui s’est passé lors <strong>de</strong> sa précé<strong>de</strong>nte exécution ne<br />
présente qu’un intérêt limité. Malheureusement aucun langage orienté objet n’a <strong>de</strong> prise directe sur<br />
cette capacité <strong>de</strong> persistance. Des systèmes <strong>de</strong> stockage se sont développés, certains très antérieurs<br />
aux langages orientés objet eux-mêmes. C’est le cas <strong>de</strong>s bases <strong>de</strong> données relationnelles qui stockent<br />
l’information sous forme <strong>de</strong> tables ayant <strong>de</strong>s relations entre elles. Ce mo<strong>de</strong> <strong>de</strong> stockage c’est petit à<br />
petit imposé comme acteur principal lorsqu’il s’agit d’enregistrer <strong>de</strong>s informations <strong>de</strong>puis un<br />
programme. L’ennui est que les langages <strong>de</strong> programmation actuels ne s’interfacent pas<br />
parfaitement avec ce type <strong>de</strong> persistance. Depuis longtemps déjà, <strong>de</strong>s solutions sont mises en œuvre<br />
pour palier à ce problème.<br />
Réaliser un interfaçage n’est pas tout, il reste le problème <strong>de</strong> la dépendance au niveau du co<strong>de</strong>. Si<br />
changer le système d’enregistrement <strong>de</strong>s données implique <strong>de</strong> réécrire la moitié <strong>de</strong> l’application qui<br />
les utilise, la situation est gênante. L’abstraction envers le stockage a ses limites, lui aussi. Les bases<br />
<strong>de</strong> données orientées objet et plus tard leurs homologues basées sur Xml sont venues s’ajouter dans<br />
le paysage mo<strong>de</strong>rne <strong>de</strong> la persistance. Ces gestionnaires <strong>de</strong> données n’utilisent cependant pas le<br />
même langage et passer <strong>de</strong> l’un à l’autre sans rien changer est tout simplement irréalisable. Nous<br />
avons donc actuellement affaire à <strong>de</strong>s applications mêlant plusieurs langages <strong>de</strong> programmation,<br />
généralement <strong>de</strong> l’orienté objet couplé à un langage <strong>de</strong> requêtes propre à la source <strong>de</strong> données.<br />
C’est là que la tâche du développeur se complique. Il faut non seulement maîtriser plusieurs langages<br />
mais en plus pouvoir interfacer les <strong>de</strong>ux <strong>de</strong> la façon la plus abstraite possible car un changement <strong>de</strong><br />
politique <strong>de</strong> stockage est toujours possible.<br />
<strong>Linq</strong> a été introduit avec C# 3 et VB 2008. Derrière cet acronyme, Language INtegrated Query, se<br />
cache presque tout un paradigme. Une nouvelle manière d’appréhen<strong>de</strong>r les données, spécialement<br />
celles qui sont persistantes. La présente étu<strong>de</strong> se propose d’explorer les différentes possibilités qui<br />
caractérisent <strong>Linq</strong> tout en gardant un œil critique sur ce que Microsoft annonce comme étant LA<br />
révolution dans le mon<strong>de</strong> <strong>de</strong> la programmation. Nous tenterons dans un premier temps <strong>de</strong> mettre en<br />
lumière les solutions techniques qui ont été mises en œuvre avec <strong>Linq</strong>. La question du champ<br />
d’application sera ensuite abordée, passant en revue les différentes implémentations <strong>de</strong> <strong>Linq</strong> : ce<br />
qu’il peut faire ou ne pas faire et quels types <strong>de</strong> données il peut cibler. Nous nous intéresserons tout<br />
particulièrement au cas <strong>de</strong>s données relationnelles qui restent, comme déjà précisé, un acteur<br />
incontournable dans le domaine <strong>de</strong> la gestion <strong>de</strong>s données non volatiles. Après ces différentes<br />
étu<strong>de</strong>s <strong>de</strong> cas, nous tenterons <strong>de</strong> faire le point sur les forces et faiblesses <strong>de</strong> <strong>Linq</strong> avant d’envisager<br />
les alternatives à son utilisation. Viendra enfin un examen <strong>de</strong> son <strong>de</strong>venir et <strong>de</strong> ses éventuelles<br />
évolutions avant <strong>de</strong> conclure notre étu<strong>de</strong>.
Chapitre 1 : Vue d’ensemble<br />
Dans cette section nous allons donner un avant-goût <strong>de</strong> <strong>Linq</strong> et préciser la métho<strong>de</strong> générale qui sera<br />
suivie durant cette étu<strong>de</strong>. Tout au long <strong>de</strong> ce rapport nous utiliserons le terme <strong>de</strong> <strong>Linq</strong> pour désigner<br />
l’ensemble <strong>de</strong>s fonctionnalités accessibles grâce à ses implémentations. <strong>Linq</strong> n’est pas réellement un<br />
tout unifié mais davantage un regroupement <strong>de</strong> plusieurs API partageant une syntaxe commune. Il<br />
est bon <strong>de</strong> gar<strong>de</strong>r cela en tête pour éviter toute confusion. Ce chapitre est inspiré <strong>de</strong> [1].<br />
Comme son acronyme l’indique, <strong>Linq</strong> offre la possibilité <strong>de</strong> faire <strong>de</strong>s requêtes intégrées au langage,<br />
mais que <strong>de</strong>vons-nous comprendre par là ? En réalité, le terme requête sera expliqué dans le<br />
prochain chapitre et lorsque nous parlons <strong>de</strong> langage il s’agira <strong>de</strong> C# et VB 2008. Ces <strong>de</strong>ux langages<br />
<strong>de</strong> programmation font partie <strong>de</strong> la plateforme .NET (prononcé dotNet, à l’Anglaise), parfois aussi<br />
appelé framework .NET. Comme abordé dans l’introduction, un problème majeur actuellement est<br />
<strong>de</strong> <strong>de</strong>voir mélanger <strong>de</strong>s langages appartenant à <strong>de</strong>s paradigmes différents : en général un pour la<br />
programmation orientée objet et l’autre pour la gestion <strong>de</strong>s données stockées. L’ensemble <strong>de</strong>s<br />
difficultés engendrées par la mixité <strong>de</strong>s langages est souvent appelé « impedance mismatch » dans la<br />
littérature internationale. Ce problème d’impédance entre langages résulte souvent en une baisse <strong>de</strong><br />
productivité pour le développeur et une panoplie d’erreurs difficiles à débusquer car n’apparaissant<br />
potentiellement que très tard, à l’exécution. <strong>Linq</strong> est présenté comme le remè<strong>de</strong> à tous ces soucis,<br />
offrant un seul langage où tout est vérifié à la compilation. Reste à voir quel fossé existe entre ce<br />
genre d’annonces publicitaires et la réalité <strong>de</strong> la conception d’applications.<br />
Cette étu<strong>de</strong> a pour but d’explorer <strong>Linq</strong> et ses fonctionnalités, c'est-à-dire d’examiner <strong>de</strong> quelle façon<br />
<strong>Linq</strong> se propose <strong>de</strong> faire la jonction entre le mon<strong>de</strong> <strong>de</strong>s objets et celui <strong>de</strong> leur persistance. Nous ne<br />
prétendons pas porter <strong>de</strong> jugement sur le choix <strong>de</strong> telle ou telle manière <strong>de</strong> réaliser le stockage <strong>de</strong>s<br />
données. Il n’est pas non plus <strong>de</strong> notre ressort d’établir que tel langage <strong>de</strong> programmation est mieux<br />
qu’un tel autre. Nous tâcherons également <strong>de</strong> rester aussi objectifs que possible dans nos<br />
considérations, il ne s’agit pas <strong>de</strong> vanter Microsoft ou même <strong>Linq</strong> mais d’étudier ce <strong>de</strong>rnier. Nous<br />
essayerons <strong>de</strong> partir <strong>de</strong> sources documentées aussi officielles que possible pour ensuite mettre en<br />
œuvre <strong>de</strong>s cas concrets. Diverses autres sources seront cependant mentionnées mais leur caractère<br />
non officiel sera précisé.
Chapitre 2 : Mécanismes sous-jacents<br />
2.1 Quelques rappels<br />
Avant <strong>de</strong> se lancer dans l’analyse <strong>de</strong>s nouveautés syntaxiques, rappelons certains aspects<br />
intéressants déjà présents dans le <strong>de</strong>uxième framework <strong>de</strong> .NET. Ces fonctionnalités sont à la base<br />
<strong>de</strong> <strong>Linq</strong> et constituaient déjà <strong>de</strong>s avancées majeures dans le domaine <strong>de</strong> l’abstraction en général. Il<br />
s’agit <strong>de</strong>s classes génériques, <strong>de</strong>s métho<strong>de</strong>s anonymes ainsi que <strong>de</strong>s délégués. Nous allons les passer<br />
rapi<strong>de</strong>ment en revue en montrant leur intérêt du point <strong>de</strong> vue <strong>de</strong> <strong>Linq</strong> ainsi que leur rôle. Les<br />
améliorations venues se greffer par-<strong>de</strong>ssus ces mécanismes seront détaillées dans la prochaine<br />
section. L’ensemble <strong>de</strong> cette section est principalement inspiré <strong>de</strong> *2+ pour le contenu et <strong>de</strong> *1+ pour<br />
sa structure.<br />
2.1.1 Généricité<br />
La généricité, en tant que telle, a été introduite dans le langage C++ par le biais <strong>de</strong>s classes<br />
templates. Le principe, rappelons-le brièvement, est <strong>de</strong> pouvoir considérer l’exécution d’un co<strong>de</strong><br />
sans avoir à spécifier quel type d’objets il manipulera. C#, dans sa version 2, permettait l’usage et la<br />
définition <strong>de</strong> classes, <strong>de</strong> structures, d’interfaces et <strong>de</strong> fonctions génériques. En plus d’apporter une<br />
plus gran<strong>de</strong> sûreté dans la programmation 1 , la généricité rend également un co<strong>de</strong> plus réutilisable<br />
lorsqu’elle est utilisée. De ce fait, l’usage <strong>de</strong>s génériques offre un niveau d’abstraction facile<br />
d’emploi. Une liste peut être définie <strong>de</strong> manière générique, car les principes d’insertion, <strong>de</strong><br />
recherche et <strong>de</strong> suppression ne sont aucunement liés au type <strong>de</strong> contenu. A l’utilisation, la liste sera<br />
précisée comme étant une liste d’entiers, <strong>de</strong> strings ou <strong>de</strong> n’importe quel type d’objets dont il sera<br />
question à ce moment-là. La définition <strong>de</strong> la liste représente bien une abstraction par rapport à son<br />
contenu et néanmoins, le compilateur remarquera les opérations illicites au niveau typage<br />
(impossible d’ajouter un string à une liste d’entiers par exemple). La généricité est importante pour<br />
envisager <strong>Linq</strong>, les manipulations <strong>de</strong> données (telles qu’une insertion, une suppression ou une<br />
recherche, tout comme pour la liste) n’étant pas liées directement au contenu. Comme nous le<br />
verrons plus loin, <strong>Linq</strong> utilise une interface générique pour manipuler <strong>de</strong>s données.<br />
2.1.2 Délégués<br />
Les délégués sont <strong>de</strong>s objets (<strong>de</strong> classe Delegate ou d’une <strong>de</strong> ses sous-classes) qui peuvent appeler<br />
une ou plusieurs fonctions, à la manière <strong>de</strong>s pointeurs <strong>de</strong> fonctions en C++. Le principe n’est pas<br />
nouveau, son origine remonte au C++ comme nous venons <strong>de</strong> le préciser. Ce qui est nouveau, en<br />
revanche, ce sont les facilités <strong>de</strong> syntaxes ainsi que la possibilité d’ajouter un nombre variable <strong>de</strong><br />
fonctions à un délégué. Un exemple illustratif inspiré <strong>de</strong> [2] mettra en lumière ces aspects à la page<br />
suivante.<br />
1 La généricité dans les collections permet d’encore accroître le typage et donc la vérification à la compilation.
class Exemple<br />
{<br />
static void f1() { Console.WriteLine("fct1"); }<br />
static void f2() { Console.WriteLine("fct2"); }<br />
}<br />
<strong>de</strong>legate void T(); //<strong>de</strong>claration du <strong>de</strong>legue<br />
static void Main(string[] args)<br />
{<br />
T <strong>de</strong>l = new T(f1);<br />
<strong>de</strong>l += new T(f2);<br />
}<br />
<strong>de</strong>l(); //execute les <strong>de</strong>ux fonctions pointees<br />
En lisant l’exemple ci-<strong>de</strong>ssus, la première chose qui va nous intéresser est la déclaration du délégué.<br />
Ceci correspond bien à une déclaration <strong>de</strong> type (ici un type nommé T) dont nous avons le choix <strong>de</strong><br />
son nom (T) et le mot clé <strong>de</strong>legate précise qu’il s’agit d’un type délégué. Il est possible d’ajouter au<br />
délégué une ou plusieurs références à <strong>de</strong>s fonctions à la condition que celles-ci respectent la<br />
signature du délégué (ici aucun paramètre et void comme retour). C’est ce dont il s’agit dans les <strong>de</strong>ux<br />
premières lignes <strong>de</strong> la métho<strong>de</strong> Main. Lorsqu’un délégué fait référence à plusieurs fonctions, il les<br />
exécute toutes dans l’ordre avec lequel elles ont été assignées au délégué. Ainsi, le co<strong>de</strong> <strong>de</strong><br />
l’exemple plus haut produirait en sortie le résultat suivant :<br />
Figure 1 : appels <strong>de</strong> délégués<br />
Le principe <strong>de</strong>s délégués est à la base <strong>de</strong>s expressions lambda fortement utilisées par <strong>Linq</strong>. Comme<br />
nous le verrons ces expressions sont spécialement utilisées dans la résolution <strong>de</strong>s clauses <strong>de</strong><br />
requêtes (select, where …).<br />
2.1.3 Métho<strong>de</strong>s anonymes<br />
Les métho<strong>de</strong>s anonymes ont été introduites avec la <strong>de</strong>uxième version <strong>de</strong> .NET. Il s’agit <strong>de</strong> suites<br />
d’instructions jouant le rôle d’une métho<strong>de</strong>, sans pour autant définir explicitement <strong>de</strong> fonction. Cela<br />
s’utilise en conjonction avec les délégués, <strong>de</strong> la manière suivante :<br />
<strong>de</strong>l += <strong>de</strong>legate { string text = "Il est " + DateTime.Now.ToString();<br />
Console.WriteLine(text); };<br />
En effet aucun nom <strong>de</strong> métho<strong>de</strong> n’a été spécifié. La seule contrainte d’utilisation est qu’il est<br />
impossible <strong>de</strong> faire marche arrière, puisque retirer la référence à cette métho<strong>de</strong> dans le délégué<br />
implique <strong>de</strong> pouvoir nommer cette référence. En poussant le raisonnement plus loin, pourquoi ne<br />
pas utiliser une métho<strong>de</strong> anonyme en paramètre d’une autre métho<strong>de</strong> ? La lisibilité et la<br />
compréhensibilité du co<strong>de</strong> s’en trouvent améliorées. <strong>Linq</strong> utilise les métho<strong>de</strong>s anonymes dans ce<br />
but, améliorer la lisibilité <strong>de</strong>s requêtes. Vouloir obtenir « tous les étudiants tels que leur moyenne est<br />
supérieure ou égale à 10 et les grouper par section » s’écrirait en pseudo co<strong>de</strong> :<br />
etudiants.Where(moyenne >= 10).Or<strong>de</strong>rBy(section).Select(etudiant);
Etudiants désignant une collection <strong>de</strong> données, l’appel à la métho<strong>de</strong> Where <strong>de</strong>vrait prendre en<br />
paramètre une ou plusieurs conditions à tester. Comment effectuer ces tests sans savoir au préalable<br />
leur nombre et leur nature ? Le recours à une métho<strong>de</strong> anonyme va permettre <strong>de</strong> manipuler une ou<br />
plusieurs conditions complexes 2 et d’extraire ainsi les objets répondant à la condition pour passer<br />
cette nouvelle collection en paramètre <strong>de</strong> l’appel suivant et ainsi <strong>de</strong> suite. <strong>Linq</strong> fait un usage extensif<br />
<strong>de</strong>s métho<strong>de</strong>s anonymes en conjonction avec une syntaxe simplifiée (ce point sera développé lors <strong>de</strong><br />
la présentation <strong>de</strong>s expressions lambda) pour exprimer les clauses d’une requête, comme le <strong>de</strong>rnier<br />
exemple l’a suggéré.<br />
2.2 Enrichissements syntaxiques<br />
Comme nous venons <strong>de</strong> le voir, certains aspects du langage C# dans sa version 2 introduisent <strong>de</strong>s<br />
fonctionnalités qui nous seront utiles pour mieux cerner les fon<strong>de</strong>ments <strong>de</strong> requêtes sur <strong>de</strong>s<br />
données abstraites. Nous allons maintenant abor<strong>de</strong>r <strong>de</strong> nouveaux éléments syntaxiques, pour la<br />
plupart basés sur les mécanismes que nous venons <strong>de</strong> passer en revue. Ces nouvelles fonctionnalités<br />
vont un cran plus loin et mettent en place les bases d’un langage <strong>de</strong> requêtes. Nous allons y voir<br />
comment le compilateur peut désormais inférer le typage d’un objet lorsque celui-ci n’est pas<br />
spécifié, comment mettre en place les métho<strong>de</strong>s d’interrogations proprement dites et enfin nous<br />
allons découvrir une nouvelle façon d’écrire les métho<strong>de</strong>s anonymes au moyen <strong>de</strong>s expressions<br />
lambda. Avec tous ces pré requis en place, nous allons pouvoir examiner la syntaxe globale d’une<br />
requête au sens <strong>de</strong> <strong>Linq</strong> en y montrant le rôle <strong>de</strong> chacun <strong>de</strong>s mécanismes vus jusque là. Cette section<br />
repose essentiellement sur <strong>de</strong>s informations tirées <strong>de</strong> [4] et arrangées selon une structure inspirée<br />
<strong>de</strong> [1].<br />
2.2.1 Inférence sur le typage<br />
L’inférence sur le typage permet au compilateur <strong>de</strong> déduire le type <strong>de</strong> variables sans que le<br />
développeur ait besoin <strong>de</strong> le lui signaler explicitement. Comme cette étape se passe à la compilation,<br />
les risques d’erreur restent limités tout en autorisant une syntaxe plus <strong>libre</strong>. Le mot-clé var peut être<br />
utilisé en lieu et place du type d’une variable et indique au compilateur que son type <strong>de</strong>vra être<br />
inféré à la compilation. A noter toutefois que l’inférence sur le typage est une opération locale et ne<br />
peut donc être appliquée au champ d’une classe ni dans le cas d’un paramètre.<br />
Il est important <strong>de</strong> parler, à ce sta<strong>de</strong>, d’une autre nouveauté introduite par la troisième version du<br />
framework .NET, le typage anonyme. Le typage anonyme permet <strong>de</strong> créer une variable sans en<br />
spécifier le type ni l’appel explicite au constructeur (cette <strong>de</strong>rnière particularité constitue elle aussi<br />
une nouveauté et est disponible pour tous les types). Ainsi il est parfaitement licite d’écrire le co<strong>de</strong><br />
suivant pour une classe Personne ayant comme attributs publics les champs Nom (<strong>de</strong> type string) et<br />
Age (<strong>de</strong> type int) :<br />
Personne p = new Personne { Nom = "Tartempion", Age = 40 };<br />
var monTypeAnonyme = new { p.Nom, p.Age };<br />
<strong>Linq</strong> étant présenté comme une couche d’abstraction dans l’accès aux données, l’intérêt d’intégrer<br />
une telle syntaxe est évi<strong>de</strong>nt. Un changement dans le type du champ Age (passage <strong>de</strong> integer en<br />
short par exemple) laissera intacte la présentation <strong>de</strong>s résultats <strong>de</strong> requêtes ou les modifications <strong>de</strong><br />
valeurs impliquant ce champ.<br />
2 Complexe est utilisé ici pour signifier qu’on dépasse le cadre d’un simple opérateur conditionnel tel que ==.
2.2.2 Expressions lambda<br />
Cette sous-section rassemble également <strong>de</strong>s informations provenant <strong>de</strong> [1] et <strong>de</strong> [6]. Le type<br />
<strong>de</strong>legate introduit avec la version 2 du framework .NET représente un moyen <strong>de</strong> faire pointer du<br />
co<strong>de</strong> vers une fonction définie ailleurs. L’expression lambda simplifie cette pratique et permet plus<br />
<strong>de</strong> souplesse à l’utilisation. Ceci s’avère particulièrement utile pour l’écriture <strong>de</strong> métho<strong>de</strong>s<br />
anonymes, comme le montre l’exemple suivant (trouvé sur [6]) :<br />
//metho<strong>de</strong> anonyme recuperant les entiers positifs, ecrite avec la<br />
//syntaxe 2.0<br />
List list = new List(new int[] { -1, 2, -5, 45, 5 });<br />
List positiveNumbers = list.FindAll(<strong>de</strong>legate(int i) { return i ><br />
0; });<br />
//meme co<strong>de</strong> ecrit avec une expression lambda<br />
List list = new List(new int[] { -1, 2, -5, 45, 5 });<br />
var positiveNumbers = list.FindAll((int i) => i > 0);<br />
La syntaxe d’une expression lambda est toute simple. Elle se compose d’une liste <strong>de</strong> paramètres (un<br />
seul, dans l’exemple ci-<strong>de</strong>ssus, l’entier i), du symbole « => » intuitivement traduit par « tel que » et<br />
d’une expression qui constitue en fait la valeur retournée par l’expression lambda. Cette formulation<br />
est bien plus lisible et se prononce « tous les entiers i tels que la valeur <strong>de</strong> i est strictement<br />
supérieure à 0 ». Un autre avantage est que la valeur retournée par l’expression lambda est typée<br />
implicitement, comme l’indique le mot-clé var dans l’exemple précé<strong>de</strong>nt.<br />
En plus d’une simplicité d’écriture pour les métho<strong>de</strong>s anonymes, les expressions lambda permettent<br />
<strong>de</strong> traiter <strong>de</strong>s expressions proches <strong>de</strong>s clauses Sql. Ainsi une clause Where dans une requête Sql peut<br />
se voir comme une condition <strong>de</strong> filtrage du résultat. Pour exprimer ceci en termes d’expressions<br />
lambda, nous appelons notre condition <strong>de</strong> filtrage définie par une expression lambda sur le résultat,<br />
<strong>de</strong> cette manière résultat.Filtrage(conditions). Le résultat <strong>de</strong> ce filtrage peut être lui-même soumis à<br />
d’autres opérations <strong>de</strong> ce type, présentant l’avantage <strong>de</strong> pouvoir lire les opérations séquentiellement<br />
<strong>de</strong> gauche à droite : résultat.Filtre1( condition1.1, condition1.2).Filtre2( condition2.1, … condition2.n)<br />
et ainsi <strong>de</strong> suite. C’est ainsi que <strong>Linq</strong> en fait usage, pour passer à chaque opérateur <strong>de</strong> clause le<br />
résultat créé par l’appel <strong>de</strong>s clauses précé<strong>de</strong>ntes : EnsembleFrom.Where(clause where).Select(clause<br />
select).Or<strong>de</strong>rBy(critère <strong>de</strong> tri). Nous ne parlons ici que <strong>de</strong>s clauses au sens relationnel du terme, le Sql<br />
étant <strong>de</strong>venu en quelque sorte le standard pour interroger <strong>de</strong>s données, nous nous contentons<br />
d’utiliser ici l’analogie. Nous verrons plus loin que <strong>Linq</strong> a adopté une syntaxe qui en est très proche.<br />
2.2.3 Expressions arborescentes<br />
Agir sur une source <strong>de</strong> données implique <strong>de</strong> pouvoir naviguer, disons, parmi les nœuds <strong>de</strong> sa<br />
structure. La notion <strong>de</strong> relation entre <strong>de</strong>s données stockées caractérise ceci au niveau conceptuel,<br />
mais en pratique, qu’en est-il ? Pour <strong>de</strong>s collections d’objets en mémoire, <strong>de</strong>s itérateurs ou <strong>de</strong>s<br />
métho<strong>de</strong>s <strong>de</strong> saut à un autre élément sont généralement fournis par la collection elle-même. Pour<br />
une structure relationnelle, les relations sont réalisées par <strong>de</strong>s jointures entre plusieurs tables. Aucun<br />
mécanisme orienté objet ne permet d’appeler une fonction qui sautera directement vers l’élément<br />
désiré. Le problème est le même lorsqu’on manipule <strong>de</strong>s données en Xml bien que, dans ce cas, la<br />
syntaxe <strong>de</strong>s requêtes soit plus proche du paradigme objet. Pour résoudre ce problème, les<br />
expressions arborescentes (expression tree, en Anglais) ont vu le jour. L’intérêt n’est pas tant <strong>de</strong><br />
manipuler en soi la source <strong>de</strong> données mais plutôt <strong>de</strong> pouvoir naviguer dans un résultat et surtout<br />
dans une requête. Comprendre la notion <strong>de</strong> jointure est indispensable pour écrire une requête qui y
fait appel et l’idée est ici que nous dotons notre programme orient objet d’un outil capable <strong>de</strong><br />
comprendre comment réaliser la navigation. Nous allons tenter <strong>de</strong> brièvement résumer son<br />
fonctionnement.<br />
Le concept <strong>de</strong>s expressions arborescentes est dérivé <strong>de</strong> celui <strong>de</strong>s expressions lambda. Premièrement,<br />
une expression arborescente est une expression lambda dont le co<strong>de</strong> va être maintenu en mémoire.<br />
Deuxièmement, cette expression peut être parcourue lors <strong>de</strong> l’exécution. Troisièmement, elle <strong>de</strong>vra<br />
être compilée pour pouvoir être exécutée dans le langage propre à la source <strong>de</strong> données. Tentons<br />
maintenant <strong>de</strong> faire le lien entre ces trois propriétés. Le co<strong>de</strong> sera maintenu en mémoire pour<br />
pouvoir être analysé pas à pas et modifié le cas échéant. L’expression <strong>de</strong>vra être compilée, donc son<br />
impact sur la source <strong>de</strong> données sera construit au moment <strong>de</strong> l’exécution. Il faut comprendre ici le<br />
terme « compilé » comme signifiant « transformé dans le langage propre à la source <strong>de</strong> données et<br />
optimisé pour plusieurs exécutions successives » 3 . La compilation classique, avec validation<br />
syntaxique, a bien entendu toujours lieu. Par exemple, si la requête implique une lecture dans une<br />
base <strong>de</strong> données relationnelle, la création <strong>de</strong> la requête Sql se fera à la volée. Une requête peut être<br />
jugée comme étant syntaxiquement correcte sans avoir besoin <strong>de</strong> connaître la nature <strong>de</strong> la source <strong>de</strong><br />
données. Pour pouvoir être compilée, la requête doit disposer <strong>de</strong> son co<strong>de</strong> en mémoire. Ce co<strong>de</strong> sera<br />
parcouru <strong>de</strong> manière à créer <strong>de</strong>s instructions pour la source <strong>de</strong> données. Ces instructions se<br />
composent <strong>de</strong> blocs logiques à la manière d’une équation mathématique. La longueur <strong>de</strong> ces<br />
instructions n’est pas fixée et l’expression qui en représente une possè<strong>de</strong> une structure en arbre,<br />
d’où le terme d’expressions arborescentes. En résumé, cela revient à dire qu’une expression<br />
arborescente représente un traitement générique (les paramètres sont fournis à l’exécution) et que<br />
le co<strong>de</strong> <strong>de</strong> cette expression est compilé à la volée, durant l’exécution du programme. L’intérêt est <strong>de</strong><br />
pouvoir construire <strong>de</strong>s requêtes dont les paramètres seront fournis à l’exécution et <strong>de</strong> choisir quand<br />
exécuter cette requête dans une optique d’optimisation. Cet aspect générique va permettre au<br />
développeur d’écrire une requête <strong>Linq</strong> paramétrique et <strong>de</strong> choisir à quel moment et vers quelle cible<br />
il déci<strong>de</strong>ra <strong>de</strong> l’exécuter. Cette requête pourra ainsi être traduite en une expression Sql si la source<br />
<strong>de</strong> données est relationnelle ou au contraire en une requête XQuery à <strong>de</strong>stination d’un fichier Xml.<br />
Jusqu’ici rien n’a été cité pour justifier l’utilisation d’autre chose qu’une expression lambda adaptée.<br />
En réalité, les possibilités offertes par les expressions lambda sont limitées lorsqu’un certain nombre<br />
d’appels, récursifs ou non, sont nécessaires en leur sein. Considérons le co<strong>de</strong> suivant (le co<strong>de</strong> et<br />
l’explication y afférante sont tirés <strong>de</strong> *1+):<br />
fac => x => == 0 ? 1 : x * fac(x-1)<br />
Ce co<strong>de</strong> ne compilera pas tel quel, il est impossible d’appeler une expression lambda <strong>de</strong>puis sa<br />
définition, ce que tente pourtant <strong>de</strong> faire l’appel « fac(x-1) ». Bien qu’il existe <strong>de</strong>s techniques pour<br />
palier à ce problème, nous n’allons pas les traiter ici. Le but était seulement <strong>de</strong> montrer avec un<br />
exemple simple que les expressions lambda ne permettent pas <strong>de</strong> tout faire. Rappelons que les<br />
expressions lambda sont utilisées par <strong>Linq</strong> pour représenter les clauses d’une requête. Mais certaines<br />
clauses peuvent être arbitrairement complexes. Pensons par exemple à la possibilité <strong>de</strong> réaliser une<br />
requête imbriquée dans une clause Where en Sql. Une simple expression lambda ne peut en ellemême<br />
représenter une clause réalisant une requête imbriquée, cela l’obligerait à s’appeler elle-<br />
3 Le terme compilé est traduit <strong>de</strong> l’Anglais « compiled » tel que mentionné dans *1+. L’explication fournie est en<br />
revanche personnelle.
même durant sa définition. Les expressions arborescentes permettent <strong>de</strong> résoudre ce problème en<br />
constituant une super expression composée <strong>de</strong> nœuds selon une structure en arbre. La construction<br />
finale <strong>de</strong> l’expression se fait en commençant par les feuilles <strong>de</strong> l’arbre, en remontant ensuite.<br />
L’ouvrage *1+ propose l’explication la plus claire qui a pu être trouvée sur la cuisine interne <strong>de</strong>s<br />
expressions arborescentes. Cette explication se base sur un exemple qui confronte une expression<br />
lambda avec une expression arborescente, le co<strong>de</strong> présenté ci-<strong>de</strong>ssous est tiré <strong>de</strong> cet exemple. Nous<br />
allons tenter <strong>de</strong> résumer cette explication afin <strong>de</strong> bien percevoir ce qu’est une expression<br />
arborescente. Commençons donc par un co<strong>de</strong> qui <strong>de</strong>vrait permettre d’afficher le type d’une<br />
expression lambda et celui d’une expression arborescente (certains extraits ont été traduits en<br />
<strong>de</strong>hors <strong>de</strong> quoi le co<strong>de</strong> est i<strong>de</strong>ntique à celui proposé dans l’ouvrage) :<br />
static void exemple()<br />
{<br />
Func lambdaInc = (x) => x + 1;<br />
Expression exprInc;<br />
exprInc = (x) => x + 1;<br />
}<br />
Console.WriteLine("avec expression lambda : {0}",<br />
lambdaInc.ToString());<br />
Console.WriteLine("avec expression arborescente : {0}",<br />
exprInc.ToString());<br />
Bien que la déclaration soit écrite <strong>de</strong> manière différente, le résultat est préssenti comme <strong>de</strong>vant être<br />
le même. Parlons maintenant du résultat non pas <strong>de</strong> ces <strong>de</strong>ux expressions mais <strong>de</strong> l’affichage dans la<br />
console. Le résultat produit par ce co<strong>de</strong> prend cette forme :<br />
Figure 2 : types d'expressions<br />
Notre expression lambda est en fait une référence vers un type créé par le compilateur, le type « `2 »<br />
prenant <strong>de</strong>ux paramètres entiers. Notre expression arborescente quant à elle semble contenir la<br />
structure <strong>de</strong> l’expression sous forme plus lisible. Rappelons-nous qu’une expression lambda est en<br />
réalité une abréviation pour l’écriture d’un délégué. Le compilateur crée donc un objet délégué d’un<br />
nouveau type, puisqu’une expression lambda fait usage du typage anonyme. Le type créé pour notre<br />
expression arborescente n’est pas un délégué, comme nous pouvons le voir. Pour découvrir<br />
comment est interprétée cette expression, [1] nous fournit le schéma page suivante ainsi que le co<strong>de</strong><br />
correspondant à ce schéma.
Pour réaliser une structure similaire, voici le co<strong>de</strong> proposé :<br />
static void equivalent()<br />
{<br />
Expression exprInc;<br />
}<br />
ConstantExpression constante = Expression.Constant(1,<br />
typeof(int));<br />
ParameterExpression parametre =<br />
Expression.Parameter(typeof(int), "x");<br />
BinaryExpression addition = Expression.Add(parametre,<br />
constante);<br />
exprInc = Expression.Lambda(addition, new<br />
ParameterExpression[] { parametre });<br />
Console.WriteLine("ex. arb. avec syntaxe equivalente :");<br />
Console.WriteLine("{0}", exprInc.ToString());<br />
Et ce co<strong>de</strong> produit le résultat que voici :<br />
Figure 3 : schéma d'une expression arborescente<br />
Figure 4 : type d'une expression arborescente<br />
Notons que les quelques lignes qui apparaissent entre la déclaration <strong>de</strong> l’expression et l’affichage en<br />
console, tout ce groupe représente l’équivalent <strong>de</strong> ce qui avait écrit la première fois comme<br />
« exprInc = (x) => x + 1 ». Nous avons bien généré l’arbre en partant <strong>de</strong>s feuilles, nœud par nœud.<br />
Sans compliquer trop l’explication, signalons juste que chaque nœud est matérialisé par un objet<br />
nœud <strong>de</strong> type générique, permettant d’assurer la navigation au sein <strong>de</strong> l’expression.
2.2.4 Extensions <strong>de</strong> métho<strong>de</strong><br />
« Extension <strong>de</strong> métho<strong>de</strong> » est le terme utilisé pour ajouter une métho<strong>de</strong> à une classe donnée sans<br />
manipuler le co<strong>de</strong> <strong>de</strong> cette classe. Cela reviendrait à créer une classe héritant <strong>de</strong> la classe <strong>de</strong> base en<br />
spécifiant la ou les métho<strong>de</strong>s que l’on souhaite y ajouter. Plus qu’une facilité syntaxique, l’extension<br />
<strong>de</strong> métho<strong>de</strong> permet d’éviter les pièges tendus par l’héritage et n’introduit pas <strong>de</strong> biais conceptuel<br />
dans la programmation. Une extension <strong>de</strong> métho<strong>de</strong> ne peut utiliser que les champs publics <strong>de</strong> la<br />
classe dont elle est l’extension. Elle doit elle-même être déclarée comme publique et statique. Son<br />
premier paramètre doit être précédé du mot-clé this et le type <strong>de</strong> ce paramètre définit le type dont<br />
la métho<strong>de</strong> est une extension. Le co<strong>de</strong> suivant (tiré <strong>de</strong> [11]) montre une extension <strong>de</strong> métho<strong>de</strong> ayant<br />
pour effet <strong>de</strong> représenter un nombre décimal sous forme <strong>de</strong> chaîne <strong>de</strong> caractères au format standard<br />
US :<br />
//Exemple fourni par <strong>Linq</strong> in Action<br />
public static string FormattedUS( this <strong>de</strong>cimal d) {<br />
return String.Format( formatUS, "{0:#;0.00}", d);<br />
}<br />
L’utilisation <strong>de</strong>s extensions <strong>de</strong> métho<strong>de</strong>s donnera peut-être l’impression <strong>de</strong> faire double emploi avec<br />
les métho<strong>de</strong>s virtuelles mais il faut gar<strong>de</strong>r à l’esprit qu’une métho<strong>de</strong> virtuelle est résolue à<br />
l’exécution alors qu’une extension <strong>de</strong> métho<strong>de</strong> doit être résolue dès la compilation. Précisons enfin<br />
que le type étendu peut être générique, ce qui offre la possibilité d’étendre un ensemble <strong>de</strong> types<br />
simultanément.<br />
Typiquement, les extensions <strong>de</strong> métho<strong>de</strong>s sont utilisées lors <strong>de</strong> l’appel <strong>de</strong> clauses dans les requêtes<br />
comme les clauses Where ou Select. Le framework .NET dans sa version 3 propose une interface<br />
générique IEnumerable pour représenter toute donnée susceptible <strong>de</strong> faire l’objet d’une requête<br />
ou d’une manipulation propre aux données. Des extensions <strong>de</strong> métho<strong>de</strong>s sont définies pour chaque<br />
clause avec cette interface (ou l’un <strong>de</strong> ses <strong>de</strong>scendants) comme type étendu.<br />
2.2.5 Formalisme <strong>de</strong> requêtes<br />
Cette sous-section s’inspire très fortement <strong>de</strong> *1+ et *11+. Les expressions <strong>de</strong> types requêtes ont été<br />
introduites avec la troisième version du framework .NET et définissent la forme générale d’une<br />
requête lorsqu’elle est écrite en C# ou en Visual Basic 2008. Une requête apparaît ici au sens large<br />
puisque la syntaxe sera la même que ce soit une requête vers une base <strong>de</strong> données relationnelle,<br />
vers une liste d’objets en mémoire ou encore vers un fichier XML. Une requête correctement<br />
formulée doit commencer par une clause From et se terminer par une clause Select ou une clause<br />
group. La clause From indique quels types d’objets vont être interrogés et ceux-ci doivent<br />
implémenter l’interface IEnumerable représentant une collection <strong>de</strong> données générique<br />
susceptible <strong>de</strong> faire l’objet d’une requête. Chaque clause <strong>de</strong> l’expression correspond à un ou<br />
plusieurs appels d’extensions <strong>de</strong> métho<strong>de</strong>s pour l’interface citée précé<strong>de</strong>mment. Voici un exemple<br />
<strong>de</strong> requête typique :<br />
var query = from e in etudiants<br />
where e.moyenne >= 10<br />
select e;<br />
Le compilateur interprétera l’expression ci-<strong>de</strong>ssus <strong>de</strong> la manière suivante :<br />
var query = etudiants.Where(e => e.moyenne >= 10).Select(e => e);
A noter que etudiants n’a pas été particularisé et pourrait être aussi bien une classe représentant<br />
une table <strong>de</strong> données relationnelle qu’une liste ordonnée d’objets en mémoire. Dans les <strong>de</strong>ux cas, le<br />
co<strong>de</strong> écrit reste correct.<br />
Les expressions <strong>de</strong> type requête sont traduites par le compilateur. La validité d’une requête et la<br />
vérification <strong>de</strong>s types sont établis lors <strong>de</strong> la phase <strong>de</strong> compilation du co<strong>de</strong>. Les fautes <strong>de</strong> frappe et les<br />
erreurs syntaxiques seront donc détectées par le compilateur, ce qui représente un avantage certain<br />
par rapport aux accès Sql ou Xml qui ne révèleront leurs erreurs qu’à l’exécution.<br />
Ceci permet déjà d’avoir une vue d’ensemble <strong>de</strong> <strong>Linq</strong> au travers <strong>de</strong>s enrichissements syntaxiques<br />
apportés par le framework version 3. Les expressions <strong>de</strong> type requête sont <strong>de</strong>s interrogations<br />
génériques qui appellent une succession <strong>de</strong> métho<strong>de</strong>s, chacune utilisant la réponse <strong>de</strong> la précé<strong>de</strong>nte<br />
comme paramètre. Les métho<strong>de</strong>s d’extensions simplifient l’écriture <strong>de</strong> ces appels, les rendant plus<br />
lisibles et plus faciles à implémenter. Les métho<strong>de</strong>s anonymes permettent la transmission <strong>de</strong>s<br />
paramètres d’un appel à l’autre. Les expressions lambda permettent <strong>de</strong> définir la logique <strong>de</strong> la<br />
plupart <strong>de</strong>s opérations effectuées par ces métho<strong>de</strong>s. Les types anonymes permettent <strong>de</strong> gérer en<br />
mémoire les résultats <strong>de</strong> requêtes et l’inférence sur le typage est ce qui permet au compilateur <strong>de</strong><br />
faire le lien entre toutes ces fonctionnalités et <strong>de</strong> les vali<strong>de</strong>r à la compilation. Avant <strong>de</strong> rentrer dans<br />
les détails <strong>de</strong> comment <strong>Linq</strong> est implémenté, <strong>de</strong>ux remarques sont encore à souligner.<br />
Premièrement, au vu <strong>de</strong>s nouveautés syntaxiques du langage, <strong>Linq</strong> apparaît d’ores et déjà comme<br />
une partie intégrante du langage et non comme un outil ORM 4 à greffer sur une architecture<br />
existante. Cela implique qu’utiliser <strong>Linq</strong> ne nécessite que le fait <strong>de</strong> programmer avec une version<br />
trois (ou supérieure) du framework .NET. L’autre point à soulever est que <strong>Linq</strong> n’est défini jusqu’ici<br />
qu’en termes d’interface générique (IEnumerable pour rappel) et <strong>de</strong> mécanismes <strong>de</strong> langage. Ceci<br />
implique <strong>de</strong>ux choses : <strong>Linq</strong> est défini en plusieurs implémentations <strong>de</strong> base et il est possible<br />
d’ajouter une implémentation <strong>de</strong> <strong>Linq</strong> pour s’adapter à un besoin précis.<br />
Ceci termine notre tour d’horizon <strong>de</strong>s changements généraux d’ordre purement syntaxique proposés<br />
avec la troisième version du framework .NET. Nous avons introduit les principaux mécanismes qui<br />
constituent <strong>Linq</strong> ainsi que leurs interactions, ayant souligné également que <strong>Linq</strong> se compose <strong>de</strong><br />
plusieurs implémentations. Nous allons <strong>de</strong> ce pas examiner ces différentes implémentations en<br />
détails.<br />
4 ORM signifie Object Relational Mapper. Il s’agit le plus souvent d’une désignation pour un utilitaire assurant<br />
une certaine automatisation dans la réalisation du mapping objet relationnel.
Chapitre 3 : Implémentation Objets<br />
Nous avons vu jusqu’ici quelle est la manière <strong>de</strong> formuler une requête syntaxiquement vali<strong>de</strong>,<br />
requête au sens <strong>de</strong> <strong>Linq</strong>. Rien n’a été spécifié en ce qui concerne la nature <strong>de</strong> la source <strong>de</strong> données,<br />
ce qui implique que tout ce qui a été vu jusqu’ici sera toujours valable tant que nous parlons <strong>de</strong> <strong>Linq</strong>.<br />
Mais <strong>Linq</strong> en lui-même n’est qu’un tas <strong>de</strong> concepts mis bout à bout. Si nous faisons le point <strong>de</strong> ce qui<br />
a déjà été vu, nous avons un formalisme pour écrire <strong>de</strong>s requêtes avec l’explication générale <strong>de</strong><br />
comment cette requête pourra être traduite vers une source <strong>de</strong> données, quelle qu’elle soit. C’est<br />
précisément cette opération <strong>de</strong> traduction qui va nécessiter <strong>de</strong> connaître cette source plus en<br />
détails. Pour répondre à cette attente, <strong>Linq</strong> est présenté avec une syntaxe unifiée, faisant abstraction<br />
(répétons-le une fois encore) <strong>de</strong> la nature du stockage <strong>de</strong>s données. Et pourtant, ce que nous<br />
appelons <strong>Linq</strong> <strong>de</strong>puis le début est en réalité un ensemble d’implémentations partageant une même<br />
syntaxe extérieure. Chaque implémentation est définie par un nom qui correspond au type <strong>de</strong><br />
données qu’elle cible. Ainsi nous parlerons <strong>de</strong> <strong>Linq</strong> to Sql, <strong>Linq</strong> to DataSet et ainsi <strong>de</strong> suite. Nous<br />
commencerons notre étu<strong>de</strong> par l’implémentation objet, <strong>Linq</strong> to Objects. Cette implémentation sera<br />
rapi<strong>de</strong>ment parcourue, examinant quelques aspects intéressants. Parmi ces aspects, nous nous<br />
intéresserons à certains opérateurs généraux (donc valables pour toutes les implémentations) et à<br />
l’interface IEnumerable que doit implémenter toute donnée désirant interagir avec <strong>Linq</strong>. Ce<br />
chapitre dans son ensemble est inspiré <strong>de</strong> [11] et <strong>de</strong> [1] pour la <strong>de</strong>scription <strong>de</strong>s opérateurs.<br />
3.1 Champ d’application<br />
<strong>Linq</strong> to Objects permet d’interagir avec toute collection qui implémente IEnumerable. Les<br />
collections standards intégrées au framework implémentent toutes cette interface, ce qui permet <strong>de</strong><br />
ne pas se poser la question dès lors qu’on utilise une structure <strong>de</strong> base. Pour ce qui est <strong>de</strong>s<br />
collections définies par l’utilisateur (le programmeur), le plus simple est <strong>de</strong> créer une collection par<br />
héritage d’une structure standard. Cela peut s’avérer être une erreur conceptuelle dans certains cas,<br />
et l’utilisateur <strong>de</strong>vra réaliser lui-même l’implémentation <strong>de</strong> cette interface. Ceci s’applique à toutes<br />
les implémentations <strong>Linq</strong>.<br />
Regardons d’un peu plus près cette fameuse interface. Elle est définie dans l’espace <strong>de</strong> noms<br />
System.Collections.Generic et la documentation 5 [4] montre que la seule métho<strong>de</strong> qui est renseignée<br />
est GetEnumerator qui doit retourner un objet capable <strong>de</strong> parcourir les éléments <strong>de</strong> la collection un à<br />
un. La documentation officielle fait également état d’un ensemble d’extensions <strong>de</strong> métho<strong>de</strong> qui<br />
représentent en fait l’ensemble <strong>de</strong>s opérateurs disponibles. Implémenter IEnumerable signifie<br />
possé<strong>de</strong>r les extensions <strong>de</strong> métho<strong>de</strong> nécessaires à <strong>Linq</strong> pour assurer le bon déroulement <strong>de</strong> requêtes<br />
écrites avec la syntaxe vue précé<strong>de</strong>mment. Nous allons maintenant nous intéresser à certains<br />
opérateurs, ne pouvant tous les détailler par manque <strong>de</strong> place (il y en a plus d’une centaine en<br />
comptant toutes les surcharges !).<br />
5 La référence exacte est : http://msdn.microsoft.com/en-us/library/9eekhta0.aspx
3.2 Quelques opérateurs à la loupe<br />
Les opérateurs représentent ce qu’il est possible <strong>de</strong> faire en utilisant <strong>Linq</strong> sur un ensemble <strong>de</strong><br />
données. Nous pouvons aisément nous imaginer qu’il est possible <strong>de</strong> réaliser une projection à la<br />
manière d’un Select en Sql ou même un filtrage avec une clause Where puisque ces opérations sont<br />
définies explicitement dans la syntaxe. Certaines fonctionnalités légèrement plus subtiles se sont<br />
néanmoins glissées dans <strong>Linq</strong> et nous allons tâcher d’en rassembler quelques unes. Nous parlerons<br />
ainsi <strong>de</strong> l’opérateur <strong>de</strong> projection groupée, <strong>de</strong> l’opérateur d’inversion, <strong>de</strong> l’opérateur <strong>de</strong><br />
groupement, <strong>de</strong>s quantificateurs et <strong>de</strong> l’opérateur <strong>de</strong> conversion en énumérable.<br />
3.2.1 Projection groupée<br />
L’opérateur <strong>de</strong> projection groupée est appelé officiellement SelectMany. Son comportement est<br />
globalement similaire à celui <strong>de</strong> l’opérateur Select, mais il a pour effet d’aplatir le résultat obtenu.<br />
Ainsi, une requête <strong>de</strong>mandant l’ensemble <strong>de</strong>s cours suivis par les étudiants <strong>de</strong> <strong>de</strong>rnière année<br />
recevrait une réponse sous la forme d’une collection <strong>de</strong> cours et non comme une collection <strong>de</strong><br />
collections <strong>de</strong> cours. Lorsque seules les valeurs distinctes doivent être reprises, il suffit d’appliquer au<br />
résultat l’opérateur Distinct. Il est intéressant <strong>de</strong> noter que l’aplatissement du résultat est disponible<br />
mais non imposé au programmeur. S’il désire travailler sur les collections, un Select suffira. S’il désire<br />
travailler sur les éléments eux-mêmes, un SelectMany constituera un raccourci appréciable.<br />
3.2.2 Inversion et in<strong>de</strong>xes<br />
Les opérateurs <strong>de</strong> tri classiques que sont Or<strong>de</strong>rBy et ThenBy permettent <strong>de</strong> trier les éléments d’un<br />
résultat pour peu que ceux-ci implémentent l’interface IComparable. Mais en certaines<br />
circonstances, il peut être intéressant d’avoir les <strong>de</strong>rniers éléments <strong>de</strong> la liste. Pour ce faire, <strong>Linq</strong><br />
propose un opérateur d’inversion appelé Reverse qui retourne l’ordre <strong>de</strong>s éléments au sein d’une<br />
collection. Rien d’extraordinaire jusqu’ici mais il est nécessaire d’introduire un autre petit concept,<br />
celui <strong>de</strong>s opérateurs à in<strong>de</strong>x. Il faut savoir que plusieurs opérateurs acceptent un argument<br />
supplémentaire <strong>de</strong> type entier et qui permet <strong>de</strong> ne considérer le résultat qu’après en avoir exclu un<br />
nombre d’éléments correspondant à la valeur passée en argument. Avec cette information,<br />
l’opérateur d’inversion permet d’inverser un résultat dont les x premières entrées ont été retirées ou<br />
d’ignorer les x <strong>de</strong>rnières entrées d’un résultat. Bien que cela puisse faire l’effet d’un simple gadget, il<br />
est intéressant <strong>de</strong> noter cette combinaison d’opérateurs qui permet <strong>de</strong> réaliser une sélection plus<br />
fine du résultat.<br />
3.2.3 Groupement<br />
L’opérateur <strong>de</strong> groupement, appelé GroupBy, réalise le groupement <strong>de</strong>s résultats selon une clé<br />
spécifiée. Une clause <strong>de</strong> groupement rend facultative la clause <strong>de</strong> projection (<strong>de</strong> sélection) car le<br />
groupement renvoie le résultat sous forme d’une collection <strong>de</strong> collections, chaque collection étant<br />
accessible par la valeur <strong>de</strong> sa clé associée. Le critère <strong>de</strong> groupement est défini par une expression<br />
lambda ce qui rend le groupement très lisible. Notons que le critère <strong>de</strong> groupement n’est pas tenu <strong>de</strong><br />
faire partie <strong>de</strong> la clause <strong>de</strong> projection, comme c’est le cas en Sql. L’opérateur <strong>de</strong> groupement<br />
possè<strong>de</strong> plusieurs surcharges, permettant <strong>de</strong>s réglages supplémentaires. Parmi ces surcharges, citons<br />
la possibilité <strong>de</strong> préciser un sélecteur d’éléments ainsi qu’un sélecteur <strong>de</strong> résultat. Un exemple<br />
d’utilisation se trouve à la page suivante.
var r = etudiants.GroupBy(<br />
e => e.facultes,<br />
e => new { e.nom, e.matricule },<br />
(key, elements) => new {<br />
Cle = key,<br />
Nbr = elements.Count()} );<br />
Le premier paramètre est le critère <strong>de</strong> groupement, le second est le sélecteur d’éléments qui va<br />
jouer le rôle d’opérateur <strong>de</strong> projection embarqué. C’est ce paramètre qui va définir quelles valeurs<br />
seront gardées lors <strong>de</strong> l’affichage du résultat. Le troisième paramètre est le sélecteur <strong>de</strong> résultat qui<br />
permet <strong>de</strong> réaliser une projection non plus sur les éléments mais sur les groupes, ce qui est<br />
particulièrement intéressant lorsqu’on souhaite obtenir <strong>de</strong>s informations d’agrégation sur les<br />
résultats sans le détail <strong>de</strong> ceux-ci. Le résultat produit par le co<strong>de</strong> ci-<strong>de</strong>ssus fournira <strong>de</strong>s informations<br />
sur nombre d’étudiants inscrits dans chaque faculté. Un groupement similaire mais sans le sélecteur<br />
<strong>de</strong> résultat donnerait le nom et le matricule <strong>de</strong> chaque étudiant, ceux-ci étant groupés par faculté.<br />
3.2.4 Quantificateurs<br />
Les quantificateurs sont bien pratiques lorsqu’il s’agit <strong>de</strong> faire <strong>de</strong>s manipulations complexes sur les<br />
données. Les fameux « Pour tout » et « Il existe » sont d’ailleurs une légère entrave en Sql puisque<br />
les quantificateurs universels n’y sont pas définis [12]. <strong>Linq</strong> fournit trois opérateurs <strong>de</strong> quantifications<br />
Any, All et Contains. Le quantificateur Any retourne la valeur true si au moins un élément <strong>de</strong> la<br />
collection sur laquelle il est appelé répond favorablement à l’expression lambda qui lui est passée en<br />
paramètre. Si aucune expression ne lui est passée, il répond simplement à la question « Y a-t-il un<br />
élément dans la collection ? ». Le quantificateur All permet <strong>de</strong> vérifier que l’ensemble <strong>de</strong>s éléments<br />
d’une collection vérifient un prédicat ayant la forme d’une expression lambda. En réalité cet<br />
opérateur tente <strong>de</strong> vérifier qu’aucun élément <strong>de</strong> la collection ne vérifie pas le prédicat, ce qui signifie<br />
qu’il retournera toujours true sur une collection vi<strong>de</strong>. Le quantificateur Contains tente <strong>de</strong> vérifier si<br />
une collection contient un élément spécifique. En termes objets, n’oublions pas que <strong>de</strong>ux éléments<br />
seront jugés i<strong>de</strong>ntiques si et seulement si leurs références pointent toutes <strong>de</strong>ux vers le même objet.<br />
Il est néanmoins possible <strong>de</strong> fournir un comparateur personnalisé pour déterminer l’égalité <strong>de</strong> <strong>de</strong>ux<br />
objets sur base d’autres critères que leurs simples références.<br />
3.2.5 Conversion en énumérable<br />
Il s’agit ici d’un opérateur prenant en paramètre une séquence d’objets et cet opérateur retournera<br />
<strong>de</strong>s valeurs i<strong>de</strong>ntiques mais sous forme d’objets énumérables, c’est-à-dire implémentant l’interface<br />
IEnumerable. Comment s’y prend-il ? Très simplement, en utilisant un énumérable générique<br />
dans lequel il encapsule les données <strong>de</strong> chaque élément. Cet énumérable générique est en fait une<br />
collection assez pauvre puisque son seul but va être <strong>de</strong> fournir accès aux extensions <strong>de</strong> métho<strong>de</strong>s qui<br />
caractérisent les énumérables. Toute autre information sera superflue puisque supposée déjà être<br />
présente dans la collection à convertir. Notons qu’il existe d’autres opérateurs <strong>de</strong> conversion tels que<br />
ToList, ToArray et ToDictionnary qui peuvent être utiles pour convertir un résultat dans un format<br />
plus classique. L’opération <strong>de</strong> conversion en énumérable est fortement utilisé avec l’implémentation<br />
<strong>Linq</strong> to DataSet, ceci sera développé dans la section consacrée à cette implémentation.
3.3 Exemple illustratif<br />
Considérons un graphe d’objets représenté par une liste <strong>de</strong> listes. A titre d’exemple, imaginons que<br />
les nœuds <strong>de</strong> ce graphe soient <strong>de</strong>s villes et que chaque ville soit reliée par une route à une ou<br />
plusieurs autres villes. Pour corser un peu la situation, nous allons doter nos villes d’informations<br />
supplémentaires, un nombre d’habitants, un nom et une liste <strong>de</strong> toutes les communes qui<br />
dépen<strong>de</strong>nt <strong>de</strong> celle-ci. Il faut bien sûr que chaque ville ait à sa disposition la liste <strong>de</strong>s villes auxquelles<br />
elle est connectée, cela constituera les branches <strong>de</strong> notre réseau. Le co<strong>de</strong> comprenant les définitions<br />
nécessaires à cet exemple se trouve en annexe (Annexe 1 : Co<strong>de</strong> <strong>de</strong> l’exemple <strong>Linq</strong> to object).<br />
Commençons par écrire une requête adressée au réseau et permettant <strong>de</strong> retrouver toutes les villes<br />
comptant au plus 10000 habitants. Ceci s’écrit comme suit :<br />
var petitesVilles = from v in reseauRoutier<br />
where v.Hab = 2<br />
&& c.Hab
Ceci produit le résultat suivant qui correspond bien à ce que nous attendions :<br />
Figure 5 : exemple <strong>Linq</strong> to object<br />
Les <strong>de</strong>ux clauses From représentent en réalité l’équivalent d’une jointure. Les relations sont<br />
accessibles <strong>de</strong> manière simple, il suffit d’avoir un accès à la propriété correspondante. Ceci permet<br />
<strong>de</strong> justifier l’emploi <strong>de</strong> <strong>Linq</strong> même dans un contexte déjà « tout objet ». <strong>Linq</strong> représente un moyen à<br />
la fois simple et puissant d’exprimer <strong>de</strong>s requêtes. Nous avons vu en détails plusieurs opérateurs<br />
ainsi qu’un exemple pratique mettant en scène l’implémentation objet <strong>de</strong> <strong>Linq</strong>. Nous allons<br />
maintenant nous pencher sur les sources <strong>de</strong> données relationnelles.
Chapitre 4 : Implémentations relationnelles<br />
La problématique <strong>de</strong> la persistance <strong>de</strong>s objets remonte probablement à la popularisation <strong>de</strong>s<br />
premiers langages orientés objets [3]. Les applications utilisent <strong>de</strong>s objets en mémoire pour décrire<br />
le mon<strong>de</strong> et ceux-ci disparaissent dès la fin <strong>de</strong> l’application. Pour les enregistrer, le recours à <strong>de</strong>s<br />
bases <strong>de</strong> données relationnelles reste une option plus que répandue [3]. Or le mon<strong>de</strong> objet et celui<br />
du relationnel ne s’interfacent pas parfaitement. Tout ceci n’est pas nouveau, bien sûr, mais pourtant<br />
<strong>de</strong> nouvelles solutions à ce problème continuent <strong>de</strong> voir le jour à un rythme régulier. <strong>Linq</strong> se<br />
présente comme l’une <strong>de</strong> ces solutions, voulant offrir une transition plus souple entre ces <strong>de</strong>ux<br />
mon<strong>de</strong>s. Nous allons voir comment <strong>Linq</strong> a été pensé pour interagir avec les données relationnelles.<br />
Nous commencerons par présenter quelques concepts à la base <strong>de</strong> la correspondance objetrelationnel<br />
telle que réalisée par <strong>Linq</strong>. Nous détaillerons ensuite les différentes implémentations<br />
relationnelles <strong>de</strong> <strong>Linq</strong>, en précisant leur moyen d’arriver à une correspondance entre objets et tables<br />
relationnelles. Nous tâcherons également <strong>de</strong> soulever les avantages et limitations <strong>de</strong> chaque<br />
implémentation. Après ces analyses, nous terminerons par un bilan global <strong>de</strong>s possibilités offertes<br />
par <strong>Linq</strong> pour manipuler <strong>de</strong>s bases <strong>de</strong> données. Ce chapitre est inspiré <strong>de</strong> *3+ pour l’introduction.<br />
Chaque section ayant été le fruit <strong>de</strong> recherches particulières, les références y seront détaillées au cas<br />
par cas.<br />
4.1 Entités en mémoire<br />
Cette section est tiré <strong>de</strong> [1] et [11]. Interroger une base <strong>de</strong> données suppose d’avoir préalablement<br />
établi un canal <strong>de</strong> communication ainsi qu’un langage commun. <strong>Linq</strong> ne permet pas d’écrire <strong>de</strong>s<br />
requêtes pour une base <strong>de</strong> données mais plutôt d’écrire <strong>de</strong>s requêtes qui seront soumises aux objets<br />
réalisant le mapping <strong>de</strong> la base <strong>de</strong> données. Il s’agit d’un mécanisme <strong>de</strong> cache où <strong>de</strong>s objets<br />
représentent l’état <strong>de</strong>s données dans la base. Nous appellerons ces objets <strong>de</strong>s entités et les<br />
définitions <strong>de</strong> leur classe seront appelées <strong>de</strong>s classes entités (ceci correspond à la nomenclature<br />
utilisée dans l’ouvrage *1+). Cette notion est centrale et doit toujours être gardée à l’esprit : nous ne<br />
pourrons jamais parler directement à la base <strong>de</strong> données. Chaque implémentation proposée par <strong>Linq</strong><br />
repose sur le concept <strong>de</strong>s entités, bien que les solutions varient <strong>de</strong> l’une à l’autre, comme nous allons<br />
le voir.<br />
4.2 <strong>Linq</strong> to Sql<br />
La première implémentation que nous allons étudier porte le nom officiel <strong>de</strong> « <strong>Linq</strong> to Sql » et nous<br />
pourrions intuitivement penser qu’il s’agit <strong>de</strong> l’implémentation avec un grand « i » au regard <strong>de</strong>s<br />
bases <strong>de</strong> données relationnelles. Nous verrons que ce n’est pas le cas. Cette section contient<br />
énormément d’informations tirées <strong>de</strong> *11+ pour les concepts théoriques, <strong>de</strong> *1+ et <strong>de</strong> *4+ pour<br />
combler les vi<strong>de</strong>s laissés par [11]. Nous allons voir comment cette implémentation réalise le mapping<br />
objet relationnel au niveau du co<strong>de</strong> et quels sont les outils qui permettront <strong>de</strong> nous ai<strong>de</strong>r dans cette<br />
tâche. Nous verrons quelles classes sont impliquées dans le mapping et quel est leur rôle, avec un cas<br />
concret. Nous abor<strong>de</strong>rons ensuite les possibilités <strong>de</strong> manipulation <strong>de</strong>s données et les contraintes<br />
associées. Nous terminerons l’étu<strong>de</strong> <strong>de</strong> cette implémentation par un bref passage en revue <strong>de</strong>s<br />
pièges à éviter lors <strong>de</strong> l’usage <strong>de</strong> <strong>Linq</strong> to Sql.
4.2.1 Mapping<br />
4.2.1.1Syntaxe au niveau du co<strong>de</strong><br />
Commençons par une petite précision. Les cibles <strong>de</strong> <strong>Linq</strong> to Sql doivent implémenter l’interface<br />
IQueryable qui est une <strong>de</strong>scendante <strong>de</strong> IEnumerable. L’explication est simplement que<br />
certaines possibilités disparaissent avec l’apparition <strong>de</strong>s bases <strong>de</strong> données, comme par exemple la<br />
notion d’ordre dans une table. Les opérateurs <strong>de</strong> sélection d’éléments permettant <strong>de</strong> choisir un<br />
élément du résultat sur base d’un in<strong>de</strong>x n’ont plus <strong>de</strong> raison d’être. Des notions <strong>de</strong> synchronisations<br />
entrent également en jeu, puisqu’il va falloir surveiller que nos entités n’aient pas fait l’objet d’une<br />
modification dans la base <strong>de</strong> données. Pour asseoir ce changement <strong>de</strong> comportement (qui pourrait<br />
entraîner <strong>de</strong>s erreurs), c’est une autre interface qui définit les extensions <strong>de</strong> métho<strong>de</strong>s. Cette<br />
précision faite, nous pouvons envisager la question du mapping.<br />
L’assemblage System.Data.<strong>Linq</strong> permet d’utiliser les classes entités dans le co<strong>de</strong>. Ici, l’esprit général<br />
<strong>de</strong>s entités est <strong>de</strong> concevoir une classe entité comme la représentation d’une table dans la base <strong>de</strong><br />
données. Cette représentation doit être explicitement renseignée dans la définition d’une classe, au<br />
moyen du mot clé « Table » suivi par l’expression « (Name= « NomDeLaTable ») », le tout, entre<br />
crochets, placé juste avant l’i<strong>de</strong>ntificateur <strong>de</strong> la classe. Chaque propriété <strong>de</strong> la classe représentant un<br />
attribut <strong>de</strong> la table doit être précédée par le mot clé « Column » entre crochets également. Lorsque<br />
le nom <strong>de</strong> la propriété est différent <strong>de</strong> celui <strong>de</strong> l’attribut, une expression « Name » similaire à celle <strong>de</strong><br />
la table doit être utilisée. Voici l’exemple d’une classe entité (cet exemple s’appuie sur *11+ et *1+) :<br />
[Table(Name="Clients")]<br />
public partial class Class1<br />
{<br />
[Column] private string IDClient;<br />
[Column(Name="Nom")]private string _champ2;<br />
}<br />
Cette classe est une correspondance <strong>de</strong> la table Clients <strong>de</strong> la base <strong>de</strong> données, table qui possè<strong>de</strong><br />
dans ses attributs au moins un champ « IDClient » et un champ « Nom ». Une classe entité n’est pas<br />
tenue d’effectuer le mapping <strong>de</strong> chaque attribut <strong>de</strong> la table. Elle peut également contenir <strong>de</strong>s<br />
attributs ou propriétés qui lui sont propres et qui ne participent pas au mapping. Il s’agit ici <strong>de</strong><br />
considérations syntaxiques mais comment le mapping est-il réellement effectué ? Nous n’avons<br />
spécifié aucune information qui pourrait permettre d’i<strong>de</strong>ntifier la base <strong>de</strong> données, nous avons<br />
seulement fait « l’autre moitié » du mapping. En effet, nous avons renseigné qu’une classe Class1 est<br />
une classe entité et que certaines <strong>de</strong> ses propriétés sont en correspondance directe avec <strong>de</strong>s<br />
attributs relationnels. Jetons maintenant un coup d’œil sur l’autre facette <strong>de</strong> ce mapping.<br />
4.2.1.2 Principes et fonctionnement<br />
La correspondance est réalisée au moyen <strong>de</strong> fichiers spécifiques pouvant être <strong>de</strong> <strong>de</strong>ux formats : le<br />
format Dbml (pour DataBase Markup Language) et le format Xml. Commençons par parler du format<br />
Dbml qui est considéré comme principal pour <strong>Linq</strong> to Sql. Le format Dbml est un langage à balises<br />
tout comme le Xml dont il est dérivé. Une définition <strong>de</strong> ce schéma est disponible avec Visual Studio<br />
2008, cette définition servant <strong>de</strong> validateur intégré. Dans un fichier Dbml représentant un mapping<br />
vali<strong>de</strong>, les premières lignes fournissent tout un tas <strong>de</strong> renseignements généraux comme la chaîne <strong>de</strong><br />
connexion à la base <strong>de</strong> données et le nom <strong>de</strong> la classe utilisée comme DataContext. Nous reparlerons<br />
plus amplement du DataContext dans une section ultérieure, considérons pour l’instant cette classe
comme gestionnaire <strong>de</strong>s objets entités. Viennent ensuite les définitions <strong>de</strong>s tables relationnelles et<br />
les classes entités qui leur correspon<strong>de</strong>nt. Les procédures stockées et les fonctions utilisateurs <strong>de</strong> la<br />
base <strong>de</strong> données sont également renseignées à cet endroit. Pour chaque table, les attributs et leurs<br />
propriétés Sql sont renseignés. En plus <strong>de</strong>s propriétés purement relationnelles, <strong>de</strong>s informations <strong>de</strong><br />
synchronisation sont fournies, comme IsDbGenerated dans l’exemple ci-<strong>de</strong>ssous. Ceci touche en<br />
particulier à la synchronisation qui sera abordée lorsque nous abor<strong>de</strong>rons les cas pratiques <strong>de</strong><br />
mapping. Voici un extrait d’un fichier Dbml 6 :<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
<br />
Lorsque le DataContext approprié est instancié, le fichier Dbml fournit toutes les informations<br />
nécessaires à la correspondance entre les mon<strong>de</strong>s objet et relationnel. Le lien entre ces <strong>de</strong>ux fichiers<br />
est très simple : le DataContext possè<strong>de</strong> <strong>de</strong>s références vers les classes entités, elles-mêmes validées<br />
par le Dbml du même nom que le DataContext 7 . Il faut savoir que le format Dbml est le format le<br />
plus riche en termes d’informations incluses dans le mapping mais il n’est pas le seul disponible. Il a<br />
été prévu qu’un fichier Xml puisse servir <strong>de</strong> source externe <strong>de</strong> mapping, le terme « externe » est le<br />
terme officiel utilisé par Microsoft (dans la documentation) et désigne le fait que en interne ce sera<br />
toujours un fichier Dbml qui sera manipulé même s’il pourra être créé <strong>de</strong>puis une source en Xml. Les<br />
informations fournies par un tel fichier Xml sont sensiblement moins fines que celles d’un fichier<br />
Dbml. En effet, les informations générales sur les tables et les entités sont présentes dans les <strong>de</strong>ux<br />
6 L’alignement <strong>de</strong>s balises laisse ici quelque peu à désirer, ceci étant du au manque <strong>de</strong> place.<br />
7 nomDataContext hérite <strong>de</strong> DataContexte et nom.dbml est le fichier <strong>de</strong> mapping.
cas, mais certaines informations concernant la synchronisation <strong>de</strong>s entités en mémoire ne peuvent<br />
pas être exprimées. Nous n’allons pas approfondir ces détails dans le cadre <strong>de</strong> cette étu<strong>de</strong>, signalons<br />
simplement que ces <strong>de</strong>ux formes <strong>de</strong> mappings sont équivalentes, l’une étant moins expressive que<br />
l’autre (<strong>de</strong>s réglages supplémentaires seront à la charge du programmeur s’il déci<strong>de</strong> d’utiliser cette<br />
métho<strong>de</strong>).<br />
4.2.1.3 Gestion par le DataContext<br />
A ce sta<strong>de</strong>, nous sommes en droit <strong>de</strong> poser la question suivante « Quand et Comment seront<br />
instantiées ces classes entités ? ». L’idée est que ces objets ne seront créés que lorsqu’ils seront<br />
nécessaires afin <strong>de</strong> ne pas encombrer la mémoire et une classe en particulier sera responsable <strong>de</strong><br />
leur gestion. Cette classe sera en quelque sorte le « chef d’orchestre » du mapping, il s’agit <strong>de</strong> la<br />
classe DataContext.<br />
Un objet <strong>de</strong> classe DataContext représente le lien entre la base <strong>de</strong> données et les classes entités qui<br />
lui sont assignées. Un DataContext contient une ou plusieurs classes entités et est responsable <strong>de</strong> la<br />
génération du co<strong>de</strong> Sql qui sera envoyé à la base <strong>de</strong> données. C’est toujours le DataContext qui va<br />
maintenir les entités, au besoin les créer et les mettre à jour. En pratique, la classe DataContext est le<br />
plus souvent dérivée et c’est un <strong>de</strong> ses <strong>de</strong>scendants qui est utilisé [1], la raison en <strong>de</strong>viendra<br />
apparente lorsque nous envisagerons notre cas pratique. Quand est-ce que les objets entités seront<br />
instanciés ? Lorsque le DataContext reçoit une requête, s’il n’a pas les entités nécessaires à la<br />
fabrication <strong>de</strong> la réponse, il va soumettre la requête à la base <strong>de</strong> données en créant les entités<br />
correspondantes. Nous <strong>de</strong>vons attirer l’attention sur le terme « reçoit » qui est laissé expréssément<br />
flou. <strong>Linq</strong> to Sql utilise une métho<strong>de</strong> d’exécution différée (<strong>de</strong>ferred loading en Anglais) pour ses<br />
requêtes. Cela signifie que lorsqu’une requête est soumise au DataContext, celui-ci ne va pas<br />
nécessairement la transmettre directement à la base <strong>de</strong> données 8 . Par contre, une provision<br />
d’entités sera créée pour pouvoir accélérer leur chargement le moment venu. Bien que cela puisse<br />
apparaître comme un mécanisme tordu, cela prend son sens dans un contexte où les accès à la base<br />
<strong>de</strong> données sont nombreux et éventuellement concurrentiels. Une politique d’optimisation complexe<br />
est déployée par <strong>Linq</strong> mais nous en reparlerons plus tard. Nous avons vu jusqu’ici le rôle d’un<br />
DataContext vis-à-vis <strong>de</strong>s entités, tant celles qui se trouvent en mémoire que celles qui séjournent<br />
dans les tables relationnelles. Nous verrons plus loin comment la théorie du mapping est mise en<br />
pratique et quel y est le rôle du DataContext.<br />
8 Il s’agit là d’une interprétation <strong>de</strong> diverses sources contradictoires. *11+ précise que chaque appel au<br />
DataContexte entraîne une réaction vers la base <strong>de</strong> données, sans préciser sa nature. [4] reste très flou. [1] dit<br />
clairement que pour une lecture, l’interaction n’est pas initiée par le programmeur.
4.2.2 Opérations fondamentales<br />
Nous allons explorer dans cette sous-section les procédures dites CUD (Create Update Delete) et<br />
comment elles sont traduites vers les habituelles instructions Sql INSERT, UPDATE et DELETE. Nous<br />
examinerons en priorité les mécanismes sous-jacents et comment ceux-ci font pour surveiller les<br />
éventuels changements dans les entités. Chacune <strong>de</strong>s opérations sera ensuite analysée pour avoir<br />
une idée globale <strong>de</strong> la gestion <strong>de</strong>s entités vis-à-vis du support relationnel en arrière-plan. De<br />
nombreuses informations apparaissant ici sont directement tirées <strong>de</strong> [4], <strong>de</strong>s sources non officielles<br />
[13] ont contribué à éclaircir les points obscurs.<br />
Pour pouvoir considérer <strong>de</strong>s entités et leur évolution, il faut pouvoir agir sur ces entités et disposer<br />
d’un système capable <strong>de</strong> gérer ces modifications. Nous allons commencer par nous intéresser à ce<br />
<strong>de</strong>rnier. Pour <strong>Linq</strong> to Sql, ce système porte le nom <strong>de</strong> « change tracking service » que nous<br />
appellerons le service sentinelle bien que ceci ne constitue en aucun cas une appellation officielle.<br />
Cette sentinelle est chargée <strong>de</strong> conserver les états originaux <strong>de</strong>s entités <strong>de</strong> manière à pouvoir<br />
détecter les changements. L’autre fonction <strong>de</strong> cette sentinelle est <strong>de</strong> pouvoir recréer les<br />
modifications observées en termes d’instructions Sql, pour éviter <strong>de</strong> systématiquement inclure les<br />
valeurs <strong>de</strong> tous les attributs d’une entité [4]. Le service sentinelle a recours à un système <strong>de</strong><br />
marquage pour i<strong>de</strong>ntifier les entités modifiées. Les marques désignent l’état courant <strong>de</strong> l’entité,<br />
l’état par défaut étant « non modifiée ». Des états comme « à insérer », « à supprimer » ou « à<br />
mettre à jour » permettent <strong>de</strong> rapi<strong>de</strong>ment savoir quelle modification a été apportée à l’entité. Le<br />
service sentinelle est accessible <strong>de</strong>puis le DataContext via l’appel <strong>de</strong> la métho<strong>de</strong> GetChangeSet [1]. La<br />
sentinelle est active en permanence et collecte le moindre changement survenu sur une entité.<br />
Lorsque le DataContext reçoit l’instruction d’exécuter les modifications faites jusqu’alors, le service<br />
sentinelle vérifie l’état <strong>de</strong> toutes les entités en mémoire et créer le co<strong>de</strong> Sql nécessaire pour obtenir<br />
les mêmes modifications dans la base <strong>de</strong> données.<br />
Les ajouts et suppressions d’entités se font <strong>de</strong> manière relativement similaire, nous allons donc les<br />
examiner ensemble. Ajouter une entité se fait <strong>de</strong> la même manière qu’un ajout <strong>de</strong> n’importe quel<br />
type d’objets, par un appel au constructeur. La suppression d’une entité implique <strong>de</strong> l’avoir<br />
préalablement isolée, autrement dit d’avoir en main l’objet entité en question. Pour l’une ou l’autre<br />
<strong>de</strong> ces opérations, il suffit désormais d’appliquer les changements au DataContext en marquant<br />
l’entité à modifier. Marquer une entité « à insérer » se fait en appelant InsertOnSubmit sur le<br />
DataContext en passant en paramètre une référence vers l’entité à marquer. Pour réaliser la même<br />
chose lors d’une suppression, il faut utiliser DeleteOnSubmit, toujours en passant la référence<br />
adéquate en paramètre. A noter qu’une version a été développée pour chacune <strong>de</strong> ces métho<strong>de</strong>s <strong>de</strong><br />
manière à opérer en une fois l’opération sur une collection d’entités. Ces versions groupées sont<br />
appelées InsertAllOnSubmit et DeleteAllOnSubmit respectivement. Ces variantes groupées ne sont là<br />
que dans le but simplifier l’écriture, aucune forme d’optimisation par groupement n’a lieu 9 .<br />
La mise à jour d’une entité est légèrement différente. Elle nécessite d’avoir isolé l’entité et d’avoir<br />
changé la valeur d’au moins une <strong>de</strong> ses propriétés. <strong>Linq</strong> to Sql n’autorise pas la modification <strong>de</strong> la<br />
propriété renseignée comme étant la clé primaire. Ceci pourrait être intéressant lorsqu’on souhaite<br />
remplacer une entité par une autre en prenant en compte la participation à <strong>de</strong>s relations entre<br />
entités. Une mise à jour <strong>de</strong> ce type se fait par insertion <strong>de</strong> la nouvelle valeur et remplacement <strong>de</strong><br />
9 Ceci a été vérifié en rebasculant le log d’écriture Sql vers Console.out : y défile une longue suite <strong>de</strong> requêtes<br />
Sql indépendantes.
l’ancienne valeur dans chaque relation à laquelle elle participe. Notons également que les mises à<br />
jour sont les seules opérations pouvant être invisibles au programmeur inattentif. Dans le cadre<br />
d’une relation entre entités, s’il l’un <strong>de</strong>s participants est supprimé l’autre subira une mise à jour pour<br />
refléter que cette relation n’existe plus [4] [11]. Cette mise à jour n’aura éventuellement pas été<br />
spécifiée explicitement. De même, lorsqu’une association est créée en passant à une entité une<br />
référence vers une autre entité, l’entité reliée (mais non modifiée explicitement) sera mise à jour.<br />
Ajouter ou modifier une entité du DataContext n’est en soi que la moitié du travail. Les entités en<br />
mémoire ne sont plus synchronisées par rapport à la base <strong>de</strong> données et les modifications désirées<br />
n’y seront pas encore effectives [1]. Pour notifier le DataContext qu’une mise à jour est souhaitée, il<br />
faut le faire explicitement par le biais <strong>de</strong> la métho<strong>de</strong> SubmitChanges. Cette opération va prendre en<br />
charge toutes les modifications apportées aux entités, qu’il s’agisse <strong>de</strong> créations, <strong>de</strong> mises à jour ou<br />
<strong>de</strong> suppressions. C’est lors <strong>de</strong> l’appel <strong>de</strong> cette métho<strong>de</strong> que le service sentinelle va répertorier toutes<br />
les modifications apportées aux entités. Des instructions Sql vont ensuite être générées pour amener<br />
la base <strong>de</strong> données correspondant aux entités en mémoire. Ceci a plusieurs implications en termes<br />
<strong>de</strong> performances et d’ordonnancement <strong>de</strong>s opérations. Premièrement, chaque appel <strong>de</strong> la métho<strong>de</strong><br />
SubmitChanges peut potentiellement donner lieu à <strong>de</strong>s opérations inattendues à cet instant précis<br />
[4]. En effet, la sentinelle enregistre toutes les modifications et il est tout à fait possible qu’une autre<br />
partie du programme ait créé <strong>de</strong>s modifications entre une insertion et sa soumission quelques lignes<br />
plus loin. Dans un contexte multithreads ou dans celui d’une programmation par événements,<br />
l’enchaînement exact <strong>de</strong> chaque instruction est au mieux dur à prévoir. Les mises à jour implicites<br />
sont également à ranger dans cette catégorie d’opérations fantômes. Deuxième remarque à<br />
soulever, le service sentinelle passe en revue chaque entité en mémoire à chaque appel <strong>de</strong> cette<br />
fameuse métho<strong>de</strong>. La seule opération effectuée sur une entité non modifiée consiste à tester si son<br />
marquage est toujours égal à « non modifiée ». Pour un ensemble conséquent d’entités, ceci peut<br />
créer un délai non désiré dont il faut rester conscient. Il est, par exemple, conseillé d’éviter d’appeler<br />
SubmitChanges dans une boucle <strong>de</strong>stinée à créer un ensemble d’objets.<br />
4.2.3 Cas pratiques<br />
Il nous reste maintenant à découvrir les techniques pratiques pour construire un mapping et son<br />
DataContext associé. Nous allons ensuite soumettre quelques requêtes et observer comment nous<br />
parviennent les résultats. Nous envisagerons d’abord un cas très simple à vocation illustrative avant<br />
<strong>de</strong> considérer <strong>de</strong>s scénarios plus complexes.<br />
Supposons que nous ayons une base <strong>de</strong> données Sql Server 2008 pleinement fonctionnelle à notre<br />
disposition et que notre application ne souhaite pas prendre en charge les accès concurrentiels pour<br />
l’instant. Nous allons réaliser tout cela avec Visual Studio 2008, étape par étape.<br />
En ayant créé un nouveau projet C#, nous allons ouvrir l’explorateur <strong>de</strong> serveurs pour y localiser<br />
notre base <strong>de</strong> données. Lorsque nous l’avons trouvée, nous pouvons désormais voir les tables qu’elle<br />
contient, ses procédures stockées, ses fonctions utilisateurs, les associations entre les tables et <strong>de</strong><br />
nombreuses autres informations dont nous ne ferons pas usage dans ce simple petit test. Ajoutons<br />
une classe à notre projet dont le type sera « <strong>Linq</strong> to Sql class ». Les classes <strong>Linq</strong> to Sql arborent<br />
l’extension Dbml, aussi nous pouvons en conclure qu’il s’agira <strong>de</strong> notre fichier <strong>de</strong> mapping.<br />
Choisissons lui un nom <strong>de</strong> circonstance (par exemple « MonMapping ») et validons notre choix afin<br />
<strong>de</strong> créer ce fameux fichier <strong>de</strong> mapping. Rappelons que la structure d’un tel fichier, vue à la section
précé<strong>de</strong>nte, est assez complexe et semble assez rébarbative à établir. Aller vérifier chacune <strong>de</strong>s<br />
nombreuses propriétés <strong>de</strong> chaque attribut d’une table, pour toutes les tables impliquées dans le<br />
mapping, cela semble totalement rebutant. Et bien, réjouissons-nous, cela n’a pas à être fait. En<br />
effet, à la création <strong>de</strong> notre classe à l’extension Dbml, Visual Studio nous présente un éditeur<br />
graphique où nous pouvons, à notre convenance, créer <strong>de</strong>s entités <strong>de</strong>puis la base <strong>de</strong> données ou <strong>de</strong><br />
toutes pièces pour les ajouter dans le futur. Créer une classe entité <strong>de</strong>puis la base <strong>de</strong> données, cela<br />
signifie faire du « glisser-déplacer » <strong>de</strong>puis l’explorateur <strong>de</strong> serveurs vers l’espace d’édition<br />
graphique. Toutes les métadonnées sont automatiquement extraites <strong>de</strong>puis les tables concernées,<br />
mettant directement à jour le fichier Dbml. Ajoutons <strong>de</strong>ux tables à celui-ci. Si nous consultons le<br />
fichier Dbml, nous pouvons y trouver désormais la définition d’une classe nommée<br />
MonMappingDataContext héritant <strong>de</strong> DataContext et <strong>de</strong>s <strong>de</strong>ux classes entités que nous avons créées<br />
par « glisser-déplacer ». La génération <strong>de</strong>s informations <strong>de</strong> mapping ainsi que celle <strong>de</strong> notre classe<br />
héritant <strong>de</strong> DataContext. Voici pourquoi nous avions dit plus haut qu’en pratique c’est une classe<br />
dérivée qui est utilisée. Cela nous permet également <strong>de</strong> définir plusieurs contextes et mappings<br />
différents si tel est notre bon vouloir. De retour à notre exemple, nous pouvons à présent écrire <strong>de</strong>s<br />
requêtes pour nos nouvelles classes entités, le compilateur aura à sa disposition toutes les<br />
informations nécessaires pour juger <strong>de</strong> la validité syntaxique <strong>de</strong> cette requête. C’est ce que nous<br />
allons faire.<br />
MonMappingDataContext db = new MonMappingDataContext();<br />
var query = from c in db.<strong>de</strong>mo_customer<br />
//clause where eventuelle<br />
select c.customer_name;<br />
Console.WriteLine("Voici la table <strong>de</strong>mo_customer :");<br />
foreach (var q in query)<br />
{<br />
Console.WriteLine(q);<br />
}<br />
En exécutant ce co<strong>de</strong> sur le mapping considéré précé<strong>de</strong>mment (nommé MonMapping), nous<br />
retrouvons notre DataContext ainsi que la structure <strong>de</strong>s expressions <strong>de</strong> requêtes. Notons la simplicité<br />
d’utilisation : le mapping a été créé <strong>de</strong> manière presqu’entièrement automatique, une ligne pour<br />
instancier notre DataContext, <strong>de</strong>ux lignes pour notre requête et l’affaire est faite. Nous avons par<br />
ailleurs la sécurité <strong>de</strong> la validation par le compilateur, aucun co<strong>de</strong> « unchecked » n’est utilisé.<br />
Procédons maintenant à une insertion et à une suppression. Le co<strong>de</strong> correspondant à ces opérations<br />
est le suivant :<br />
Console.WriteLine("Ajoutons un nouveau client avec <strong>Linq</strong>");<br />
<strong>de</strong>mo_customer dc = new <strong>de</strong>mo_customer();<br />
dc.customer_name = "nouveau client";<br />
db.<strong>de</strong>mo_customer.InsertOnSubmit(dc);<br />
db.SubmitChanges();<br />
Console.ReadLine();<br />
Console.WriteLine("Voici la nouvelle table <strong>de</strong>mo_customer :");<br />
foreach (var q2 in query)<br />
{
}<br />
Console.WriteLine(q2);<br />
Console.WriteLine("Supprimons le nouvel ajout");<br />
db.<strong>de</strong>mo_customer.DeleteOnSubmit(dc);<br />
db.SubmitChanges();<br />
Console.ReadLine();<br />
Console.WriteLine("Regardons a nouveau la table <strong>de</strong>mo_customer :");<br />
var query2 = from c in db.<strong>de</strong>mo_customer<br />
select new { c.customer_id, c.customer_name };<br />
foreach (var q3 in query2)<br />
{<br />
Console.WriteLine(q3);<br />
}<br />
Console.ReadLine();<br />
Et le résultat <strong>de</strong> l’ensemble <strong>de</strong> ce co<strong>de</strong> est le suivant :<br />
Figure 6 : requêtes <strong>Linq</strong> to Sql<br />
Tout cela paraît bien simple et automatisation rime souvent avec perte <strong>de</strong> contrôle sur les<br />
mécanismes sous-jacents. En effet, revenons à nos hypothèses <strong>de</strong> départ, à savoir que nous ayons<br />
accès à une base <strong>de</strong> données Sql Server 2008 pleinement opérationnelle et que nous ne nous<br />
préoccupions pas <strong>de</strong>s accès concurrentiels. Dans la pratique, il est tout à fait possible que nous ayons<br />
affaire à une base <strong>de</strong> données dont la structure soit connue mais sans l’avoir à disposition. De même,<br />
nous serions bien impru<strong>de</strong>nts en affirmant qu’un projet quelconque utilisera une base <strong>de</strong> données<br />
Sql Server 2008. Bon, et qu’est-ce que cela change pour nous si nous n’avons pas la base <strong>de</strong> données<br />
sous la main ? Et bien, l’extraction automatique <strong>de</strong>s métadonnées se glisse hors <strong>de</strong> notre portée et<br />
nous <strong>de</strong>vrons trouver un moyen d’obtenir la structure <strong>de</strong> la base <strong>de</strong> données. Rappelons que le
mapping peut-être effectué par un fichier Dbml ou Xml, qui sont globalement <strong>de</strong>s fichiers textes et<br />
donc, faciles à échanger via le réseau car <strong>de</strong> taille mo<strong>de</strong>ste. Visual Studio 2008 offre la possibilité<br />
d’extraire facilement un fichier Dbml <strong>de</strong>puis une base <strong>de</strong> données, ne reste alors plus qu’à le rendre<br />
disponible via un service web ou un procédé semblable. D’accord, mais que se passera-t-il si notre<br />
base <strong>de</strong> données n’est pas (ou n’est plus) du type Sql Server 2008 ? La réponse va paraître brutale,<br />
cela ne marchera tout simplement pas, <strong>Linq</strong> to Sql est prévu pour être utilisé uniquement avec Sql<br />
Server. Attention, il est question ici <strong>de</strong> « <strong>Linq</strong> to Sql » et rappelons nous qu’il ne s’agit que d’une<br />
implémentation <strong>Linq</strong> et non <strong>de</strong> « <strong>Linq</strong> vers relationnel » dans son ensemble. Plus qu’un tour <strong>de</strong><br />
passe-passe linguistique, il s’agit <strong>de</strong> <strong>de</strong>ux choses différentes. <strong>Linq</strong> to Sql peut être vu comme un<br />
raccourci simplifié pour les utilisateurs <strong>de</strong> Sql Server alors que les autres variantes <strong>de</strong> <strong>Linq</strong> offrent<br />
d’autres possibilités pour relier le mon<strong>de</strong> relationnel et celui <strong>de</strong>s objets. Nous détaillerons ces autres<br />
possibilités dans les sections qui leur sont dédiées.<br />
4.2.4 Erreurs et difficultés<br />
Pour terminer l’analyse pratique <strong>de</strong> <strong>Linq</strong> to Sql, nous allons envisager <strong>de</strong>ux cas plus complexes mais<br />
néanmoins tout à fait plausibles. Tout d’abord le cas où nous n’avons tout simplement pas <strong>de</strong> base<br />
<strong>de</strong> données, il arrive pour certains projets <strong>de</strong> <strong>de</strong>voir créer leur base <strong>de</strong> données « from scratch ».<br />
Nous allons voir quelle ai<strong>de</strong> <strong>Linq</strong> peut apporter dans ce contexte et quelles en sont les inconvénients.<br />
Nous allons ensuite examiner la gestion <strong>de</strong>s accès concurrentiels et les diverses erreurs considérées<br />
comme typiques. Cette sous-section est tirée <strong>de</strong> [1], plus particulièrement du chapitre 5 : Managing<br />
Sql Data.<br />
Dans le cas où nous n’avons pas encore <strong>de</strong> base <strong>de</strong> données, nous pouvons soit la créer <strong>de</strong> manière<br />
classique, soit utiliser <strong>Linq</strong> pour le faire. Rappelons-nous lors <strong>de</strong> notre premier test, nous avions à<br />
notre disposition une boîte à outil fournie par Visual Studio. Cette boîte à outil porte en réalité le<br />
nom <strong>de</strong> « Object Relational Designer » ou Concepteur Objet Relationnel pour rester dans la langue<br />
<strong>de</strong> Molière. Ce concepteur nous permet <strong>de</strong> définir <strong>de</strong> nouvelles entités, sans nous préoccuper <strong>de</strong>s<br />
données relationnelles existantes (c’est-à-dire que cette possibilité est disponible même si la<br />
génération <strong>de</strong>s entités <strong>de</strong>puis une base <strong>de</strong> données a été utilisée). Ces entités ne correspondant à<br />
aucune table relationnelle, elles sont inutilisables telles quelles. Néanmoins une instance d’un<br />
DataContext peut créer une base <strong>de</strong> données <strong>de</strong>puis son schéma <strong>de</strong> mapping en faisant appel à la<br />
fonction CreateDatabase définie pour la classe DataContext. L’ouvrage [1] nous met en gar<strong>de</strong> contre<br />
une telle pratique, précisant que ce n’est à utiliser que lorsque les classes entités sont jugées plus<br />
importantes que la structure relationnelle. En effet, le passage d’une base <strong>de</strong> données à un fichier<br />
Dbml n’est pas parfaitement bidirectionnel et certaines opérations ne sont pas possibles comme par<br />
exemple la création <strong>de</strong> procédures stockées ou <strong>de</strong> triggers. Autrement dit, nous pouvons utiliser<br />
cette métho<strong>de</strong> lorsque la base <strong>de</strong> données est utiliser pour servir l’application et non le contraire,<br />
auquel cas nous <strong>de</strong>vrions créer notre base <strong>de</strong> données avec du co<strong>de</strong> Sql. Cela reste très intéressant<br />
car nous pouvons désormais réaliser la persistance d’objets sans <strong>de</strong>voir écrire la moindre ligne <strong>de</strong><br />
co<strong>de</strong> Sql et nous pouvons profiter <strong>de</strong> la validation <strong>de</strong>s requêtes à la compilation, ce qui simplifie les<br />
tests et réduit le temps <strong>de</strong> développement.<br />
Les bases <strong>de</strong> données sont énormément utilisées dans le mon<strong>de</strong> du web. Cet environnement à la<br />
particularité <strong>de</strong> pouvoir provoquer <strong>de</strong>s accès concurrentiels n’importe quand. Profitons <strong>de</strong> passage<br />
pour préciser ce que nous entendons par « accès concurrentiels ». Nous allons parler ici d’accès<br />
concurrentiels pour désigner les problématiques différentes que sont les opérations concourantes
sur <strong>de</strong>s données (typiquement <strong>de</strong>man<strong>de</strong> <strong>de</strong> lecture d’une donnée en cours <strong>de</strong> modification ou à<br />
modifier), les transactions (avec mécanismes <strong>de</strong> roll-back si satisfaire la transaction s’avère<br />
impossible) et la gestion <strong>de</strong>s exceptions Sql [12]. Dans ces trois domaines, la presque totalité <strong>de</strong>s<br />
problèmes pouvant survenir sont découverts à l’exécution, ce qui est toujours gênant lorsqu’on<br />
souhaite offrir un accès en ligne aux ressources. <strong>Linq</strong> manipule les objets entités en mémoire et donc<br />
crée un délai supplémentaire durant lequel <strong>de</strong>s accès concurrentiels peuvent apparaître. Un objet<br />
entité contient plusieurs indicateurs permettant <strong>de</strong> savoir s’il correspond ou non à son homologue en<br />
base <strong>de</strong> données et le DataContext peut donc détecter lorsqu’un conflit survient. Il est possible<br />
d’assigner une politique <strong>de</strong> gestion <strong>de</strong> conflits plus ou moins fine selon ce qui est désiré. Nous<br />
n’allons pas trop nous étendre là-<strong>de</strong>ssus, les heuristiques <strong>de</strong> résolution <strong>de</strong> tels conflits dépassent<br />
largement le cadre <strong>de</strong> ce travail. Nous nous contenterons <strong>de</strong> dire que les opérations concourantes<br />
ont été envisagées et que le DataContext est en mesure <strong>de</strong> réaliser une gestion <strong>de</strong> conflits assez<br />
performantes pour peu qu’une politique adaptée lui soit fournie. Les informations <strong>de</strong><br />
synchronisations jouent également un rôle important à ce niveau. En effet, lorsque le comportement<br />
est laissé par défaut (il s’agit du IsDbGenerated), le DataContext ira régulièrement s’informer auprès<br />
<strong>de</strong> la base <strong>de</strong> données pour s’enquérir d’éventuels changements survenus pour chaque attribut<br />
ayant ce comportement. Le comportement inverse est également possible. Lorsqu’une entité doit<br />
modifier l’une <strong>de</strong> ses propriétés, elle en avertit le DataContext et celui-ci va marquer l’entité avec le<br />
statut « modifié » le temps <strong>de</strong> répercuter les changements dans la base <strong>de</strong> données. Cela nous laisse<br />
déjà entrevoir les possibilités <strong>de</strong> conflits engendrés par <strong>de</strong>s opérations concourantes. Microsoft, par<br />
le biais <strong>de</strong> la bibliothèque en ligne MSDN [4], propose plusieurs politiques <strong>de</strong> gestion <strong>de</strong> conflits mais,<br />
comme dit plus haut, tout cela dépasse le cadre <strong>de</strong> notre étu<strong>de</strong>.<br />
Les transactions sont utilisées systématiquement par les objets <strong>de</strong> type DataContext. Cela n’a rien<br />
d’étrange quand nous avons vu que le DataContext est l’unique lien entre la base <strong>de</strong> données et<br />
l’ensemble <strong>de</strong>s entités qui lui sont associées. Lorsque le DataContext se voit soumettre une ou<br />
plusieurs requêtes, il effectue en priorité les changements sur les entités en mémoire et va, selon la<br />
disponibilité <strong>de</strong> la connexion à la base <strong>de</strong> données, soumettre à son tour les changements à la base<br />
<strong>de</strong> données. Chaque requête soumise au DataContext est encapsulée dans une transaction au sens<br />
relationnel du terme. Il est néanmoins possible pour le programmeur d’élargir une transaction, en y<br />
ajoutant <strong>de</strong>s modifications. La classe TransactionScope (disponible dans la bibliothèque<br />
System.Transactions) permet cette opération le plus naturellement qui soit [4]. Il suffit d’instancier<br />
un nouvel objet TransactionScope sans paramètre et d’y inclure l’ensemble <strong>de</strong>s opérations<br />
souhaitées, comme le montre le morceau <strong>de</strong> co<strong>de</strong> suivant :<br />
using (TransactionScope ts = new TransactionScope())<br />
{<br />
monDataContext.SubmitChanges();<br />
//Soumission <strong>de</strong> l'ensemble <strong>de</strong>s requêtes en attente<br />
}<br />
//... co<strong>de</strong> supplémentaire éventuel<br />
ts.Complete();<br />
Nous avons encore à abor<strong>de</strong>r le problème <strong>de</strong>s exceptions. Les auteurs <strong>de</strong> « Programming <strong>Linq</strong> » [1]<br />
nous présentent trois sources principales d’exceptions, la création d’un DataContext, les accès à une<br />
base données en lecture et ceux en écriture. Notons que <strong>Linq</strong> ne possè<strong>de</strong> pas d’exceptions qui lui
sont propres. La plupart <strong>de</strong>s exceptions classiques peuvent être rencontrées en utilisant « <strong>Linq</strong> to<br />
Sql » néanmoins nous n’allons détailler que celles qui sont typiques <strong>de</strong> son utilisation.<br />
Les erreurs susceptibles d’apparaître lors <strong>de</strong> la création d’un DataContext sont du type<br />
InvalidOperationException et indiquent un problème au niveau du mapping. De par leur nature, ces<br />
exceptions « sont considérées comme étant irrécupérables ( « unrecoverable » ) la plupart du<br />
temps » [1]. Ce genre d’erreurs est « très peu probable si le mapping est généré automatiquement »<br />
[1].<br />
Lors <strong>de</strong> l’accès en lecture à une base <strong>de</strong> données, plusieurs types <strong>de</strong> problèmes peuvent survenir :<br />
l’accès peut être impossible, refusé, la structure <strong>de</strong>s tables peut être différente <strong>de</strong> ce qui était prévu<br />
et ainsi <strong>de</strong> suite avec la presque totalité <strong>de</strong>s problèmes pouvant survenir lors <strong>de</strong> l’accès à une base <strong>de</strong><br />
données relationnelle. Le principal problème avec l’accès en lecture est qu’il est impossible <strong>de</strong><br />
prévoir quand il aura lieu exactement. En effet, nous avions vu dans les précé<strong>de</strong>ntes sections que<br />
<strong>Linq</strong> to Sql ne permettait <strong>de</strong> parler directement qu’au DataContext (c’est-à-dire aux représentations<br />
en mémoire que sont les entités) et jamais à la base <strong>de</strong> données. Et pire encore, le DataContext va<br />
lui-même effectuer le travail en arrière-plan (groupement <strong>de</strong> requêtes et analyses pour maintenir la<br />
synchronisation principalement) responsable d’avoir déclenché l’exception sans que vous puissiez<br />
avoir directement connaissance <strong>de</strong> la nature <strong>de</strong> ces menues opérations ni du moment précis où elles<br />
auront lieu. En imaginant une application multi tiers, les causes d’erreur sur les accès à la base <strong>de</strong><br />
données peuvent se retrouvés en <strong>de</strong> nombreux points (nous parlons seulement ici <strong>de</strong>s accès en<br />
lecture, rappelons-le). Nous pourrons toutefois nous consoler en constatant que la plupart <strong>de</strong>s<br />
erreurs courantes auront été détectées dès la compilation et la syntaxe Sql, véritable nid à problèmes<br />
pour le développeur orienté objet, est l’affaire du DataContext et non celle du programmeur<br />
désormais.<br />
Les accès en écriture ont lieu quant à eux à <strong>de</strong>s moments relativement contrôlés. A ces instants, le<br />
DataContext répercute toutes les modifications apportées aux entités sur la base <strong>de</strong> données. Du fait<br />
<strong>de</strong> cette écriture décalée et d’éventuelles opérations concourantes, nous <strong>de</strong>vons envisager la<br />
possibilité que <strong>de</strong>s opérations inattendues aient lieu à chaque écriture. Ceci veut dire qu’une écriture<br />
est susceptible <strong>de</strong> déclencher n’importe quel type d’exception Sql [11][1][4]. Ceci rejoint gran<strong>de</strong>ment<br />
le problème <strong>de</strong>s opérations concourantes et <strong>de</strong> la mise en place d’une gestion <strong>de</strong> conflits. Nous<br />
<strong>de</strong>vions cependant souligner le fait que d’autres erreurs peuvent survenir lors <strong>de</strong> la phase d’écriture.<br />
C’est le cas <strong>de</strong>s violations <strong>de</strong> contraintes d’intégrité, <strong>de</strong>s dépassements <strong>de</strong> délais lors <strong>de</strong> l’attente<br />
d’une réponse et ainsi <strong>de</strong> suite, la liste étant longue. <strong>Linq</strong> to Sql ne propose aucun remè<strong>de</strong> miracle<br />
contre l’apparition <strong>de</strong> ces exceptions et elles doivent être gérées comme dans le cadre d’un accès<br />
relationnel classique.
4.2.5 Performances<br />
Les questions <strong>de</strong> performance sont toujours à gar<strong>de</strong>r à l’œil lorsque mapping il y a. En effet, le<br />
mapping n’est jamais qu’une ou plusieurs couches insérées dans une pile parfois déjà conséquente.<br />
La performance n’est pas toujours facile à cerner, en revanche. Nous pouvons d’ores et déjà parler<br />
<strong>de</strong> performances exprimées en temps développeurs car nous avons vu ce qu’il en était <strong>de</strong> la syntaxe<br />
et <strong>de</strong> la simplicité <strong>de</strong> mise en œuvre. Le travail est plus rapi<strong>de</strong> et moins porteur d’erreurs ([1] utilise<br />
les termes <strong>de</strong> « less error prone » pour désigner le développement avec <strong>Linq</strong> to Sql). Si le temps <strong>de</strong><br />
mise en œuvre est réduit par rapport à une approche ADO.NET qu’en est-il <strong>de</strong> la qualité ? Les co<strong>de</strong>s<br />
auto-générés sont souvent pointés du doigt dans ce domaine. Et les temps <strong>de</strong> réponses, seront-ils<br />
satisfaisant ? Nous allons réaliser plusieurs tests afin <strong>de</strong> nous forger notre propre opinion.<br />
4.2.5.1 Test d’insertions<br />
Nous allons ici envisager un scénario très simple, à savoir une succession d’insertions dans une table<br />
relationnelle vierge. Nous mettrons en compétition une version <strong>Linq</strong> to Sql avec une version<br />
ADO.NET. Les requêtes seront groupées ainsi que le ferait une application avec base <strong>de</strong> données<br />
locale, dans un but <strong>de</strong> réduction du nombre d’accès disques. La situation est exprimée ci-<strong>de</strong>ssous en<br />
pseudo co<strong>de</strong> :<br />
DateTime star t = now ; //start pour le test 1<br />
for(int i=1 ; i < nbrOps ; ++i)<br />
{<br />
requete.ajout(insertion unitaire);<br />
}<br />
requete.Execute() ;<br />
DateTime end = now ; //Ecart = end – start<br />
Les co<strong>de</strong>s utilisés lors <strong>de</strong> ce test sont disponibles en annexe (Annexe 2 : co<strong>de</strong>s <strong>de</strong> tests <strong>de</strong><br />
performances pour <strong>Linq</strong> to Sql). Après plusieurs exécutions <strong>de</strong> la version <strong>Linq</strong> pour un même nombre<br />
d’opérations, un phénomène curieux apparaît. Cette version est plus lente à la première exécution<br />
du test, et les temps <strong>de</strong> réponses se stabilisent dès la <strong>de</strong>uxième exécution du test. C’est assez<br />
inattendu et rien, à ce sta<strong>de</strong> <strong>de</strong> notre étu<strong>de</strong>, ne semble pouvoir justifier cela. [1] nous apprend que le<br />
DataContext rassemble toutes les informations nécessaires au mapping lors <strong>de</strong> sa première<br />
utilisation. Le mapping est gardé en mémoire jusqu’à son remplacement par un autre mapping ou<br />
jusqu’à la fermeture <strong>de</strong> Visual Studio. Le gain <strong>de</strong> temps observé varie entre quelques dixièmes à <strong>de</strong>ux<br />
secon<strong>de</strong>s, quel que soit le nombre d’opérations <strong>de</strong>mandées par la suite. Pour revenir à notre test,<br />
nous pouvons désormais comparer la version ADO.NET aux <strong>de</strong>ux temps <strong>de</strong> réponse <strong>de</strong> la version<br />
<strong>Linq</strong>. De manière surprenante, la version <strong>Linq</strong> a été observée comme étant plus rapi<strong>de</strong> pour ses <strong>de</strong>ux<br />
temps <strong>de</strong> réponse. Ceci semble aller contre toute la logique du mapping et en réalité, c’est là qu’un<br />
examen plus approfondi du co<strong>de</strong> permettra <strong>de</strong> dénicher l’astuce. Pour ce premier test, la version<br />
ADO.NET a été construite <strong>de</strong> manière très naïve. La requête étant représenté par un string, celui-ci<br />
est modifié à chaque tour <strong>de</strong> boucle pour représenter une requête <strong>de</strong> plus en plus longue. La<br />
manipulation <strong>de</strong> ce string en mémoire va entraîner un nombre d’opérations responsables <strong>de</strong><br />
l’accumulation <strong>de</strong> ce retard. Notons déjà ici une <strong>de</strong>s forces <strong>de</strong> <strong>Linq</strong> to Sql qui est <strong>de</strong> réaliser<br />
l’optimisation pour le programmeur alors que la version ADO.NET peut fonctionner en étant très mal<br />
programmée. En ne considérant que les temps d’accès à la base <strong>de</strong> données, la version ADO.NET<br />
s’est montrée plus rapi<strong>de</strong>, ce qui était beaucoup plus prévisible. Le temps d’exécution nécessaire<br />
varie presque du simple au double entre les <strong>de</strong>ux approches dans ces conditions. Ces temps varient<br />
linéairement avec le nombre d’opérations à effectuer. L’ergonomie et la productivité du développeur
<strong>Linq</strong> se paient par une hausse conséquente <strong>de</strong>s temps d’accès (ceci est confirmé par [11] et [1]). Le<br />
graphique ci-<strong>de</strong>ssous montre les résultats moyens observés durant les <strong>de</strong>ux précé<strong>de</strong>nts tests. L’axe<br />
<strong>de</strong>s ordonnées représente les temps moyen d’exécution exprimés en millisecon<strong>de</strong>s, alors que les<br />
abscisses représentent le nombre d’opérations d’insertion successives effectuées durant le test. Les<br />
courbes suivies <strong>de</strong> la mention « (with Q) » désignent les résultats du premier test, où le temps<br />
d’exécution comprend également le temps nécessaire à la construction <strong>de</strong> la requête. Pour les<br />
courbes qui ne sont pas suivies par cette mention, il s’agit <strong>de</strong>s résultats obtenus lors du second test<br />
où le temps d’exécution considéré ne comprend que le temps nécessaire à la connexion directe à la<br />
base <strong>de</strong> données. Voici la situation exprimée en pseudo co<strong>de</strong> :<br />
DateTime star t = now ; //start pour le test 1<br />
for(int i=1 ; i < nbrOps ; ++i)<br />
{<br />
requete.ajout(insertion unitaire);<br />
}<br />
DateTime start2 = now ; //start pour le test 2<br />
requete.Execute() ;<br />
DateTime end = now ; //Ecart = end – start<br />
Figure 7 : test d'insertions (temps <strong>de</strong> réponse (ms) en ordonnées, nbr d'insertions en abscisse)
4.2.5.2 Test en situation concurrentielle<br />
Le test suivant porte sur un contexte où les connexions sont nombreuses et indépendantes. Pour<br />
simuler cette situation, nous allons simplement imposer au programme <strong>de</strong> ne considérer que <strong>de</strong>s<br />
insertions simples directement transmises à la base <strong>de</strong> données. Comme précé<strong>de</strong>mment, un grand<br />
nombre d’insertions seront effectuées mais cette fois elles seront transmises directement à la base<br />
<strong>de</strong> données sans regroupement. Bien qu’il ne s’agisse pas d’un scénario vraiment réaliste, cela suffira<br />
pour se faire une idée <strong>de</strong>s performances relatives <strong>de</strong> <strong>Linq</strong> et d’une connexion plus traditionnelle.<br />
Le graphique qui suit ce paragraphe montre les temps nécessaires (exprimés en millisecon<strong>de</strong>s) à<br />
l’envoi <strong>de</strong> toutes les requêtes une à une pour les <strong>de</strong>ux métho<strong>de</strong>s, le nombre <strong>de</strong> requêtes <strong>de</strong> chaque<br />
lot étant repris en ordonnée. On voit clairement que la courbe intitulée « <strong>Linq</strong> sequence» croît<br />
exponentiellement avec l’augmentation du nombre <strong>de</strong> requêtes à satisfaire. L’explication principale<br />
en est que <strong>Linq</strong> doit manipuler <strong>de</strong>s objets en mémoire comme nous l’avons vu dans la section<br />
« Opérations fondamentales » <strong>de</strong> ce chapitre, <strong>de</strong>rnier paragraphe. Chaque ajout créé un objet<br />
supplémentaire et cette modification a lieu en mémoire centrale, ce qui sera fait beaucoup plus<br />
rapi<strong>de</strong>ment que l’envoi <strong>de</strong> la requête à la base <strong>de</strong> données. Chaque soumission entraîne l’analyse du<br />
marquage <strong>de</strong> chaque objet, ce qui ralentit l’ensemble du programme. La métho<strong>de</strong> ADO.NET,<br />
représentée par la courbe « ADO sequence », ne manipule pas d’autres objets que les strings utilisés<br />
pour construire les requêtes, celles-ci étant ensuite transmises au gestionnaire <strong>de</strong> la base <strong>de</strong><br />
données. Les temps d’accès varient donc selon une progression linéaire. La troisième courbe<br />
présente sur le graphe représente les temps d’accès correspondant pour une utilisation idéalisée <strong>de</strong><br />
<strong>Linq</strong>. Utilisation idéalisée car l’envoi <strong>de</strong>s requêtes n’est fait qu’au <strong>de</strong>rnier moment, économisant ainsi<br />
sur les temps d’analyse <strong>de</strong> marquages par la sentinelle. L’appel à la fonction SubmitChanges réalise<br />
l’appel à la base <strong>de</strong> données et toutes les entités marquées comme étant « à insérer » vont générer<br />
une requête Sql correspondant à leur insertion. En n’effectuant cette tâche qu’une seule fois, on<br />
aperçoit que le gain <strong>de</strong> temps est conséquent 10 . Dans la réalité pratique, on pourrait regrouper<br />
plusieurs requêtes par petits lots afin d’économiser un peu sur les accès successifs à la base <strong>de</strong><br />
données. Mais un appel unique est impensable, du fait que « la fin » est impossible à prévoir en<br />
pratique ainsi qu’à cause <strong>de</strong>s problèmes d’accès concurrentiels aux données. Les possibilités<br />
d’optimiser <strong>Linq</strong> relèvent principalement <strong>de</strong> la gestion <strong>de</strong>s conflits, <strong>de</strong> la synchronisation et du <strong>de</strong>sign<br />
<strong>de</strong> l’application. Les performances en situation réelles sont certainement moins bonnes que le cas<br />
idéal mais vraisemblablement plus proches <strong>de</strong> cette valeur que <strong>de</strong> celle <strong>de</strong> la version naïve.<br />
10 Cette observation semble n’apparaître nulle part, *4+ le laisse tout juste sous-entendre.
Figure 8 : tests en situation concurrentielle (temps <strong>de</strong> réponse (ms) en ordonnées, nbr d'insertions en abscisse)<br />
4.2.6 Conclusion<br />
<strong>Linq</strong> to Sql effectue la correspondance entre objets et tables relationnelles avec <strong>de</strong>s fichiers<br />
spécifiques. Nous avons vu comment ces fichiers peuvent être automatiquement générés, délivrant<br />
le développeur <strong>de</strong> la tâche ardue qu’est la réalisation d’un mapping objet relationnel. Les<br />
mécanismes <strong>de</strong> gestion et <strong>de</strong> modifications <strong>de</strong>s entités sont intuitifs et rapi<strong>de</strong>ment mis en œuvre,<br />
comme nous l’avons fait avec un exemple concret. Plusieurs pièges restent tendus, guettant le<br />
développeur inattentif mais ceux-ci sont nettement moins nombreux que dans un contexte d’accès<br />
aux données en Sql. La plupart du co<strong>de</strong> est vérifié à la compilation et un système <strong>de</strong> gestion <strong>de</strong><br />
conflits diminuera encore davantage les risques liés aux accès concurrentiels. En ce qui concerne la<br />
performance, <strong>Linq</strong> s’est montré plus lent qu’une approche ADO.NET mais tout en restant dans <strong>de</strong>s<br />
limites acceptables. Le seul dommage semble être que <strong>Linq</strong> to Sql n’est en réalité que <strong>Linq</strong> to Sql<br />
Server, ce qui limite fortement son champ d’application 11 .<br />
11 Cette information est relativement dissimulée. C’est par le réseau développez.com que j’en ai eu<br />
connaissance *13+, ce n’est apparu dans *1+ qu’au 12 e chapitre !
4.3 <strong>Linq</strong> to DataSet<br />
Un DataSet est quelque chose <strong>de</strong> très proche <strong>de</strong> la notion <strong>de</strong>s classes entités vues précé<strong>de</strong>mment. Il<br />
s’agit d’une représentation en mémoire d’une collection <strong>de</strong> données. Précisons qu’un DataSet est<br />
une représentation générique puisque la collection <strong>de</strong> données est une représentation d’une source<br />
<strong>de</strong> données que cette <strong>de</strong>rnière soit d’origine relationnelle ou non. Les DataSets font partie <strong>de</strong> la<br />
couche <strong>de</strong> données ADO.NET et n’ont donc pas été introduits par <strong>Linq</strong>. La couche d’accès aux<br />
données ADO.NET fait un grand usage <strong>de</strong>s DataSets, notamment en lecture pour conserver <strong>de</strong>s<br />
informations <strong>de</strong> structure liées aux données. Il est assez logique que <strong>Linq</strong> propose une<br />
implémentation <strong>de</strong>stinée à interroger ces structures. Nous allons poursuivre notre étu<strong>de</strong> avec<br />
l’analyse cette implémentation nommée <strong>Linq</strong> to DataSet. Cette section est gran<strong>de</strong>ment inspirée <strong>de</strong><br />
[1] et <strong>de</strong> [4]. Comme son nom l’indique, cette implémentation a pour cibles <strong>de</strong>s objets <strong>de</strong> type<br />
DataSet. Cette section ne sera pas découpée en sous-parties car <strong>Linq</strong> n’introduit que peu <strong>de</strong><br />
nouveautés en ce qui concerne les DataSets. L’étu<strong>de</strong> approfondie <strong>de</strong>s mécanismes sous-jacents aux<br />
DataSets en général n’entre pas dans le cadre <strong>de</strong> ce travail et nous les passerons sous silence. Pour<br />
pouvoir être utilisable par une implémentation <strong>Linq</strong>, il est nécessaire d’implémenter l’interface<br />
IEnumerable. Sans nous étendre sur le sujet, signalons qu’il y a moyen <strong>de</strong> créer <strong>de</strong>s DataSets<br />
typés mais en toute généralité un DataSet doit être considéré comme non typé et n’implémente<br />
donc pas la fameuse interface. Nous ne considèrerons ici que les DataSets non typés et nous<br />
tenterons d’éclaircir comment les utiliser en tant que IEnumerable.<br />
Etant un concept abstrait, les DataSets peuvent être construits <strong>de</strong>puis n’importe quel type <strong>de</strong> base<br />
<strong>de</strong> données. Ils disposent en outre <strong>de</strong> mécanismes pour recréer la structure <strong>de</strong>s tables relationnelles<br />
sous-jacentes. Un mapping ? En effet, cela y ressemble fortement, mais celui-ci doit être<br />
explicitement spécifié « à la main » par le développeur. Pour interroger <strong>de</strong> telles structures, <strong>Linq</strong> a<br />
réalisé une implémentation spécifique appelée « <strong>Linq</strong> to DataSet ». Ceci permet <strong>de</strong> bénéficier <strong>de</strong><br />
toutes les améliorations apportées par <strong>Linq</strong> sur <strong>de</strong>s structures pouvant s’interfacer sur n’importe<br />
quel type <strong>de</strong> base <strong>de</strong> données. Ceci n’est en aucun cas le remè<strong>de</strong> universel au vi<strong>de</strong> laissé par <strong>Linq</strong> to<br />
Sql puisque ce ne sont que les DataSets qui sont interrogeables. Leur construction ainsi que le suivi<br />
<strong>de</strong>s modifications vers la base <strong>de</strong> données sont encore à faire. Un DataSet représentant une cache <strong>de</strong><br />
base <strong>de</strong> données est classiquement construit à partir d’un DataAdapter (c’est-à-dire en utilisant la<br />
couche ADO.NET). A noter qu’il est possible d’utiliser <strong>Linq</strong> pour remplir un DataSet sans passer par un<br />
DataAdapter, mais cela suppose d’avoir préalablement récupéré les données et <strong>de</strong> les avoir sous<br />
forme IEnumerable.<br />
Pour pouvoir utiliser <strong>Linq</strong> sur une table d’un DataSet, il faut donc explicitement créer un mapping<br />
avec la fonction TableMappings.Add(string sourceTable, string DataSetTable) du DataSet. Pour que<br />
cette table soit énumérable, il faut utiliser l’opérateur AsEnumerable qui sera, dans ce cas-ci, utilisé<br />
sur une DataTable. Voyons un exemple utilisant une base <strong>de</strong> données MySql (sa procédure <strong>de</strong><br />
construction est détaillée dans l’annexe 3 : utilisation d’une base <strong>de</strong> données MySql avec <strong>Linq</strong> to<br />
DataSet).
using MySql.Data;<br />
using MySql.Data.MySqlClient;<br />
class Program<br />
{<br />
static void Main(string[] args)<br />
{<br />
MySqlConnection con = new MySqlConnection<br />
("Database=My<strong>Linq</strong>Sql;Uid='root'");<br />
con.Open();<br />
}<br />
DataSet ds = new DataSet("MySqlDataSet");<br />
string selectString = @"SELECT * FROM objects1";<br />
MySqlDataAdapter da = new<br />
MySqlDataAdapter(selectString,con);<br />
da.TableMappings.Add("objects1", "Table");<br />
da.Fill(ds);<br />
}<br />
//interrogation du DataSet ds avec <strong>Linq</strong> to DataSet<br />
DataTable dt1 = ds.Tables["Table"];<br />
var dataSetQuery = from o in dt1.AsEnumerable()<br />
select new {ID = o.Field("Id"), TEXT =<br />
o.Field("Desc")};<br />
foreach(var res in dataSetQuery)<br />
{<br />
Console.WriteLine(res);<br />
}<br />
//Fermeture <strong>de</strong> la connexion<br />
con.Close();<br />
Console.WriteLine("Appuyez sur Enter pour terminer...");<br />
Console.ReadLine();<br />
Le résultat <strong>de</strong> ce co<strong>de</strong>, pour la base <strong>de</strong> données considérée est le suivant :<br />
Figure 9 : <strong>Linq</strong> to DataSet pour interroger un DataSet construit avec MySql<br />
Revenons maintenant sur les particularités <strong>de</strong> ce co<strong>de</strong>. Nous avons bien créé une connexion MySql<br />
que nous ouvrons et nous instancions un nouveau DataSet nommé MySqlDataSet. Nous créé un<br />
nouveau DataAdapter avec l’instruction <strong>de</strong> sélectionner toutes les lignes <strong>de</strong> la table objects1. Un<br />
mapping est ensuite créé pour pouvoir retrouver dans le DataSet la table équivalente à objects1 sous<br />
le nom <strong>de</strong> Table. Le DataSet est rempli et nous définissons une nouvelle DataTable avec Table. Vient<br />
ensuite notre requête qui a toutes les apparences d’une requête <strong>Linq</strong> qui <strong>de</strong>vrait maintenant nous<br />
être familière. Néanmoins, la référence à la table utilise l’opérateur AsEnumerable car une DataTable<br />
n’implémente pas IEnumerable, comme nous l’avions dit plus haut. Mais nous voyons également
que les attributs <strong>de</strong> cette table doivent être accédés par la métho<strong>de</strong> Field en précisant<br />
explicitement la nature <strong>de</strong> T, car le compilateur ne pourrait réaliser l’inférence <strong>de</strong>puis un champ<br />
d’une DataTable. Ceci n’est simplement pas prévu. Peut-être cela changera-t-il dans le futur, mais<br />
pour l’heure nous <strong>de</strong>vons nous-mêmes fournir les informations qui sont en réalité <strong>de</strong>s informations<br />
<strong>de</strong> mapping encore une fois. Pour en revenir à l’opérateur AsEnumerable, il s’agit d’un opérateur<br />
réalisant la conversion à la volée. Il s’applique à une séquence d’éléments et va les encapsuler dans<br />
une nouvelle séquence d’objets implémentant IEnumerable. Cet opérateur peut être redéfini,<br />
offrant ainsi la possibilité <strong>de</strong> créer <strong>de</strong>s variantes adaptées au contexte.<br />
En ce qui concerne les performances, la question est <strong>de</strong> mettre en balance ce que <strong>Linq</strong> apporte et les<br />
délais introduits. Le mapping est réalisé par le DataSet lui-même et <strong>de</strong>s instructions explicites au<br />
niveau du co<strong>de</strong>. Aucune logique <strong>de</strong> mapping automatique ne vient alourdir l’usage classique d’un<br />
DataSet. Les performances en termes <strong>de</strong> temps <strong>de</strong> réponses seront fonction du contexte et non du<br />
choix d’avoir ou non utilisé <strong>Linq</strong>. Notons tout <strong>de</strong> même que l’utilisation <strong>de</strong> telles structures n’est pas<br />
toujours aisée et en particulier traduire une requête en langage naturel <strong>de</strong>vrait être beaucoup plus<br />
simple en utilisant <strong>Linq</strong>.<br />
Avec <strong>Linq</strong> to DataSet, nous avons une solution portable vers tout type <strong>de</strong> base <strong>de</strong> données et même<br />
vers d’autres systèmes <strong>de</strong> persistance car, rappelons-le, un DataSet représente simplement une<br />
collection <strong>de</strong> données. Cela permet d’utiliser le confort lié aux requêtes <strong>de</strong> <strong>Linq</strong>, certes, mais <strong>Linq</strong> to<br />
DataSet <strong>de</strong>man<strong>de</strong> pas mal <strong>de</strong> réglages <strong>de</strong> la part du développeur, notamment d’assurer le suivi entre<br />
le DataSet et la base <strong>de</strong> données. Il s’agit là d’un inconvénient majeur puisque cela peut amener <strong>de</strong>s<br />
problèmes liés à la synchronisation ainsi que toute la panoplie d’erreurs classiques liées à l’accès aux<br />
données en Sql. Ceci ne constitue pas alternative à <strong>Linq</strong> to Sql mais plutôt comme un complément<br />
pour répondre aux besoins <strong>de</strong>s développeurs ayant choisi <strong>de</strong> manipuler <strong>de</strong>s DataSets ou ayant à<br />
maintenir <strong>de</strong>s applications qui en font toujours usage. [4] maintient que « les DataSets sont une<br />
composante importante <strong>de</strong> la couche ADO.NET », ce qui fait <strong>de</strong> <strong>Linq</strong> to DataSet un outil appréciable.<br />
4.4 <strong>Linq</strong> to Entities<br />
Une autre implémentation ayant pour cible <strong>de</strong>s données relationnelles s’appelle <strong>Linq</strong> to Entities. Le<br />
terme Entities dans son nom fait référence à la technologie Entity Framework, qui est une évolution<br />
<strong>de</strong> la couche ADO.NET que nous appellerons par raccourci EF. EF est légèrement plus récent encore<br />
que <strong>Linq</strong>. Nous allons brièvement introduire les idées fondatrices <strong>de</strong> cette technologie ainsi que ses<br />
principaux concepts. Nous tenterons ensuite <strong>de</strong> mettre en lumière les forces et faiblesses <strong>de</strong> ce<br />
framework, tout en particularisant cela aux contributions <strong>de</strong> <strong>Linq</strong> to Entities. Cette section puise ses<br />
informations principalement <strong>de</strong> [5] pour EF et <strong>de</strong> [1] pour <strong>Linq</strong> to Entities.<br />
4.4.1 Concepts fondateurs<br />
L’idée au départ <strong>de</strong> l’EF est celle-là même qui a amené <strong>Linq</strong>. Nous pourrions résumer cette idée <strong>de</strong><br />
base en « une application orientée objet ne <strong>de</strong>vrait pas dépendre <strong>de</strong>s mécanismes <strong>de</strong> persistance ».<br />
Nous n’allons pas expliquer à nouveau le problème <strong>de</strong> correspondance entre l’orienté objet et le<br />
relationnel mais nous allons plutôt ce problème dans le contexte <strong>de</strong> la <strong>de</strong>uxième édition du<br />
framework .NET. A cette époque pas si lointaine d’ailleurs, les accès relationnels se faisaient en<br />
utilisant les services <strong>de</strong> la couche ADO.NET, elle-même une évolution <strong>de</strong>s premiers services ADO<br />
(ADO signifie ActiveX Data Object). Cette nouvelle couche apportait son lot <strong>de</strong> nouveautés, parmi<br />
lesquelles l’interface IDbConnection, qui permettait <strong>de</strong> considérer une connexion générique à une
ase <strong>de</strong> données. Chaque fournisseur <strong>de</strong> données (il faut comprendre par là tant MySql que Oracle<br />
ou Sql Server) implémentait cette interface au sein d’une classe concrète nommée client<br />
(MySqlClient, OracleClient …). De même, la lecture <strong>de</strong>s données se faisait en remplissant un DataSet<br />
pouvant lui-même gar<strong>de</strong>r une structure très proche <strong>de</strong>s tables relationnelles <strong>de</strong> la source. Pour en<br />
revenir à l’idée énoncée plus haut, une application <strong>de</strong> l’époque dépendait <strong>de</strong> la nature <strong>de</strong> la source<br />
<strong>de</strong> données en ce qui concernait la construction du DataSet et l’instanciation <strong>de</strong> la connexion. La<br />
dépendance envers la source en elle-même et non son type est localisée au niveau <strong>de</strong>s requêtes Sql<br />
qui imposent <strong>de</strong> nommer les tables et attributs relationnels. Un changement dans la structure <strong>de</strong>s<br />
tables ou dans le type <strong>de</strong> la base <strong>de</strong> données imposent <strong>de</strong> réécrire tout le co<strong>de</strong> responsable <strong>de</strong> la<br />
connexion à la base <strong>de</strong> données et <strong>de</strong> la construction du DataSet. Selon les cas, il pouvait être<br />
nécessaire <strong>de</strong> réécrire également certaines requêtes, en particulier lorsque les dialectes Sql parlés<br />
par l’ancienne et la nouvelle source ne coïncidaient pas. Reprenons l’idée <strong>de</strong> départ mais<br />
transformons-la en une nouvelle formulation dont le sens global sera i<strong>de</strong>ntique à celui <strong>de</strong> la<br />
première : « Une application orientée objet <strong>de</strong>vrait pouvoir utiliser la logique conceptuelle <strong>de</strong>s<br />
données en faisant abstraction <strong>de</strong> son mo<strong>de</strong> <strong>de</strong> stockage ». Avec la version 2 du framework, il n’est<br />
pas possible d’utiliser la logique telle qu’elle serait définie dans un modèle conceptuel entitésrelations,<br />
nous sommes condamnés à utiliser les tables telles que définies dans la base <strong>de</strong> données.<br />
De même, il n’est pas possible <strong>de</strong> vérifier entièrement la logique dès que celle-ci interagit un temps<br />
soit peu avec du co<strong>de</strong> Sql. L’Entity Framework a été créé pour résoudre ces problèmes et se présente<br />
comme une couche ajoutée par-<strong>de</strong>ssus les mécanismes ADO.NET classiques. Manipuler <strong>de</strong>s entités<br />
conceptuelles sans directement agir sur la base <strong>de</strong> données, nous pouvons en effet comprendre que<br />
ce concept est similaire à celui <strong>de</strong> <strong>Linq</strong>. Là où <strong>Linq</strong> propose une syntaxe unifiée pour les requêtes, EF<br />
propose un accès conceptuel aux données défini indépendamment <strong>de</strong> la base <strong>de</strong> données.<br />
4.4.2 Fonctionnement<br />
Commençons par préciser quelques termes <strong>de</strong> vocabulaires et mettons-les en rapport avec la réalité<br />
concrète du moins aussi concrète que peut être la réalité <strong>de</strong>s accès aux données. Sans entrer dans<br />
toutes les spécificités techniques, nous tenterons <strong>de</strong> mettre en lumière qui sont les principaux<br />
acteurs <strong>de</strong> cet Entity Framework et quel est leur rôle.<br />
Service objet est le terme officiel utiliser par Microsoft [5][1] pour décrire l’action d’EF. Les données<br />
relationnelles n’ont pas changé, elles sont rendues disponibles en tant qu’objets. C’est ainsi que peut<br />
être perçu le concept <strong>de</strong> service objet. Il est important <strong>de</strong> comprendre qu’il s’agit bien d’un service et<br />
donc d’une couche supplémentaire posée sur la pile <strong>de</strong>s mécanismes ADO.NET.<br />
Une entité au sens d’EF est un concept décrit dans une modélisation entités-relations. Microsoft les<br />
décrit parfois sous le nom <strong>de</strong> data object, littéralement objet donnée. Il s’agit donc <strong>de</strong> classes qui<br />
vont jouer un rôle similaire à celui <strong>de</strong>s entités au sens <strong>de</strong> <strong>Linq</strong>. Les entités d’EF regroupent <strong>de</strong>s<br />
données pouvant se retrouver sur plusieurs tables relationnelles et en termes relationnels, ce qui<br />
approchent le plus <strong>de</strong> ces entités, ce sont les vues. Il est important <strong>de</strong> noter que les entités sont<br />
mappées aux données relationnelles mais cette correspondance n’associe aucune ressource<br />
directement aux entités. Cela pourrait se résumer <strong>de</strong> cette manière : les entités sont les objets<br />
métiers qui décrivent la logique <strong>de</strong> l’application et lorsqu’on en vient à parler <strong>de</strong> la persistance, le<br />
mapping permet aux entités <strong>de</strong> savoir où aller regar<strong>de</strong>r.
Une relation est un lien entre <strong>de</strong>ux entités ou plus. Il s’agit là <strong>de</strong>s mêmes concepts que ceux présents<br />
dans le fameux schéma entités-relations. Une relation peut être <strong>de</strong> diverses multiplicités : un à un, un<br />
à plusieurs ou plusieurs à plusieurs. Une relation peut elle-même possé<strong>de</strong>r <strong>de</strong>s attributs et est donc<br />
représentée par une classe.<br />
Un objet contexte, ou context object en Anglais, est le terme pour désigner la classe en charge <strong>de</strong> la<br />
gestion du modèle. La classe ou plutôt un <strong>de</strong> ses objets. Ce rôle s’apparente fort à celui du<br />
DataContext <strong>de</strong> <strong>Linq</strong> to Sql et cet objet contexte gère à la fois le mapping et les instanciations<br />
d’entités et <strong>de</strong> relations. Un seul objet contexte peut exister pour un modèle donné. Il intercepte<br />
toutes les <strong>de</strong>man<strong>de</strong>s, qu’elles soient faites aux entités, aux relations ou au modèle lui-même. L’objet<br />
contexte est le point d’entrée <strong>de</strong> la couche <strong>de</strong>s services objets (c’est ainsi qu’il est présenté dans la<br />
documentation officielle fournie par Microsoft [5]).<br />
Le modèle <strong>de</strong> données, appelé Entity Data Mo<strong>de</strong>l ou EDM par Microsoft, regroupe les entités et leurs<br />
relations. C’est en quelque sorte un schéma entités-relations mais les données peuvent y être<br />
spécifiées déjà finement, notamment au niveau <strong>de</strong>s types. Ce modèle peut être créé <strong>de</strong>puis une base<br />
<strong>de</strong> données disposant d’un schéma entités-relations. Il peut également être créé à la main ou <strong>de</strong>puis<br />
la structure <strong>de</strong> tables relationnelles d’une base <strong>de</strong> données. Dans tous les cas, <strong>de</strong>s modifications sont<br />
toujours possibles à postériori.<br />
Le fonctionnement général d’EF est le suivant : l’application utilise <strong>de</strong>s concepts dont la forme<br />
correspond à la logique <strong>de</strong> l’application. Cet assemblage d’objets est manipulé directement en<br />
mémoire et aucune interférence due à la couche relationnelle n’a lieu à ce niveau. Il n’y a que les<br />
entités et relations ayant un rôle à jouer qui sont instanciées, les autres disparaissent dès leur rôle<br />
terminé. L’objet contexte ne maintient que le minimum d’objets nécessaires. Lorsque ceux-ci<br />
nécessitent un rafraîchissement ou lorsqu’ils font l’objet d’une <strong>de</strong>man<strong>de</strong> d’écriture, l’objet contexte<br />
va utiliser les informations <strong>de</strong> mapping à sa disposition pour créer les comman<strong>de</strong>s Sql appropriées.<br />
Sql étant <strong>de</strong>venu standard (ou presque), la seule question délicate est <strong>de</strong> gérer l’accès direct aux<br />
données. Là et seulement là, le mapping contient <strong>de</strong>s informations qui seront dépendantes <strong>de</strong> la<br />
nature <strong>de</strong> la base <strong>de</strong> données. Ces informations sont utilisées pour créer <strong>de</strong>s connexions et <strong>de</strong>s<br />
comman<strong>de</strong>s avec la couche ADO.NET classique, c’est-à-dire telle qu’elle permet déjà <strong>de</strong> le faire<br />
<strong>de</strong>puis la version 2 du framework .NET. Un changement du mo<strong>de</strong> <strong>de</strong> stockage <strong>de</strong>s données impose<br />
<strong>de</strong> seulement changer quelques lignes au niveau du mapping. Les informations <strong>de</strong> structure pouvant<br />
être utilisée pour recréer <strong>de</strong>s tables relationnelles en correspondance avec le mapping actuel. Voici<br />
le principe <strong>de</strong> l’Entity Framework.<br />
4.4.3 Mapping<br />
Le mapping est réalisé <strong>de</strong> manière distribuée, dans le sens où plusieurs fichiers y participent, chacun<br />
apportant <strong>de</strong>s informations différentes. Sans entrer dans les détails <strong>de</strong> leur syntaxe, un premier<br />
fichier représente la structure <strong>de</strong>s entités et relations, un <strong>de</strong>uxième fichier représente la structure<br />
<strong>de</strong>s données au niveau <strong>de</strong> la source relationnelle et le troisième fichier fait la correspondance entre<br />
les <strong>de</strong>ux [5]. Ceci se fait (presque) indépendamment <strong>de</strong> la nature <strong>de</strong> la base <strong>de</strong> données et la<br />
structure <strong>de</strong> cette <strong>de</strong>rnière peut être récupérée ou recréée à l’envie. A l’envie signifie dans la limite<br />
où ce type <strong>de</strong> base <strong>de</strong> données est supporté par EF sans quoi ces étapes doivent être faites à la main.<br />
Ceci est d’autant plus intéressant que le développeur n’a pratiquement aucune ligne <strong>de</strong> co<strong>de</strong> à écrire<br />
pour réaliser ce fameux mapping, un éditeur <strong>de</strong> modèle EDM graphique est fourni avec EF.
4.4.4 Rôle <strong>de</strong> <strong>Linq</strong><br />
En plus <strong>de</strong> tous ces mécanismes, <strong>Linq</strong> propose une implémentation ayant pour cible l’EF, nommée<br />
<strong>Linq</strong> to Entities. L’EF disposant <strong>de</strong> son propre moteur <strong>de</strong> requêtes, <strong>Linq</strong> se présente comme une<br />
syntaxe unifiée, qui resterait valable que l’on ait recours à l’EF ou non. Tant les requêtes <strong>Linq</strong> que<br />
celles <strong>de</strong> l’EF utilisent les services objets, aucune <strong>de</strong>s <strong>de</strong>ux alternatives n’est réellement plus rapi<strong>de</strong><br />
que l’autre 12 [1][5]. Précisons toutefois que les requêtes EF sont écrites dans un langage proche du<br />
Sql nommé Entity Sql, ce qui laisse apparaître du co<strong>de</strong> non vérifiable à la compilation, ce que <strong>Linq</strong><br />
permet justement d’éviter. De plus, nous pouvons faire la même remarque que celle faite lorsque<br />
nous parlions <strong>de</strong> l’implémentation objet : <strong>Linq</strong> propose un formalisme <strong>de</strong> requêtes nettement plus<br />
lisible et plus compact.<br />
La difficulté <strong>de</strong> la mise en œuvre d’une application <strong>Linq</strong> to Entities tient davantage à la construction<br />
du modèle <strong>de</strong> données qu’à la mise en place <strong>de</strong>s requêtes. C’est pourquoi nous ne ferons pas<br />
mention d’exemple dans cette section, la syntaxe <strong>Linq</strong> restant inchangée. Signalons tout <strong>de</strong> même<br />
que certains opérateurs sont à manier avec pru<strong>de</strong>nce, en particulier les opérateurs d’agrégation<br />
personnalisés qui peuvent ne pas être supportés, en toute généralité, par le gestionnaire <strong>de</strong> bases <strong>de</strong><br />
données.<br />
4.5 Db <strong>Linq</strong><br />
Il s’agit ici d’une implémentation particulière, dans le sens où elle n’émane pas directement <strong>de</strong><br />
Microsoft mais d’une équipe mixte, regroupant <strong>de</strong>s développeurs <strong>de</strong> Microsoft et d’autres<br />
contributeurs indépendants. Son nom est d’ailleurs toujours un nom <strong>de</strong> co<strong>de</strong> et non une désignation<br />
standard « <strong>Linq</strong> to something ». Cette implémentation, résumée en une phrase, a pour but <strong>de</strong> faire<br />
comme <strong>Linq</strong> to Sql mais mieux que <strong>Linq</strong> to Sql. La plupart <strong>de</strong>s informations la concernant sont<br />
disponibles sur [4] mais tout est rassemblé sur le site du projet en lui-même [14]. En effet, cette<br />
implémentation se base sur l’API <strong>de</strong> <strong>Linq</strong> to Sql mais intègre d’autres types <strong>de</strong> bases <strong>de</strong> données que<br />
Sql Server. Citons parmi eux MySql, Oracle et PostGreSql qui sont probablement les plus célèbres.<br />
Pas grand-chose à ajouter sur cette implémentation, si ce n’est qu’elle est toujours en<br />
développement. La <strong>de</strong>rnière release est la version 0.20 datée du 16 avril 2010, ce qui en fait la plus<br />
récente à l’heure d’écrire ce paragraphe.<br />
Microsoft ne semble pas vouloir intégrer cette implémentation dans les librairies standards, pour une<br />
raison indéterminée. Même si tous les outils <strong>de</strong> génération <strong>de</strong> co<strong>de</strong> disponibles pour <strong>Linq</strong> to Sql n’ont<br />
pas d’équivalents pour Db <strong>Linq</strong>, se contraindre à Sql Server semble peu judicieux. Nous reparlerons<br />
plus loin du projet Mono qui a choisi quant à lui la solution Db <strong>Linq</strong> [14][15].<br />
12 Il est d’ailleurs possible d’écrire <strong>de</strong>s requêtes Entity Sql avec <strong>Linq</strong> en utilisant les objets ObjectQuery.
Chapitre 5 : Implémentation Xml<br />
Le recours à <strong>de</strong>s bases <strong>de</strong> données Xml s’est fortement répandu ces <strong>de</strong>rnières années et tout<br />
naturellement <strong>Linq</strong> propose une implémentation pour interagir avec ces structures. Xml est<br />
cependant une technologie difficile à interfacer avec les langages orientés objet, en particulier<br />
lorsque ceux-ci désirent créer du contenu Xml. L’approche classique utilise DOM 13 qui oblige à créer<br />
l’ensemble <strong>de</strong>s éléments avant <strong>de</strong> les rassembler. <strong>Linq</strong> to Xml propose une nouvelle syntaxe pour<br />
créer du contenu Xml, la construction fonctionnelle. Elle permet <strong>de</strong> créer les éléments selon une<br />
structure hiérarchique beaucoup plus compacte. Cette syntaxe est encore plus évi<strong>de</strong>nte dès lors que<br />
nous utilisons Visual Basic 2008, car ce <strong>de</strong>rnier a introduit dans sa <strong>de</strong>rnière version les littéraux Xml.<br />
Avec cela nous pouvons écrire directement le co<strong>de</strong> en Xml, comme dans l’exemple ci<strong>de</strong>ssous<br />
(inspiré <strong>de</strong> [1]) où nous créons un étudiant :<br />
Dim exempleXml As XDocument = _<br />
<br />
<br />
Schoonenbergh<br />
Ecole polytechnique<br />
MA2<br />
<br />
Kleine Daalstraat<br />
79<br />
1930<br />
Zaventem<br />
<br />
<br />
<strong>Linq</strong> to Xml a été pensé pour manipuler du Xml tel que défini par le W3C 14 , c'est-à-dire pour<br />
manipuler directement du contenu Xml sans passer par un intermédiaire. Cela permet d’interagir<br />
avec <strong>de</strong> telles structures tout en restant dans un paradigme objet. La construction fonctionnelle est<br />
supportée par l’assemblage System.Xml.<strong>Linq</strong> et peut être utilisée même sans recours direct à <strong>Linq</strong>.<br />
Plusieurs classes sont définies pour représenter les éléments structurels du langage Xml, citons parmi<br />
elles quelques exemples intéressants. La classe XObject tout d’abord qui est la classe <strong>de</strong> base pour<br />
représenter n’importe quel objet Xml. Cette classe propose <strong>de</strong>s métho<strong>de</strong>s qui représentent toute la<br />
gestion structurelle <strong>de</strong> l’arbre Xml qui sera créé en mémoire. Des métho<strong>de</strong>s telles que AddBeforeSelf<br />
ou AddAfterSelf permettent ainsi d’ajouter un élément à un endroit précis <strong>de</strong> l’arbre. Cette classe<br />
supporte également l’annotation Xml. La classe XStreamingElement contient un arbre d’objets<br />
IEnumerable et peuvent donc faire l’objet <strong>de</strong> requête. Les objets <strong>de</strong> cette classe réalisent<br />
l’exécution différée <strong>de</strong>s requêtes chargeant partiellement le résultat, ce qui permet <strong>de</strong> mieux doser<br />
la quantité d’objets chargés en mémoire, bien utile dans une optique <strong>de</strong> performance. Parlons enfin<br />
<strong>de</strong> la classe XDocument représentant un document Xml. Les objets <strong>de</strong> cette classe n’ont pas besoin<br />
<strong>de</strong> références à un fichier pour être instanciés. Ils sont créés selon le pattern Factory et peuvent être<br />
associés dans le futur à une référence (ou une URI) qui les reliera directement à du contenu existant,<br />
le cas échéant.<br />
13<br />
DOM signifie Document Object Mo<strong>de</strong>l. Il s’agit d’un ensemble <strong>de</strong> règles définissant les premières structures<br />
Xml.<br />
14<br />
World Wi<strong>de</strong> Web Consortium ; organisme <strong>de</strong> standardisation <strong>de</strong>s technologies web.
Une particularité <strong>de</strong> l’implémentation Xml est que les résultats restent valables même si la source <strong>de</strong><br />
données est déconnectée. Ce qui veut dire que les entités présentes dans un résultat ne seront<br />
resynchronisées que si la <strong>de</strong>man<strong>de</strong> explicite leur parvient. Il est également possible d’imaginer<br />
l’exécution d’une requête sur un document Xml particulier, récupérer ensuite le résultat et ré<br />
exécuter la même requête sur un fichier différent (celui-ci <strong>de</strong>vra vraisemblablement avoir une<br />
structure très similaire au premier). Une autre possibilité d’utilisation <strong>de</strong> cette propriété est qu’il est<br />
très facile d’utiliser un résultat issu d’une source <strong>de</strong> données relationnelle pour effectuer une<br />
requête sur une structure Xml. Les entités <strong>Linq</strong> to Sql par exemple se resynchronisent<br />
périodiquement mais ce n’est pas le cas du Xml. Les <strong>de</strong>ux implémentations étant indépendantes, il<br />
ne pose aucun problème d’utiliser l’une <strong>de</strong> ces sources <strong>de</strong> données pour supporter une requête sur<br />
l’autre. Par exemple, nous pourrions extraire d’un fichier Xml tous les étudiants inscrits dans chaque<br />
faculté et ensuite pour chacun <strong>de</strong> ces étudiants, faire une requête en base <strong>de</strong> données relationnelle<br />
pour obtenir ses informations personnelles.
Chapitre 6 : En pratique<br />
6.1 Ressenti général<br />
Après ce vaste tour d’horizon <strong>de</strong> <strong>Linq</strong>, il est temps <strong>de</strong> prendre un peu <strong>de</strong> recul et <strong>de</strong> faire le point sur<br />
ce que nous pouvons en retirer. Qu’a apporté cette étu<strong>de</strong> ? Voila une question qu’il est bon <strong>de</strong> se<br />
poser. La découverte <strong>de</strong> <strong>Linq</strong> s’est faite <strong>de</strong>puis les bases, tout comme ce fut relaté dans ce rapport.<br />
Nous avons introduit un ensemble <strong>de</strong> mécanismes fondateurs avant <strong>de</strong> décortiquer les différentes<br />
implémentations proposées par <strong>Linq</strong>. L’exploration <strong>de</strong> ces diverses implémentations et la mise en<br />
œuvre <strong>de</strong> cas concrets se sont avérés parfois surprenants, révélant <strong>de</strong>s comportements inattendus<br />
(cf tests <strong>de</strong> performance pour <strong>Linq</strong> to Sql) ou au contraire <strong>de</strong>s avantages conséquents (comme pour<br />
l’exemple <strong>de</strong> l’implémentation objet). La métho<strong>de</strong> appliquée tout au long <strong>de</strong> cette étu<strong>de</strong> a été <strong>de</strong><br />
partir <strong>de</strong>s bases théoriques en allant progressivement vers <strong>de</strong>s considérations <strong>de</strong> plus en plus en<br />
phase avec la réalité concrète. Bien qu’il soit tentant d’explorer encore et encore certaines options,<br />
nous nous sommes efforcés <strong>de</strong> nous en tenir au sujet initial qui ne concerne que <strong>Linq</strong>. Des ouvertures<br />
vers <strong>de</strong>s technologies passionnantes comme l’Entity Framework ont été tentées mais les essais et<br />
explications sont restés centrés sur <strong>Linq</strong>.<br />
La plus gran<strong>de</strong> difficulté rencontrée durant cette étu<strong>de</strong> concerne la disponibilité <strong>de</strong> la<br />
documentation. En effet, <strong>Linq</strong> était partie intégrante d’une solution propriétaire, une très large<br />
majorité <strong>de</strong>s informations techniques est concentrée sur le site officiel <strong>de</strong> Microsoft ou dans <strong>de</strong>s<br />
livres édités par Microsoft Press. Certains sujets sont encore mal documentés du fait <strong>de</strong> leur jeunesse<br />
et <strong>de</strong> leurs rapi<strong>de</strong>s mutations. Un <strong>de</strong>s problèmes rencontrés à ce sujet fut d’avoir accès à <strong>de</strong>s<br />
explications tronquées, certaines parties étant apparemment considérées comme triviales. Lorsque<br />
ce n’est pas l’avis du lecteur, c’est rapi<strong>de</strong>ment problématique pour ce <strong>de</strong>rnier. De même, certains<br />
sujets très récents comme l’Entity Framework ne proposent que <strong>de</strong>s informations fragmentaires et<br />
parfois contradictoires, le plus souvent à cause <strong>de</strong> changements survenus durant la version beta non<br />
répercutés dans la documentation. Heureusement la communauté qui soutient le développement<br />
.NET est nombreuse et les accès en avant-première à certaines technologies encore en<br />
développement permettent à certains spécialistes <strong>de</strong> rédiger <strong>de</strong>s explications complémentaires<br />
souvent utiles (il s’agit <strong>de</strong>s sources *13+ et *6+ principalement). Ce passage est également l’occasion<br />
<strong>de</strong> remercier tous ces contributeurs pour leur travail et leur dévouement. Proposant généralement<br />
<strong>de</strong>s informations dispersées sur <strong>de</strong>s blogs ou <strong>de</strong>s forums communautaires, ils sont parfois auteurs<br />
d’une simple phrase permettant le déclic. Leurs interventions sont aussi l’occasion d’offrir un point<br />
<strong>de</strong> vue différent <strong>de</strong> l’omniprésent Microsoft. Dans la documentation officielle, tout semble toujours<br />
simple et harmonieux. Plusieurs retours d’expérience ont également permis d’adopter une vision<br />
plus objective sur l’ensemble <strong>de</strong>s sujets abordés.<br />
6.2 Points forts, points faibles<br />
La première remarque qui s’impose après ce que nous avons vu, c’est <strong>de</strong> se dire que <strong>Linq</strong> permet<br />
d’écrire du co<strong>de</strong> beaucoup plus simplement. Les requêtes y sont écrites dans un langage plus proche<br />
du langage naturel et les erreurs sont détectées à la compilation. Durant les différents tests réalisés<br />
préalablement à l’écriture <strong>de</strong> ce rapport, le temps nécessaire à l’écriture <strong>de</strong> requêtes Sql a été<br />
constaté comme étant entre <strong>de</strong>ux et trois fois supérieur au temps requis pour produire <strong>de</strong>s requêtes<br />
<strong>Linq</strong> équivalentes (est compris dans ces mesures le temps consacré à l’écriture <strong>de</strong>s requêtes et à leur<br />
débogage). En termes <strong>de</strong> productivité, <strong>Linq</strong> constitue déjà une avancée majeure. Au-<strong>de</strong>là <strong>de</strong> ce gain<br />
<strong>de</strong> performances humaines, <strong>Linq</strong> améliore la maintenabilité d’un co<strong>de</strong> car il est plus lisible d’une part
et plus abstrait vis-à-vis <strong>de</strong>s données d’autre part. En termes <strong>de</strong> performances machines, seules<br />
certaines implémentations en pâtissent, en particulier <strong>Linq</strong> to Sql. Les autres implémentations ne<br />
rajoutent que peu <strong>de</strong> choses entre le co<strong>de</strong> et la gestion du stockage <strong>de</strong>s données, ceci n’influence<br />
donc que légèrement sur les performances. Les possibilités d’optimisation sont également plus<br />
visibles pour <strong>Linq</strong> qu’en ce qui concerne la couche ADO.NET où une connaissance <strong>de</strong>s subtilités est<br />
souvent nécessaire. Ceci n’est nullement le fruit d’une mesure cependant, simplement une<br />
impression personnelle partagée par divers interlocuteurs rencontrés sur <strong>de</strong>s sites internet<br />
spécialisés. Bien qu’il ne s’agisse pas d’une réelle information, cette tendance semble montrer que<br />
l’utilisation <strong>de</strong> <strong>Linq</strong> continuera <strong>de</strong> se répandre tant qu’il sera question <strong>de</strong> requêtes sur <strong>de</strong>s données<br />
relationnelles.<br />
Les techniques d’accès aux données relationnelles datant <strong>de</strong> la <strong>de</strong>uxième version du framework .NET<br />
sont sans comparaison avec le confort offert par <strong>Linq</strong>. Mais si nous nous plaçons dans une optique <strong>de</strong><br />
continuité, l’arrivée <strong>de</strong> <strong>Linq</strong> ne signifie nullement l’abandon <strong>de</strong> la couche ADO.NET. Certes manipuler<br />
la couche d’accès aux données signifie utiliser un autre langage avec tous les désagréments que cela<br />
comporte, mais <strong>Linq</strong> est une alternative nouvelle. La plupart <strong>de</strong>s développeurs chargés d’écrire <strong>de</strong>s<br />
requêtes ont probablement toujours une bonne connaissance du Sql et <strong>de</strong>s bases <strong>de</strong> données en<br />
général. <strong>Linq</strong> est très plaisant du point <strong>de</strong> vue du débutant mais certaines personnes décrient son<br />
utilisation abusive, arguant du fait que son utilisation à outrance finira par provoquer l’oubli <strong>de</strong>s<br />
mécanismes subtils pour accé<strong>de</strong>r aux données [13]. Ne plus être conscient <strong>de</strong> la cuisine interne<br />
réalisée par les outils <strong>de</strong> génération <strong>de</strong> co<strong>de</strong> peut certes s’avérer dangereux, mais est-ce vraiment ce<br />
à quoi nous risquons d’arriver ? Probablement pas, car les bases <strong>de</strong> données en elles-mêmes sont<br />
toujours la solution numéro un dans le mon<strong>de</strong> <strong>de</strong> la persistance et ces bases <strong>de</strong> données se doivent<br />
d’être gérées et maintenues par <strong>de</strong>s experts. Des spécialistes du relationnels pour qui les requêtes ne<br />
sont qu’une toute petite partie <strong>de</strong> ce que peut réaliser une base <strong>de</strong> données. Créer <strong>de</strong>s procédures<br />
stockées, <strong>de</strong>s déclencheurs (triggers) et ce genre <strong>de</strong> choses, cela aura toujours son lot d’artisans<br />
œuvrant avec <strong>de</strong>s lignes <strong>de</strong> comman<strong>de</strong>s bien loin <strong>de</strong> <strong>Linq</strong> et <strong>de</strong> ses interfaces graphiques.<br />
<strong>Linq</strong> se présente comme le compagnon idéal du développeur d’application qui souhaite ne pas être<br />
entravé par la présence <strong>de</strong>s mécanismes <strong>de</strong> persistance. La couche ADO.NET restera <strong>de</strong> toute façon<br />
utilisée en arrière-plan pour réaliser les dialogues intimes avec les bases <strong>de</strong> données. <strong>Linq</strong> est donc<br />
probablement plus un complément qu’un concurrent pour ADO.NET. Il est néanmoins dommage que<br />
<strong>de</strong>s implémentations comme Db <strong>Linq</strong> ne soient pas plus soutenues par Microsoft, mais cela sera<br />
peut-être amené à changer.<br />
Si nous sortons du seul point <strong>de</strong> vue relationnel, <strong>Linq</strong> permet avant tout <strong>de</strong> manipuler <strong>de</strong>s données<br />
sans se préoccuper <strong>de</strong> savoir d’où elles viennent. C’est à la fois une force et une faiblesse. La force<br />
est d’augmenter l’abstraction du co<strong>de</strong> vis-à-vis du stockage, ce qui est vital nous ne le répèterons<br />
jamais assez. La faiblesse est qu’il <strong>de</strong>vient beaucoup plus facile <strong>de</strong> commettre <strong>de</strong>s erreurs par<br />
ignorance. Ainsi une persistance assurée par plusieurs bases <strong>de</strong> données redondantes permettra <strong>de</strong>s<br />
réponses rapi<strong>de</strong>s, mais un même co<strong>de</strong> pourra s’avérer problématique s’il prend pour cible un unique<br />
fichier Xml <strong>de</strong> très gran<strong>de</strong> taille. Il faut rester conscient qu’utiliser <strong>Linq</strong> ne sera pas systématiquement<br />
une bonne chose et <strong>de</strong>vra toujours être attentivement examiné. <strong>Linq</strong> dans son ensemble commence<br />
à atteindre la maturité nécessaire pour s’attirer la confiance <strong>de</strong>s développeurs, son utilisation va sans<br />
doute <strong>de</strong>venir généralisée dans le mon<strong>de</strong> .NET.
6.3 Quelles alternatives<br />
Comme nous l’avions précé<strong>de</strong>mment fait remarquer, le problème <strong>de</strong> la dépendance par rapport au<br />
stockage <strong>de</strong>s données n’est pas nouveau. <strong>Linq</strong> y apporte sa contribution et nous avons résumé les<br />
principales techniques qui étaient utilisées à l’époque <strong>de</strong> la <strong>de</strong>uxième version <strong>de</strong> .NET. Mais qu’en<br />
est-il <strong>de</strong>s autres langages ? .NET n’a pas le monopole <strong>de</strong> l’orienté objet et <strong>Linq</strong> est résolument lié au<br />
framework. Il est impensable qu’un langage orienté objet tel que Java n’ait pas développé <strong>de</strong><br />
solutions <strong>de</strong> son crû pour tenter <strong>de</strong> créer davantage d’abstraction vis-à-vis <strong>de</strong> la couche <strong>de</strong><br />
persistance <strong>de</strong>s données. Nous allons maintenant nous pencher sur différentes technologies et leurs<br />
points communs avec <strong>Linq</strong>.<br />
6.3.1 Java<br />
Java reste une pointure dans le domaine <strong>de</strong>s langages <strong>de</strong> programmation orientés objet. En<br />
perpétuelle concurrence avec son grand rival .NET, leurs évolutions respectives se sont toujours plus<br />
ou moins suivies <strong>de</strong> près. Ainsi, les accès aux données relationnelles se faisaient via un pilote Odbc 15<br />
en C# 2.0 et Java 5 faisait <strong>de</strong> même avec Jdbc, pour sa part. Voyons maintenant quelles sont les<br />
solutions actuellement proposées pour réaliser une persistance abstraite en Java.<br />
6.3.1.1 JDO<br />
Les paragraphes consacrés à l’étu<strong>de</strong> <strong>de</strong> JDO sont presqu’entièrement inspirés <strong>de</strong> *8+ et <strong>de</strong> la<br />
documentation qui s’y trouve. JDO est l’acronyme désignant Java Data Object. Il s’agit d’une librairie,<br />
plus exactement d’une API, standard pour réaliser la persistance <strong>de</strong>s objets. La première version <strong>de</strong><br />
JDO est apparue en 2002 et la secon<strong>de</strong> version est disponible <strong>de</strong>puis 2006. Comme presque tout ce<br />
qui concerne Java, cette librairie est open source. La version 2.1 appelée aussi Apache JDO est<br />
toujours en cours <strong>de</strong> développement à l’heure où ces lignes sont écrites. Cette librairie est décrite<br />
comme supportant tout type <strong>de</strong> stockage et représentant les données stockées à l’ai<strong>de</strong> <strong>de</strong> POJO<br />
(Plain Old Java Object), ce qui pourrait se traduire par <strong>de</strong> bons vieux objets Java. JDO se décline en<br />
plusieurs interfaces, comme le fait <strong>Linq</strong>, et ces implémentations permettent d’utiliser différents<br />
stockages <strong>de</strong> données parmi lesquels <strong>de</strong>s bases <strong>de</strong> données relationnelles, objet ou Xml, un système<br />
<strong>de</strong> fichiers ou même certains stockages plus exotiques comme les bases <strong>de</strong> données Big Table <strong>de</strong><br />
Google 16 .<br />
JDO présente la persistance du point <strong>de</strong> vue du développeur objet, <strong>de</strong> manière transparente. Pour<br />
décrire brièvement les classes qui entrent en jeu, nous avons un gestionnaire <strong>de</strong> persistance, <strong>de</strong>s<br />
requêtes et <strong>de</strong>s transactions. Le gestionnaire est responsable <strong>de</strong> la maintenance <strong>de</strong>s instances <strong>de</strong><br />
persistance qui ne sont en fait que l’équivalent <strong>de</strong>s entités en mémoire au sens <strong>de</strong> <strong>Linq</strong>. Le<br />
gestionnaire est également chargé d’exécuter les requêtes et transactions qui lui sont passées. Le<br />
principe est, sur le fond, très similaire à <strong>Linq</strong> to Sql : nous avons un gestionnaire <strong>de</strong> contexte qui gère<br />
<strong>de</strong>s entités persistantes. Pour interroger les sources <strong>de</strong> données, JDO a recours à un langage qui lui<br />
est propre, le langage JDOQL. Cet acronyme est l’abréviation <strong>de</strong> Java Data Object (JDO) Query<br />
Language et n’est pas directement lié à OQL comme nous pourrions l’imaginer. Il est intéressant <strong>de</strong><br />
noter que ce langage propose <strong>de</strong>ux syntaxes alternatives, la syntaxe dite déclarative et celle dite du<br />
Single String. Pour ne pas trop nous perdre dans l’analyse <strong>de</strong> toutes les possibilités syntaxiques, nous<br />
allons simplement examiner cela avec un exemple tiré du site officiel du projet Apache JDO [8], situé<br />
page suivante.<br />
15 Il existe d’autres pilotes plus anciens, odbc était le <strong>de</strong>rnier en date lors <strong>de</strong> la release <strong>de</strong> C# 2.<br />
16 Ceci est mentionné à l’adresse suivante : http://db.apache.org/jdo/impls.html
Declarative JDOQL :<br />
Query query = pm.newQuery(mydomain.Product.class,"price < limit");<br />
query.<strong>de</strong>clareParameters("double limit");<br />
query.setOr<strong>de</strong>ring("price ascending");<br />
List results = (List)query.execute(150.00);<br />
Single-String JDOQL :<br />
Query query = pm.newQuery("SELECT FROM mydomain.Product WHERE " +<br />
"price < limit PARAMETERS double limit ORDER BY price<br />
ASCENDING");<br />
List results = (List)query.execute(150.00);<br />
Pour bien comprendre l’exemple ci-<strong>de</strong>ssus, précisons que l’objet nommé pm désigne le gestionnaire<br />
<strong>de</strong> persistance qui est le point <strong>de</strong> passage obligé pour toute requête ou transaction effectuée sur ses<br />
données. Cette manière <strong>de</strong> procé<strong>de</strong>r est différente <strong>de</strong> celle <strong>de</strong> <strong>Linq</strong>. JDO considère une requête<br />
comme un objet qui sera traité <strong>de</strong> manière centralisé alors que nous avons vu que <strong>Linq</strong> adresse ses<br />
requêtes directement sur les collections <strong>de</strong> données. Le recours au gestionnaire n’avait lieu que<br />
lorsqu’une opération générale était nécessaire, comme dans le cas du SubmitChanges que nous<br />
avions abordé dans la partie sur les implémentations relationnelles.<br />
En ce qui concerne le mapping, il est réalisé à la fois par <strong>de</strong>s dénominations explicites dans le co<strong>de</strong> et<br />
par un fichier Xml. Au niveau du co<strong>de</strong>, les informations ajoutées servent à avertir les objets s’ils<br />
doivent ou non s’occuper <strong>de</strong> leur persistance ou <strong>de</strong> celle <strong>de</strong> leurs attributs. En ce qui concerne le<br />
fichier Xml, les informations spécifient quelle classe est la représentation <strong>de</strong> quelles données et où<br />
trouver ces <strong>de</strong>rnières. Diverses autres informations participent au mapping mais nous ne les<br />
détaillerons pas ici. L’important est <strong>de</strong> constater que le mapping se fait au niveau du co<strong>de</strong> avec <strong>de</strong>s<br />
indications qui permettent <strong>de</strong> savoir comment gérer les objets persistants et aussi avec un fichier <strong>de</strong><br />
correspondance qui indique quel attribut correspond à quelle donnée enregistrée. La seule<br />
différence notable entre <strong>Linq</strong> et JDO en ce qui concerne le mapping, c’est que <strong>Linq</strong> tente d’encore<br />
faciliter la tâche du développeur avec l’intégration d’éditeurs graphiques.<br />
6.3.1.2 Hibernate<br />
Une gran<strong>de</strong> partie <strong>de</strong>s informations relatives à Hibernate provient <strong>de</strong> [16]. Hibernate a été conçu à<br />
l’origine pour simplifier la persistance d’objets dans <strong>de</strong>s bases <strong>de</strong> données relationnelles. Ses<br />
fonctionnalités se sont quelque peu élargies mais Hibernate reste un outil cantonné au domaine du<br />
relationnel. Son rôle est d’offrir un mapping objet relationnel plus aisé que lorsqu’il est réalisé à la<br />
main avec la technologie Jdbc. Hibernate gère les mécanismes <strong>de</strong> persistance, c’est-à-dire les<br />
enregistrements et chargements <strong>de</strong>puis la base <strong>de</strong> données, mais il laisse le développeur écrire <strong>de</strong>s<br />
requêtes dans le langage <strong>de</strong> son choix parmi lesquels le langage propre à Hibernate (HQL), le langage<br />
générique <strong>de</strong> l’interface <strong>de</strong> persistance implémentée par Hibernate 17 (JPQL) ou directement en Sql.<br />
Là où <strong>Linq</strong> fait figure <strong>de</strong> canif suisse intégrant <strong>de</strong> multiples possibilités via un outil passe-partout,<br />
Hibernate est la boîte à outils du développeur désireux d’avoir le choix <strong>de</strong>s armes.<br />
A noter que Hibernate est souvent considéré comme une solution à n’utiliser que lorsque la situation<br />
est adaptée [13] 18 . Alors que <strong>Linq</strong> se veut être une solution flexible, avec abstraction du stockage,<br />
17 Il s’agit en réalité d’une interface définie dans la librairie JPA, mais par abus <strong>de</strong> langage nous dirons que<br />
l’interface implémentée est JPA. JPA signifie Java Persistence API.<br />
18 Il s’agit là d’interventions non officielles mais néanmoins non(ou très peu) démenties par la communauté*13+
Hibernate est un choix lié au mon<strong>de</strong> relationnel offrant un ensemble <strong>de</strong> fonctionnalités pas toujours<br />
aisées à maîtriser. Précisons tout <strong>de</strong> même qu’Hibernate est bien plus ancien que <strong>Linq</strong>, puisque sa<br />
création remonte à 2001. Bien que les interfaces JPA aient été créées pour englober plusieurs<br />
utilitaires <strong>de</strong> persistance dont Hibernate, ce <strong>de</strong>rnier reste davantage un ORM qu’une solution<br />
intégrée au langage. De manière générale, le rôle <strong>de</strong>s implémentations <strong>de</strong> JPA reste borné à<br />
effectuer une persistance plus simple lorsque les données sont stockées sous forme relationnelle.<br />
6.3.2 .NET<br />
Bien que <strong>Linq</strong> y soit récent et que, au contraire <strong>de</strong> Java, ce langage ne s’enrichisse pas directement<br />
sur la base d’une communauté <strong>de</strong> contributeurs, .NET et C# en particulier proposent d’autres<br />
alternatives pour réaliser la persistance <strong>de</strong>s données sans passer par toute la panoplie <strong>de</strong>s<br />
comman<strong>de</strong>s ADO.NET. Les autres métho<strong>de</strong>s que nous allons abor<strong>de</strong>r sont, <strong>de</strong> manière générale,<br />
<strong>de</strong>stinées à un autre public que celui prévu pour <strong>Linq</strong>.<br />
6.3.2.1 NHibernate<br />
Comme son nom l’indique, il s’agit là d’une version d’Hibernate adaptée au langage C# (le N est une<br />
abréviation courante en préfixe d’une technologie pour signifier qu’elle s’applique à .NET). Les<br />
informations relatives à NHibernate émanent <strong>de</strong> [17]. Le cœur d’Hibernate a été porté vers .NET et<br />
son utilisation a été quelque peu adaptée pour mieux répondre aux attentes <strong>de</strong>s développeurs .NET.<br />
Cet outil a été développé par la même équipe que celle en charge <strong>de</strong> la version Java. De par sa<br />
structure et son mo<strong>de</strong> <strong>de</strong> fonctionnement, il s’agit encore une fois d’un ORM qui vient se greffer sur<br />
une architecture déjà existante, celle <strong>de</strong> l’incontournable couche ADO.NET. NHibernate est présenté<br />
comme une solution mature, capable <strong>de</strong> réaliser la correspondance objet relationnel <strong>de</strong> manière<br />
transparente pour le développeur. NHibernate répond à une <strong>de</strong>man<strong>de</strong> particulière : ne pas être<br />
limité par l’ORM en étant sûr que, <strong>de</strong> toute façon, les données seront sauvées dans une base <strong>de</strong><br />
données relationnelle. Il s’agit là d’une optique quelque peu hors <strong>de</strong>s objectifs <strong>de</strong> <strong>Linq</strong> qui souhaite<br />
créer une abstraction avec le stockage tout en mettant davantage en avant la facilité <strong>de</strong> mise en<br />
œuvre. A noter que <strong>de</strong>puis peu, NHibernate propose un module complémentaire <strong>Linq</strong> to NHibernate<br />
permettant d’utiliser la syntaxe <strong>Linq</strong> pour réaliser <strong>de</strong>s requêtes avec NHibernate [18].<br />
6.3.2.2 Entity Framework<br />
Voici une figure connue, les informations à son sujet sont toujours majoritairement issues <strong>de</strong> [5].<br />
L’Entity Framework, que nous avions brièvement introduit dans la section <strong>Linq</strong> to Entities, est lui<br />
aussi en soi une alternative à <strong>Linq</strong>. Il serait mieux <strong>de</strong> parler <strong>de</strong> complémentarité entre ces <strong>de</strong>ux outils<br />
que <strong>de</strong> concurrence, néanmoins il reste possible d’utiliser l’un sans l’autre. L’Entity Framework<br />
permet essentiellement <strong>de</strong>ux choses : choisir la forme <strong>de</strong>s entités à manipuler et automatiser toute<br />
la cuisine interne nécessaire pour pouvoir fournir <strong>de</strong>s entités personnalisées <strong>de</strong> manière efficace.<br />
Comme nous l’avions vu, cela ajoute également un niveau d’abstraction vis-à-vis <strong>de</strong> la base <strong>de</strong><br />
données, car le co<strong>de</strong> manipulant les entités est bien séparé <strong>de</strong>s manipulations relationnelles. <strong>Linq</strong><br />
apporte quant à lui la puissance et la simplicité d’utilisation <strong>de</strong> son langage <strong>de</strong> requêtes. L’Entity<br />
Framework a donc pour vocation d’ai<strong>de</strong>r à définir la « business logic » <strong>de</strong> l’application sans entrave<br />
<strong>de</strong> la part du stockage <strong>de</strong>s données, alors que <strong>Linq</strong> permet <strong>de</strong> récupérer <strong>de</strong>s éléments précis plus<br />
aisément. Il est tout à fait envisageable <strong>de</strong> vouloir gar<strong>de</strong>r la main sur le langage <strong>de</strong> requêtes car, en<br />
effet, celles-ci seront traduites tôt ou tard en Sql. Dans cette optique-là, l’utilisation <strong>de</strong> l’Entity<br />
Framework se fera sans l’ai<strong>de</strong> <strong>de</strong> <strong>Linq</strong>. En résumé, si la difficulté se situe au niveau <strong>de</strong> l’architecture<br />
du co<strong>de</strong> ou <strong>de</strong> la jonction entre la logique métier et la couche <strong>de</strong> persistance <strong>de</strong>s données, Entity
Framework est une solution envisageable. Le champ d’application <strong>de</strong> <strong>Linq</strong> sera plutôt atteint si la<br />
difficulté rési<strong>de</strong> dans la capacité à produire ou à vérifier <strong>de</strong>s requêtes vers <strong>de</strong>s données persistantes.<br />
Ces outils répon<strong>de</strong>nt à <strong>de</strong>s besoins séparés et n’entrent pas en concurrence directe.<br />
6.3.3 Autres langages<br />
Nous avons vu un peu ce qui se faisait en Java comme en .NET pour mettre en place une ai<strong>de</strong> à la<br />
persistance. Ces <strong>de</strong>ux langages sont probablement les porte-drapeaux du paradigme orienté objet et<br />
pourtant ils n’en sont pas non plus les seuls représentants. Le PHP est très fort présent dans le<br />
domaine du développement web et ce domaine fait généralement un usage extensif <strong>de</strong>s moyens <strong>de</strong><br />
persistance. Nous allons maintenant parcourir <strong>de</strong>s approches plus globales, permettant <strong>de</strong> nous faire<br />
une meilleure idée <strong>de</strong>s types <strong>de</strong> persistance recherchées et pourquoi. Les paragraphes suivants sont<br />
issus <strong>de</strong> diverses sources, principalement [19],[20] et [21].<br />
6.3.3.1 Persistance personnalisée<br />
De nombreux ORM existent sur le marché et ce <strong>de</strong>puis maintenant quelques années. Les plus connus<br />
restent ceux liés à Java et .NET. Pourquoi ? La première chose qui <strong>de</strong>vrait nous mettre la puce à<br />
l’oreille est que ces langages sont fortement typés, à l’inverse du PHP ou <strong>de</strong> Python. Il est impératif<br />
<strong>de</strong> connaître le type d’une variable avant <strong>de</strong> pouvoir l’utiliser. Or les types sont quelque peu<br />
différents lorsque nous passons du mon<strong>de</strong> relationnel à celui <strong>de</strong>s objets, ou vice versa. De même, les<br />
gran<strong>de</strong>s applications orientées objet s’appuient sur <strong>de</strong>s architectures parfois titanesques et cela<br />
entraîne invariablement une complexification structurelle <strong>de</strong>s données à stocker. Les langages<br />
comme PHP et Python ont quant à eux une réputation <strong>de</strong> légèreté et <strong>de</strong> facilité <strong>de</strong> mise en œuvre.<br />
Vu <strong>de</strong> cet angle là, la multiplication <strong>de</strong>s utilitaires <strong>de</strong> persistance pour Java et .NET s’explique mieux.<br />
Leur supériorité numérique également. Nous avons vu le cas <strong>de</strong> Hibernate et <strong>de</strong> NHibernate dans les<br />
paragraphes précé<strong>de</strong>nts, précisons tout <strong>de</strong> même qu’il en existe <strong>de</strong>s dizaines d’autres. Il existe<br />
malgré tout <strong>de</strong>s ORM pour PHP (Doctrine en est le plus connu) et Python (avec l’ORM SQLAlchemy).<br />
Ceux-ci sont souvent <strong>de</strong>s versions d’ORM utilisés en Java portées vers le langage cible. Des langages<br />
comme PHP et .NET sont difficilement comparables lorsque le domaine d’application est défini, il<br />
n’est donc pas réellement question <strong>de</strong> concurrence entre <strong>Linq</strong> et ces différents outils.<br />
Une autre situation qui se généralise <strong>de</strong> plus en plus est d’embarquer sa propre couche <strong>de</strong><br />
persistance, offrant l’abstraction désirée. C’est particulièrement vrai pour <strong>de</strong>s outils <strong>de</strong> conception<br />
« tout en un », tels <strong>de</strong>s fournisseurs d’ERP (TinyErp s’appuie sur du co<strong>de</strong> Python [24]) ou <strong>de</strong><br />
gestionnaire <strong>de</strong> contenu CMS lorsqu’ils sont développés en PHP ou Python. Les gestionnaires <strong>de</strong><br />
contenu intègrent souvent la possibilité d’insérer du co<strong>de</strong> à plusieurs niveaux, que ce soit lors <strong>de</strong> la<br />
création d’une page ou l’écriture d’un script dont l’appel est géré par le CMS. Bien qu’il s’agisse<br />
essentiellement d’outils spécialisés dans le développement web, notons que ces solutions intègrent<br />
chaque jour plus <strong>de</strong> fonctionnalités ce qui les approchent très fortement du statut d’environnement<br />
<strong>de</strong> développement. Parmi ceux-ci, nous pouvons citer <strong>de</strong>s exemples comme eZ Publish [22] ou<br />
Drupal [23]. Ces outils <strong>de</strong> gestion <strong>de</strong> contenu permettent d’insérer du co<strong>de</strong> pratiquement n’importe<br />
où et constituent en eux-mêmes <strong>de</strong>s outils <strong>de</strong> développements (tous <strong>de</strong>ux s’appuient sur du PHP). Ils<br />
ont chacun à leur manière établis une couche <strong>de</strong> persistance tentant <strong>de</strong> faire abstraction <strong>de</strong> la nature<br />
<strong>de</strong> la source <strong>de</strong> données. Un ensemble <strong>de</strong> classes prennent en charge les différents types <strong>de</strong> bases <strong>de</strong><br />
données et les interactions avec le stockage sont créées à la volée, selon le type <strong>de</strong> stockage détecté.<br />
L’abstraction vis-à-vis du support <strong>de</strong> persistance est une idée similaire à celle <strong>de</strong> <strong>Linq</strong> mais les<br />
requêtes <strong>de</strong>meurent écrites en Sql ou en XQuery. Ceci n’est pas très surprenant car les langages non
typés comme PHP n’ont généralement pas recours à une phase <strong>de</strong> compilation. Dès lors, la<br />
vérification syntaxique n’a plus <strong>de</strong> sens. Simplifier l’écriture <strong>de</strong>s requêtes pourrait être fait mais<br />
gardons en tête que la majorité <strong>de</strong>s applications réalisées avec ces CMS et ERP sont <strong>de</strong>puis<br />
longtemps liées à <strong>de</strong>s bases <strong>de</strong> données. Pour ces développeurs, il est envisageable que l’écriture <strong>de</strong><br />
requêtes soit un chapitre <strong>de</strong>puis longtemps maîtrisé, ou du moins avec lequel ils ont établis leurs<br />
habitu<strong>de</strong>s.<br />
6.3.3.2 Bases <strong>de</strong> données objets<br />
Les bases <strong>de</strong> données objets sont restées, <strong>de</strong>puis leur apparition, en marge <strong>de</strong>s autres systèmes <strong>de</strong><br />
persistance. Certains indices laissent croire qu’elles font l’objet d’un regain d’intérêt, spécialement<br />
pour <strong>de</strong>s applications légères. Leur gros problème a toujours été <strong>de</strong> pouvoir justifier le changement<br />
<strong>de</strong> toute une couche <strong>de</strong> persistance pourtant déjà fonctionnelle [3]. Un tel changement implique une<br />
migration <strong>de</strong>s données en plus d’une réécriture conséquente <strong>de</strong>s applications qui font usage <strong>de</strong> la<br />
persistance.<br />
Une base <strong>de</strong> données objet constitue un moyen <strong>de</strong> réaliser physiquement la persistance et non <strong>de</strong><br />
fournir une ai<strong>de</strong> au programmeur en soi. Bien sûr la syntaxe du langage Oql, permettant d’interroger<br />
ces bases <strong>de</strong> données, est très proche <strong>de</strong>s langages orientés objet. Mais elle n’est pas réellement<br />
intégrée au langage comme l’est <strong>Linq</strong>. Si nous en parlons ici c’est pour faire un plus ample lien entre<br />
les <strong>de</strong>ux. Plusieurs logiciels <strong>de</strong> gestion <strong>de</strong> bases <strong>de</strong> données orientées objet ont littéralement sauté<br />
sur l’occasion pour se mettre dans la mouvance <strong>de</strong> <strong>Linq</strong>. Ainsi nous pouvons citer db4o qui utilise<br />
<strong>Linq</strong> comme langage <strong>de</strong> requêtes 19 mais aussi Eloquera, une base <strong>de</strong> données <strong>de</strong>stinée au<br />
multimédia, ou encore Siaqodb 20 qui intègre même un éditeur <strong>de</strong> requêtes pour <strong>Linq</strong>. L’écriture <strong>de</strong><br />
requêtes avec <strong>Linq</strong> se fait déjà dans une optique orientée objet, que <strong>de</strong>s bases <strong>de</strong> données objet<br />
tentent <strong>de</strong> s’aligner <strong>de</strong>ssus n’est finalement pas surprenant. Est-ce que <strong>Linq</strong> contribuera à donner un<br />
second souffle aux bases <strong>de</strong> données orientées objet ? L’avenir nous le dira.<br />
19 Information vue sur http://www.db4o.com/s/linqdb.aspx<br />
20 Information vue sur http://siaqodb.com/
6.4 Evolutions <strong>de</strong> <strong>Linq</strong><br />
Nous allons maintenant parler d’avenir. De ce qui est en projets, en développement et <strong>de</strong> ce qui<br />
pourrait le <strong>de</strong>venir. Microsoft a montré sa volonté d’avancer dans le domaine <strong>de</strong> la gestion <strong>de</strong>s<br />
données persistantes mais bien <strong>de</strong>s choses restent à faire.<br />
6.4.1 Version 4<br />
La quatrième version du framework .NET est en phase <strong>de</strong> beta test au moment où sont rédigées ces<br />
lignes. Lors d’une annonce faite le 28 octobre 2008 21 , l’équipe en charge du développement <strong>de</strong> <strong>Linq</strong><br />
précisait que <strong>Linq</strong> to Sql serait désormais considéré comme une moins prioritaire, tous les efforts<br />
étant dirigés vers <strong>Linq</strong> to Entities. Ceci en raison <strong>de</strong> sa capacité à s’adapter à <strong>de</strong> plus nombreux types<br />
<strong>de</strong> bases <strong>de</strong> données relationnelles. <strong>Linq</strong> to Sql n’a pas été abandonné pour autant, mais les<br />
changements annoncés ressemblent plus à un planning <strong>de</strong> résolutions <strong>de</strong> bogues qu’à la mise en<br />
place d’une évolution. <strong>Linq</strong> to Entities quant à lui semble avoir le vent en poupe, tout comme son<br />
compagnon l’Entity Framework. Des améliorations concernant l’ergonomie d’utilisation et <strong>de</strong>s<br />
possibilités étendues comme la création d’une base <strong>de</strong> données <strong>de</strong>puis un modèle édité<br />
graphiquement sont actuellement déployées dans la version en test du framework et <strong>de</strong> Visual<br />
Studio 2010. Quelques changements précieux comme la capacité <strong>de</strong> « self tracking » <strong>de</strong>s objets<br />
entités vont encore alléger la charge du développeur. En ce qui concerne strictement <strong>Linq</strong>, les<br />
améliorations concernent la flexibilité d’utilisation en offrant davantage <strong>de</strong> réglages optionnels. Leur<br />
mise en pratique semble encore un peu floue et les informations obtenues sont parfois<br />
contradictoires, il est donc dur d’être précis. Ce qui est sûr c’est que <strong>Linq</strong> to Entities continuera son<br />
petit bonhomme <strong>de</strong> chemin.<br />
En ce qui concerne les autres implémentations <strong>de</strong> <strong>Linq</strong> il n’y a pas grand-chose à signaler. Les<br />
changements apportés par la version 4 22 semblent surtout tenir <strong>de</strong> la correction.<br />
6.4.2 Ce qui reste à améliorer<br />
Le passage par l’Entity Framework résout en partie le problème posé par <strong>Linq</strong> to Sql et sa non<br />
portabilité vers d’autres bases <strong>de</strong> données que Sql Server. Tant qu’il y aura <strong>de</strong>s options non<br />
proposées par <strong>Linq</strong>, son utilisation restera l’objet d’un compromis. La synchronisation <strong>de</strong>s entités<br />
n’est par exemple pas toujours évi<strong>de</strong>nte et les mappings auto-générés offrent toujours le risque que<br />
les mécanismes cachés soient mal compris. Ces fonctionnalités feront sans doute l’objet<br />
d’améliorations progressives mais pour l’heure nous ne pouvons qu’espérer. Il est bon <strong>de</strong> noter que<br />
Microsoft et l’équipe ADO.NET en particulier essaient <strong>de</strong> se montrer comme étant à l’écoute du<br />
développeur, précisant régulièrement que les retours <strong>de</strong> la communauté seront pris en compte.<br />
Malgré cela, plusieurs implémentations <strong>de</strong> <strong>Linq</strong> comme <strong>Linq</strong> to NHibernate citée précé<strong>de</strong>mment ne<br />
sont pas suivies par Microsoft. Bien que cela puisse paraître normal puisqu’ils n’en sont pas les<br />
auteurs, cela laisse le risque qu’une modification <strong>de</strong>s spécifications <strong>de</strong> <strong>Linq</strong> vienne mettre hors jeu<br />
ces implémentations extérieures. Bien sûr ce risque existe pour <strong>de</strong> nombreuses technologies, mais<br />
l’évolution <strong>de</strong> <strong>Linq</strong> semble encore hésitante, parlant tantôt d’ajouter telle fonctionnalité, tantôt <strong>de</strong><br />
laisser tomber telle autre, livrant les informations officielles au tout <strong>de</strong>rnier moment. Ce qui nous<br />
amène à parler d’une amélioration plus que souhaitable, celle concernant la documentation. En<br />
<strong>de</strong>hors <strong>de</strong> certains ouvrages, l’ensemble <strong>de</strong> la documentation sur <strong>Linq</strong> se trouve en ligne, sur le site<br />
21 http://blogs.msdn.com/adonet/archive/2008/10/29/update-on-linq-to-sql-and-linq-to-entities-<br />
roadmap.aspx<br />
22 Consulter à ce sujet : http://damieng.com/blog/2009/06/01/linq-to-sql-changes-in-net-40
<strong>de</strong> la bibliothèque MSDN. Les livres <strong>de</strong>viennent rapi<strong>de</strong>ment dépassés, constituant rapi<strong>de</strong>ment <strong>de</strong>s<br />
sources d’informations obsolètes et parfois dangereuses. La documentation en ligne est dispersée et<br />
ne propose pas la même visibilité que celle <strong>de</strong> l’API <strong>de</strong> Java. Cela amène rapi<strong>de</strong>ment à <strong>de</strong>voir récolter<br />
<strong>de</strong>s informations morcelées et <strong>de</strong> tenter <strong>de</strong> les mettre soi-même en place. Le but <strong>de</strong> cette étu<strong>de</strong> était<br />
d’explorer et <strong>de</strong> tester, ce qui fait que les miettes d’informations ne constituaient pas réellement un<br />
problème dans la mesure où <strong>de</strong>s tests étaient faits <strong>de</strong> toute façon. Pour une application<br />
professionnelle en revanche, jouer à la <strong>de</strong>vinette est à déconseiller. Une meilleure présentation <strong>de</strong> la<br />
documentation y constituerait une ai<strong>de</strong> précieuse.<br />
6.4.3 <strong>Linq</strong> et Mono<br />
Avant <strong>de</strong> faire le point sur ce tour d’horizon <strong>de</strong> <strong>Linq</strong>, il nous reste un <strong>de</strong>rnier point à traiter qui est<br />
celui du projet Mono. Le projet Mono consiste à porter les spécifications du framework .NET vers les<br />
plateformes Linux. La version actuelle <strong>de</strong> Mono est la 2.6, sortie le 14 décembre 2009. L’ensemble <strong>de</strong><br />
cette section tire ses informations <strong>de</strong> [15] et <strong>de</strong> [14] en ce qui concerne Db <strong>Linq</strong>. Elle intègre, parmi<br />
d’autres nouveautés, les fonctionnalités <strong>Linq</strong> to Sql avec quelques variantes. Il s’agit en réalité <strong>de</strong><br />
l’implémentation Db <strong>Linq</strong> partageant une partie <strong>de</strong> l’API <strong>de</strong> <strong>Linq</strong> to Sql. La plus importante différence<br />
est que la version Mono <strong>de</strong> <strong>Linq</strong> to Sql n’est pas limitée à Sql Server mais permet <strong>de</strong> gérer <strong>de</strong>s<br />
fournisseurs <strong>de</strong> données comme FireBird, MySql, PostGreSql, Ingres, Oracle et SqlLite [14]. [15]<br />
précise que « La totalité du co<strong>de</strong> n’est pas encore portée », il est par exemple signalé que le support<br />
<strong>de</strong> l’assemblage System.Data.<strong>Linq</strong> n’est pas total. Cet assemblage contient la définition <strong>de</strong>s entités et<br />
l’ensemble <strong>de</strong>s extensions <strong>de</strong> métho<strong>de</strong>. Il est amusant <strong>de</strong> constater que <strong>Linq</strong> avec Mono est plus<br />
polyvalent que dans sa version Microsoft qui, pour une raison mystérieuse, ne semble pas vouloir<br />
soutenir activement l’implémentation Db <strong>Linq</strong>. Notons que le projet Mono est développé en<br />
partenariat avec les équipes Microsoft et qu’un tel échange <strong>de</strong>vrait pourtant être possible. Affaire à<br />
suivre.<br />
Bien que n’offrant qu’une implémentation, Mono permet d’utiliser <strong>Linq</strong> avec <strong>de</strong>s bases <strong>de</strong> données<br />
relationnelles. Ceci donne une dimension supplémentaire à <strong>Linq</strong>, qui <strong>de</strong>vient une solution beaucoup<br />
plus portable. L’abstraction vis-à-vis du stockage <strong>de</strong>s données est une chose, la réutilisabilité d’un<br />
co<strong>de</strong> en est une autre. Avec le support <strong>de</strong> Mono, <strong>Linq</strong> <strong>de</strong>vient un choix viable dans un contexte multi<br />
plateformes, ce qui permet <strong>de</strong> concurrencer les solutions « à la Java ».
Conclusion<br />
Nous avons vu quels étaient les mécanismes permettant au langage d’implémenter les bases <strong>de</strong> <strong>Linq</strong>.<br />
Ces mécanismes sont pour la plupart <strong>de</strong>s évolutions <strong>de</strong> ce qui était proposé dans le <strong>de</strong>uxième<br />
framework .NET. Partis <strong>de</strong> ces bases théoriques, nous avons découvert le formalisme <strong>de</strong> requêtes<br />
proposé par <strong>Linq</strong>, formalisme proposant une syntaxe unifiée pour toutes les implémentations. <strong>Linq</strong><br />
se décline en une série d’implémentations, chacune <strong>de</strong>stinée à <strong>de</strong>s types <strong>de</strong> données particuliers.<br />
L’implémentation objet a d’abord été abordée, en détaillant les opérateurs jugés comme étant les<br />
plus intéressants. Un cas pratique a été envisagé, l’implémentation objet <strong>de</strong> <strong>Linq</strong> y a montré son<br />
intérêt vis-à-vis <strong>de</strong> techniques plus classiques. Plusieurs implémentations relationnelles ont ensuite<br />
été abordées, avec une attention toute particulière pour <strong>Linq</strong> to Sql, implémentation ve<strong>de</strong>tte <strong>de</strong> <strong>Linq</strong>.<br />
Les concepts y ont été vus en détails avant d’analyser les performances <strong>de</strong> cette implémentation<br />
confrontée à un accès classique aux données. D’autres implémentations ont ensuite été examinées,<br />
mettant en avant l’autre technologie phare <strong>de</strong> Microsoft, l’Entity Framework. L’exploration <strong>de</strong> <strong>Linq</strong><br />
s’est poursuivie avec l’étu<strong>de</strong> <strong>de</strong> l’implémentation Xml et <strong>de</strong>s nouveautés qu’elles apportaient, en<br />
particulier dans la gestion <strong>de</strong> données directement en Xml. Avec un peu <strong>de</strong> recul, le point a été fait<br />
sur l’expérience <strong>Linq</strong> ainsi que sur ses forces et faiblesses. L’abstraction suppélmentaire du co<strong>de</strong> visà-vis<br />
<strong>de</strong> la couche <strong>de</strong> persistance est un apport très positif. Les anciennes techniques d’accès<br />
relationnels ne sont pas obsolètes, le besoin <strong>de</strong> gérer plus finement <strong>de</strong>s bases <strong>de</strong> données sera<br />
toujours présent. Malgré tout, <strong>Linq</strong> constitue un danger pour le développeur non averti. Les tests ont<br />
montré qu’une connaissance <strong>de</strong>s mécanismes utilisés en interne s’avère indispensable pour éviter les<br />
pièges qu’une trop gran<strong>de</strong> facilité pourrait masquer. Le manque <strong>de</strong> documentation claire et<br />
accessible est également à déplorer. L’étu<strong>de</strong> s’est poursuivie par l’examen <strong>de</strong>s alternatives<br />
envisageables à <strong>Linq</strong> et leurs correspondances relatives. De nombreux concurrents existent mais très<br />
peu proposent une abstraction aussi forte que celle <strong>de</strong> <strong>Linq</strong>. Seul JDO en Java semble constituer une<br />
alternative sérieuse à <strong>Linq</strong> et .NET. Pour finir ce tour d’horizon, les possibles évolutions <strong>de</strong> <strong>Linq</strong> ont<br />
été discutées. Il est difficile <strong>de</strong> prévoir ce que l’avenir nous réserve mais pour l’heure <strong>Linq</strong> semble se<br />
stabiliser. Le projet Mono en est encore à ses débuts avec <strong>Linq</strong> mais la portabilité qu’il offre au projet<br />
<strong>Linq</strong> tout entier laisse croire que son évolution à lui aussi se poursuivra. <strong>Linq</strong>, nouveau paradigme,<br />
annonciateur <strong>de</strong> la programmation orientée données ? Un long chemin reste à accomplir, l’avenir<br />
nous donnera la réponse.
Bibliographie<br />
1. « Programming Microsoft <strong>Linq</strong> », Paolo Pialorsi et Marco Russo, éditions Microsoft Press,<br />
2008.<br />
2. « C# et .NET, version 2 », Gérard Lebrun, éditions Eyrolles, 2007.<br />
3. « L’orienté objet, troisième édition », Hugues Bersini, éditions Eyrolles, 2007.<br />
4. « Documentation en ligne du réseau <strong>de</strong> développeurs Microsoft »,<br />
http://msdn.microsoft.com, technical references on C# 3.0 and 3.5<br />
5. « Technical preview of Entity Framework », http://msdn.microsoft.com/enus/library/aa697427%28v=VS.80%29.aspx<br />
6. « Tout sur WPF, <strong>Linq</strong>, C# et .NET en général », Thomas Lebrun,<br />
http://blogs.<strong>de</strong>veloppeur.org/tom/<strong>de</strong>fault.aspx<br />
7. « Programming Entity Framework », Julie Lerman, O’Reilly Media, janvier 2009.<br />
8. « The Apache JDO web page », hosted by Apache Software Foundation,<br />
http://db.apache.org/jdo/<br />
9. « db4o, an object oriented datastore », http://www.db4o.com/s/linqdb.aspx<br />
10. « Siaqodb first full <strong>Linq</strong>-supported object database », http://siaqodb.com/<br />
11. « <strong>Linq</strong> in Action », par Fabrice Marguerie, Steve Eichert et Jim Wooley, O’Reilly Media, 2008.<br />
12. « Bases <strong>de</strong> données », Esteban Zimànyi, cours donné en MA1 <strong>de</strong> la faculté <strong>de</strong>s sciences<br />
appliquées <strong>de</strong> l’Université Libre <strong>de</strong> <strong>Bruxelles</strong>, 2008.<br />
13. « Forum <strong>de</strong>s développeurs C# », forum <strong>de</strong> la communauté Developpez.com,<br />
http://www.<strong>de</strong>veloppez.net/forums/f484/dotnet/langages/csharp/<br />
14. « Db<strong>Linq</strong> 2007 », site du projet Db <strong>Linq</strong>, http://co<strong>de</strong>.google.com/p/dblinq2007/<br />
15. « Mono 2.6 Release Notes », site du projet Mono, partie relative à la version 2.6,<br />
http://www.mono-project.com/Release_Notes_Mono_2.6<br />
16. « Hibernate in Action », par Christian Bauer et Gavin King, O’Reilly.<br />
17. « NHibernate for .NET », site hébergé par la communauté JBoss,<br />
http://community.jboss.org/wiki/NHibernateforNET<br />
18. « <strong>Linq</strong> to NHibernate page », http://www.hookedonlinq.com/LINQToNHibernate.ashx<br />
19. « Doctrine ORM page », http://www.doctrine-project.org/<br />
20. « ORM comparison and benchmark », ormeter.net/<br />
21. « Apache Cayenne : Orm comparison for Java », https://cwiki.apache.org/CAY/ormcomparison.html<br />
22. « eZ Publish », site du projet open-source eZ Publish, ez.no<br />
23. « Drupal community plumbing», site <strong>de</strong> la communauté Drupal, drupal.org<br />
24. « OpenERP », site <strong>de</strong> OpenERP anciennement tinyErp, http://www.openerp.com/
Annexe 1 : Co<strong>de</strong> <strong>de</strong> l’exemple <strong>Linq</strong> to object<br />
Contenu du fichier Ville.cs :<br />
using System;<br />
using System.Collections.Generic;<br />
using System.<strong>Linq</strong>;<br />
using System.Text;<br />
namespace ex<strong>Linq</strong><br />
{<br />
class Ville<br />
{<br />
private string nom;<br />
private int nbrHab;<br />
public List communes;<br />
public List liaisons;<br />
public Ville(string n, int nbr)<br />
{<br />
nom = n;<br />
nbrHab = nbr;<br />
communes = new List();<br />
liaisons = new List();<br />
}<br />
public string Nom {get{return nom;} set{nom=value;}}<br />
public int Hab { get { return nbrHab; } set { if(value>0) nbrHab =<br />
value; } }<br />
}<br />
}<br />
public void AjouterCommune(string nom)<br />
{<br />
communes.Add(nom);<br />
}<br />
public void RetirerCommune(string nom)<br />
{<br />
communes.Remove(nom);<br />
}<br />
public void RelierA(Ville v)<br />
{<br />
liaisons.Add(v);<br />
}<br />
public void EnleverLiaisonAvec(Ville v)<br />
{<br />
liaisons.Remove(v);<br />
}
Contenu du fichier program.cs :<br />
using System;<br />
using System.Collections.Generic;<br />
using System.<strong>Linq</strong>;<br />
using System.<strong>Linq</strong>.Expressions;<br />
using System.Text;<br />
namespace ex<strong>Linq</strong><br />
{<br />
class Program<br />
{<br />
static void Main(string[] args)<br />
{<br />
Villes();<br />
Console.Read();<br />
}<br />
static void Villes()<br />
{<br />
List reseauRoutier = InitialiseLeReseau();<br />
var requete = from v in reseauRoutier<br />
from c in v.liaisons<br />
where v.communes.Count() >= 2<br />
&& c.Hab
}<br />
}<br />
}<br />
v6.RelierA(v2);<br />
v3.RelierA(v5);<br />
v5.RelierA(v3);<br />
v4.RelierA(v6);<br />
v6.RelierA(v4);<br />
v5.RelierA(v6);<br />
v6.RelierA(v5);<br />
List reseau = new List();<br />
reseau.Add(v);<br />
reseau.Add(v2);<br />
reseau.Add(v3);<br />
reseau.Add(v4);<br />
reseau.Add(v5);<br />
reseau.Add(v6);<br />
return reseau;<br />
Vu les valeurs données lors <strong>de</strong> l’initialisation, on constate que « l’ensemble <strong>de</strong>s villes ayant au moins<br />
<strong>de</strong>ux communes et étant connectées à au moins une ville <strong>de</strong> maximum 10000 habitants » se résume<br />
à Braine l’Alleud, qui est la seule ville connectée à Zaventem (la seule <strong>de</strong> moins <strong>de</strong> 10000 habitants) à<br />
possé<strong>de</strong>r au moins <strong>de</strong>ux communes.
Annexe 2 : Co<strong>de</strong> <strong>de</strong> tests <strong>de</strong> performance pour <strong>Linq</strong> to Sql<br />
Contenu <strong>de</strong> la classe ADOManager.cs :<br />
using System;<br />
using System.Collections.Generic;<br />
using System.<strong>Linq</strong>;<br />
using System.Text;<br />
using System.Data.Common;<br />
using System.Data.SqlClient;<br />
namespace <strong>Linq</strong>_<strong>de</strong>mo_console_3<br />
{<br />
public class ADOManager<br />
{<br />
private SqlConnection db;<br />
private SqlDataRea<strong>de</strong>r rdr;<br />
public void Connect()<br />
{<br />
Console.WriteLine();<br />
db = new SqlConnection("Data<br />
Source=.\\SQLEXPRESS;AttachDbFilename=\"G:\\Documents and<br />
Settings\\Charles\\Mes documents\\Visual Studio<br />
2008\\Projects\\<strong>Linq</strong>_<strong>de</strong>mo_console_3\\<strong>Linq</strong>SelectTest\\<strong>Linq</strong>DB.mdf\";Integrate<br />
d Security=True;Connect Timeout=30;User Instance=True");<br />
rdr = null;<br />
try {<br />
Console.WriteLine("ADO Initializing");<br />
Console.WriteLine("Manual Connection to DataBase");<br />
db.Open();<br />
Console.WriteLine("DataSource : "+db.DataSource);<br />
Console.WriteLine("DataBase : " + db.Database);<br />
}<br />
catch(Exception e)<br />
{<br />
Console.WriteLine(e.Message);<br />
}<br />
}<br />
public void LaunchInsertTest(int charge)<br />
{<br />
this.Connect();<br />
Console.WriteLine();<br />
Console.WriteLine("*** ADO Test running ***");<br />
//Compute Time interval required to construct query<br />
DateTime start = DateTime.Now;<br />
Console.WriteLine("Starting test at " + start.Hour + ":" +<br />
start.Minute + ":" + start.Second + ":" + start.Millisecond);<br />
string sql = "INSERT INTO<br />
Customers(Name,Address_city,Address_zip,Address_street,Address_number)";<br />
string sql2;<br />
string totalQuery = "";<br />
for(int i=1;i
DateTime qStart = DateTime.Now;<br />
Console.WriteLine("Starting query at " + qStart.Hour + ":" +<br />
qStart.Minute + ":" + qStart.Second + ":" + qStart.Millisecond);<br />
SqlCommand com = new SqlCommand(totalQuery, db);<br />
com.ExecuteNonQuery();<br />
DateTime end = DateTime.Now;<br />
Console.WriteLine("End of test at " + end.Hour + ":" +<br />
end.Minute + ":" + end.Second + ":" + end.Millisecond);<br />
Console.WriteLine("Query duration : "+(end-qStart).ToString());<br />
Console.WriteLine("Test Duration : " + (end -<br />
start).ToString());<br />
this.Deconnect();<br />
}<br />
public void SingleQuery(int i)<br />
{<br />
string sql = "INSERT INTO<br />
Customers(Name,Address_city,Address_zip,Address_street,Address_number)";<br />
sql += " VALUES('cust" + i + "','city" + i + "','" + i +<br />
"','street" + i + "'," + i + "); ";<br />
SqlCommand com = new SqlCommand(sql, db);<br />
com.ExecuteNonQuery();<br />
}<br />
}<br />
}<br />
public void Deconnect()<br />
{<br />
if (db != null)<br />
db.Close();<br />
}
Contenu <strong>de</strong> la classe <strong>Linq</strong>Benchmarking.cs :<br />
using System;<br />
using System.Collections.Generic;<br />
using System.<strong>Linq</strong>;<br />
using System.Text;<br />
namespace <strong>Linq</strong>_<strong>de</strong>mo_console_3<br />
{<br />
public class <strong>Linq</strong>_benchmarking<br />
{<br />
private CustomerDataContext db;<br />
public <strong>Linq</strong>_benchmarking()<br />
{<br />
db = new CustomerDataContext();<br />
}<br />
public void LaunchInsertTest(int charge)<br />
{<br />
Console.WriteLine("*** LINQ Test running ***");<br />
//Compute Time interval required to construct query<br />
DateTime dt = System.DateTime.Now;<br />
Console.WriteLine("Starting test at<br />
"+dt.Hour+":"+dt.Minute+":"+dt.Second+":"+dt.Millisecond);<br />
for (int loop = 1; loop
}<br />
}<br />
}<br />
public void CleanDB()<br />
{<br />
var cust = from c in db.Customers<br />
select c;<br />
foreach (Customers cst in cust)<br />
{<br />
db.Customers.DeleteOnSubmit(cst);<br />
}<br />
db.SubmitChanges();<br />
}<br />
public string TimeBetween(DateTime start, DateTime end)<br />
{<br />
string time = "";<br />
time += (end - start).ToString();<br />
return time;<br />
}<br />
public void SingleQuery(int n)<br />
{<br />
Customers c = new Customers();<br />
c.ID = n;<br />
c.Name = "customer" + n.ToString();<br />
c.Address_city = "city" + n.ToString();<br />
c.Address_number = n;<br />
c.Address_street = "street" + n.ToString();<br />
c.Address_zip = n.ToString();<br />
db.Customers.InsertOnSubmit(c);<br />
//db.SubmitChanges();<br />
}<br />
public void Submit()<br />
{<br />
db.SubmitChanges();<br />
}
Contenu <strong>de</strong> Customer.dbml :<br />
#pragma warning disable 1591<br />
//-------------------------------------------------------------------------<br />
-----<br />
// <br />
// Ce co<strong>de</strong> a été généré par un outil.<br />
// Version du runtime :2.0.50727.3603<br />
//<br />
// Les modifications apportées à ce fichier peuvent provoquer un<br />
comportement incorrect et seront perdues si<br />
// le co<strong>de</strong> est régénéré.<br />
// <br />
//-------------------------------------------------------------------------<br />
-----<br />
namespace <strong>Linq</strong>_<strong>de</strong>mo_console_3<br />
{<br />
using System.Data.<strong>Linq</strong>;<br />
using System.Data.<strong>Linq</strong>.Mapping;<br />
using System.Data;<br />
using System.Collections.Generic;<br />
using System.Reflection;<br />
using System.<strong>Linq</strong>;<br />
using System.<strong>Linq</strong>.Expressions;<br />
using System.ComponentMo<strong>de</strong>l;<br />
using System;<br />
[System.Data.<strong>Linq</strong>.Mapping.DatabaseAttribute(Name="<strong>Linq</strong>DB")]<br />
public partial class CustomerDataContext :<br />
System.Data.<strong>Linq</strong>.DataContext<br />
{<br />
private static System.Data.<strong>Linq</strong>.Mapping.MappingSource<br />
mappingSource = new AttributeMappingSource();<br />
#region Extensibility Method Definitions<br />
partial void OnCreated();<br />
partial void InsertCustomers(Customers instance);<br />
partial void UpdateCustomers(Customers instance);<br />
partial void DeleteCustomers(Customers instance);<br />
#endregion<br />
public CustomerDataContext() :<br />
base(global::<strong>Linq</strong>_<strong>de</strong>mo_console_3.Properties.Settings.Default.<strong>Linq</strong>DBCo<br />
nnectionString, mappingSource)<br />
{<br />
OnCreated();<br />
}<br />
public CustomerDataContext(string connection) :<br />
base(connection, mappingSource)<br />
{<br />
OnCreated();<br />
}<br />
public CustomerDataContext(System.Data.IDbConnection<br />
connection) :<br />
base(connection, mappingSource)
{<br />
}<br />
OnCreated();<br />
public CustomerDataContext(string connection,<br />
System.Data.<strong>Linq</strong>.Mapping.MappingSource mappingSource) :<br />
base(connection, mappingSource)<br />
{<br />
OnCreated();<br />
}<br />
public CustomerDataContext(System.Data.IDbConnection<br />
connection, System.Data.<strong>Linq</strong>.Mapping.MappingSource mappingSource) :<br />
base(connection, mappingSource)<br />
{<br />
OnCreated();<br />
}<br />
}<br />
public System.Data.<strong>Linq</strong>.Table Customers<br />
{<br />
get<br />
{<br />
return this.GetTable();<br />
}<br />
}<br />
[Table(Name="dbo.Customers")]<br />
public partial class Customers : INotifyPropertyChanging,<br />
INotifyPropertyChanged<br />
{<br />
private static PropertyChangingEventArgs emptyChangingEventArgs<br />
= new PropertyChangingEventArgs(String.Empty);<br />
private int _ID;<br />
private string _Name;<br />
private string _Address_city;<br />
private string _Address_zip;<br />
private string _Address_street;<br />
private System.Nullable _Address_number;<br />
private string _Email;<br />
#region Extensibility Method Definitions<br />
partial void OnLoa<strong>de</strong>d();<br />
partial void OnValidate(System.Data.<strong>Linq</strong>.ChangeAction action);<br />
partial void OnCreated();<br />
partial void OnIDChanging(int value);<br />
partial void OnIDChanged();<br />
partial void OnNameChanging(string value);<br />
partial void OnNameChanged();<br />
partial void OnAddress_cityChanging(string value);<br />
partial void OnAddress_cityChanged();<br />
partial void OnAddress_zipChanging(string value);<br />
partial void OnAddress_zipChanged();
partial void OnAddress_streetChanging(string value);<br />
partial void OnAddress_streetChanged();<br />
partial void OnAddress_numberChanging(System.Nullable value);<br />
partial void OnAddress_numberChanged();<br />
partial void OnEmailChanging(string value);<br />
partial void OnEmailChanged();<br />
#endregion<br />
public Customers()<br />
{<br />
OnCreated();<br />
}<br />
[Column(Storage="_ID", AutoSync=AutoSync.OnInsert, DbType="Int<br />
NOT NULL IDENTITY", IsPrimaryKey=true, IsDbGenerated=true)]<br />
public int ID<br />
{<br />
get<br />
{<br />
return this._ID;<br />
}<br />
set<br />
{<br />
if ((this._ID != value))<br />
{<br />
this.OnIDChanging(value);<br />
this.SendPropertyChanging();<br />
this._ID = value;<br />
this.SendPropertyChanged("ID");<br />
this.OnIDChanged();<br />
}<br />
}<br />
}<br />
[Column(Storage="_Name", DbType="VarChar(50) NOT NULL",<br />
CanBeNull=false)]<br />
public string Name<br />
{<br />
get<br />
{<br />
return this._Name;<br />
}<br />
set<br />
{<br />
if ((this._Name != value))<br />
{<br />
this.OnNameChanging(value);<br />
this.SendPropertyChanging();<br />
this._Name = value;<br />
this.SendPropertyChanged("Name");<br />
this.OnNameChanged();<br />
}<br />
}<br />
}<br />
[Column(Storage="_Address_city", DbType="VarChar(50) NOT NULL",<br />
CanBeNull=false)]<br />
public string Address_city<br />
{<br />
get<br />
{
}<br />
}<br />
set<br />
{<br />
}<br />
return this._Address_city;<br />
if ((this._Address_city != value))<br />
{<br />
this.OnAddress_cityChanging(value);<br />
this.SendPropertyChanging();<br />
this._Address_city = value;<br />
this.SendPropertyChanged("Address_city");<br />
this.OnAddress_cityChanged();<br />
}<br />
[Column(Storage="_Address_zip", DbType="VarChar(8) NOT NULL",<br />
CanBeNull=false)]<br />
public string Address_zip<br />
{<br />
get<br />
{<br />
return this._Address_zip;<br />
}<br />
set<br />
{<br />
if ((this._Address_zip != value))<br />
{<br />
this.OnAddress_zipChanging(value);<br />
this.SendPropertyChanging();<br />
this._Address_zip = value;<br />
this.SendPropertyChanged("Address_zip");<br />
this.OnAddress_zipChanged();<br />
}<br />
}<br />
}<br />
[Column(Storage="_Address_street", DbType="VarChar(50)")]<br />
public string Address_street<br />
{<br />
get<br />
{<br />
return this._Address_street;<br />
}<br />
set<br />
{<br />
if ((this._Address_street != value))<br />
{<br />
this.OnAddress_streetChanging(value);<br />
this.SendPropertyChanging();<br />
this._Address_street = value;<br />
this.SendPropertyChanged("Address_street");<br />
this.OnAddress_streetChanged();<br />
}<br />
}<br />
}<br />
[Column(Storage="_Address_number", DbType="Int")]<br />
public System.Nullable Address_number<br />
{<br />
get<br />
{
}<br />
}<br />
set<br />
{<br />
}<br />
return this._Address_number;<br />
if ((this._Address_number != value))<br />
{<br />
this.OnAddress_numberChanging(value);<br />
this.SendPropertyChanging();<br />
this._Address_number = value;<br />
this.SendPropertyChanged("Address_number");<br />
this.OnAddress_numberChanged();<br />
}<br />
[Column(Storage="_Email", DbType="VarChar(50)")]<br />
public string Email<br />
{<br />
get<br />
{<br />
return this._Email;<br />
}<br />
set<br />
{<br />
if ((this._Email != value))<br />
{<br />
this.OnEmailChanging(value);<br />
this.SendPropertyChanging();<br />
this._Email = value;<br />
this.SendPropertyChanged("Email");<br />
this.OnEmailChanged();<br />
}<br />
}<br />
}<br />
public event PropertyChangingEventHandler PropertyChanging;<br />
public event PropertyChangedEventHandler PropertyChanged;<br />
protected virtual void SendPropertyChanging()<br />
{<br />
if ((this.PropertyChanging != null))<br />
{<br />
this.PropertyChanging(this,<br />
emptyChangingEventArgs);<br />
}<br />
}<br />
protected virtual void SendPropertyChanged(String propertyName)<br />
{<br />
if ((this.PropertyChanged != null))<br />
{<br />
this.PropertyChanged(this, new<br />
PropertyChangedEventArgs(propertyName));<br />
}<br />
}<br />
}<br />
}<br />
#pragma warning restore 1591
Contenu du fichier program.cs :<br />
using System;<br />
using System.Collections.Generic;<br />
using System.<strong>Linq</strong>;<br />
using System.Text;<br />
using System.Data;<br />
using MySql.Data;<br />
using MySql.Data.MySqlClient;<br />
namespace <strong>Linq</strong>ToDataSet<br />
{<br />
class Program<br />
{<br />
static void Main(string[] args)<br />
{<br />
MySqlConnection con = new<br />
MySqlConnection("Database=My<strong>Linq</strong>Sql;Uid='root';Pwd='C1$oon'");<br />
string cmdString = "show tables";<br />
MySqlCommand cmd = new MySqlCommand(cmdString, con);<br />
con.Open();<br />
MySqlDataRea<strong>de</strong>r dr = cmd.ExecuteRea<strong>de</strong>r();<br />
Console.WriteLine("Tables présentes dans la Db :");<br />
while (dr.Read())<br />
{<br />
Console.WriteLine(dr.GetValue(0));<br />
}<br />
//Fermeture du rea<strong>de</strong>r pour eviter conflit avec DataAdapter<br />
dr.Close();<br />
DataSet ds = new DataSet("MySqlDataSet");<br />
string selectString = @"SELECT * FROM objects1";<br />
MySqlDataAdapter da = new MySqlDataAdapter(selectString,con);<br />
da.TableMappings.Add("objects1", "Table");<br />
da.Fill(ds);<br />
//interrogation du DataSet ds avec <strong>Linq</strong> to DataSet<br />
DataTable dt1 = ds.Tables["Table"];<br />
var dataSetQuery = from o in dt1.AsEnumerable()<br />
select new {ID = o.Field("Id"), TEXT =<br />
o.Field("Desc")};<br />
Console.WriteLine("Requête adressée au DataSet construit <strong>de</strong>puis une<br />
Db MySql");<br />
}<br />
}<br />
}<br />
foreach(var res in dataSetQuery)<br />
{<br />
Console.WriteLine(res);<br />
}<br />
//Fermeture <strong>de</strong> la connexion<br />
con.Close();<br />
Console.WriteLine("Appuyez sur Enter pour terminer...");<br />
Console.ReadLine();
Annexe 3 : Utilisation d’une base <strong>de</strong> données MySQL avec <strong>Linq</strong> to<br />
DataSet<br />
Phase 1 : Création <strong>de</strong> la base <strong>de</strong> données avec MySQL<br />
Téléchargement <strong>de</strong> MySQL Community Server (disponible à cette adresse :<br />
http://<strong>de</strong>v.mysql.com/downloads/ ). Après installation et première configuration, lancement <strong>de</strong><br />
l’utilitaire MySQL en ligne <strong>de</strong> comman<strong>de</strong>. Les comman<strong>de</strong>s entrées sont, dans l’ordre :<br />
Create Database My<strong>Linq</strong>Sql \g<br />
\u My<strong>Linq</strong>Sql<br />
Create Table objects1 ( `Id` int not null, `Desc` varchar(20) <strong>de</strong>fault ‘’, primary key(Id) );<br />
Create Table objects2 ( `Id` int not null, `Co<strong>de</strong>` int not null, `ShortDesc` varchar(20) <strong>de</strong>fault ‘’,<br />
primary key (Id), foreign key (Co<strong>de</strong>) references objects1(Id) );<br />
Insert into objects1(`Id`,`Desc`) values (1,”<strong>de</strong>scription 1”); Insert into objects1(`Id`,`Desc`)<br />
values (2,”<strong>de</strong>scription 2”); insert into objects1(`Id`,`Desc`) values (3,”<strong>de</strong>scription 3”);<br />
Insert into objects2 values (1,1,”obj2 pointe vers 1”); Insert into objects2 values<br />
(2,1,”obj2.2”); Insert into objects2 values (3,2,”obj2.3”); Insert into objects2 values<br />
(4,1,”obj2.4”); Insert into objects2 values (5,2,”obj2.5”);<br />
Ce qui doit donner comme contenu pour objects1 (Select * from objects1 ;) :<br />
Et pour objects2 :