12.07.2013 Views

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

SHOW MORE
SHOW LESS

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 :

Hooray! Your file is uploaded and ready to be published.

Saved successfully!

Ooh no, something went wrong!