Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Interopératibilité Windows / Linux via MONO (suite...)

Pour ceux qui ont suivi mon billet précédent concernant l'installation d'une Mandriva et d'une OpenSuse sous VirtualBox dans un XP et de tout le bataclan MONO vous savez ce que je bricole ses jours-ci, pour les autres, et bien maintenant vous le savez :-) Le but du jeu n'est bien évidemment pas la prouesse de virtualiser un Linux sur XP, même si le chemin est long, mais de tester l'interopérabilité des exécutables .NET entre Windows et Linux.

Le casse-tête des installations et le pont réseau

Je ne vous résume pas l'épisode précédent, mais ce fut un peu laborieux. Mon dernier problème consistait à pouvoir lire mes disques XP depuis le Linux virtualisé. Ca n'a pas été facile mais j'y suis arrivé. Via un driver réseau qu'il faut installer à part de la VirtualBox Sun et qui monte dans Windows une connexion réseau spéciale (à laquelle on donne le nom qu'on veut). Ensuite il faut paramétrer VBox pour que l'instance virtualisée de Linux voit ce réseau interne et non pas la carte réseau (émulée) qui donne accès au web notamment. Mais cela n'est pas tout, il ne faut pas oublier d'aller dans les connexions de Windows, d'ouvrir les propriétés du Pont Réseau et de cocher les cases des réseaux qu'on veut relier dont le fameux réseau virtual créé pour la VBox! A partir de là on peut voir les répertoires partagés Windows sous OpenSuse et les ouvrir, sans perdre l'accès Web bien entendu. Magique...

Le casse-tête des Windows forms

Pour la petite histoire, faire du Gtk ne m'interpelle pas pour l'instant. Je veux pouvoir écrire une soft .NET avec Windows Forms et l'exécuter sous Linux. Donc j'ai besoin des Windows Forms sous MONO/Linux. Or, si l'assemblage des WF est bien présent dans les dernières livraisons de MONO, MonoDevelop, l'IDE, lui ne sait pas designer des forms WF... Diable!

Je vous passe les étapes, les recherches, les essais infructueux, j'en suis arrivé à installer MONO sous Windows et SharpDevelop, puisque celui-ci est sensé pouvoir cibler MONO. Oui... il était sensé faut-il préciser. SharpDevelop ne supporte plus MONO, ils se concentrent sur .NET tout court. Zut! Re-recherches, re-essais infructueux. Mais il existe dans les sources de SharpDevelop un répertoire Sample qui contient un sous-répertoire MONO. Qu'est-ce donc ? Un plug-in pour faire reconnaitre MONO à SharpDevelop (sous Windows donc). Bingo! Et ça marche.

La portabilité mise à l'épreuve

Une fois toutes ces usines à gaz configurées et capables de tourner sans problème (ce qui reste toujours un miracle à mes yeux), il restait à faire un premier test de portabilité. Ne soyons pas trop gourmands, disons juste un simple Hello Word en mode console. Faut y aller doucement !

Nouveau projet sous SharpDevelop (sous XP donc), cible MONO, projet console. On fait un Console.Writeline et une pause clavier histoire de voir si marche ou pas. Sous Windows le projet s'exécute sous MONO sans problème ("MONO montest.exe"), plus fort le même exécutable passe aussi directement ("montest.exe") et il est alors exécuté par le framework .NET Microsoft. Pas mal.

On lance OpenSuse dans VirtualBox, on accède via le réseau virtuel au répertoire partagé qui contient "montest.exe" sur XP et on exécute via MONO (comme sous Windows "mono montest.exe").. Et là, la magie s'opère, ce bel EXE créé sous Windows s'exécute sous OpenSuse sans aucune recompilation. C'est beau j'en pleurerais...

Conclusion

La prochaine étape: tester une application avec une form Windows Forms. Mais déjà à ce stade on peut affirmer que la compatibilité "binaire" des "exe" .NET entre Windows, Mono, et Linux est assurée, ce n'est pas de la science fiction, ça marche vraiment. je ne dis pas que des grosses applications peuvent passer sans recompilation, n'exagérons rien. Mais déjà les applis consoles, les libs de code, etc, tout ça passe.

.NET offre désormais ce que tout le monde à promis depuis des dizaines d'années sans vraiment y arriver : l'interopérabilité. Delphi avec Kylix avait fait une belle tentative, hélas sans succès (vendre un IDE Kylix à 20.000 francs de l'époque pour des linuxiens habitués à la gratuité, c'était crétin, tout autant que d'impose QT sous Windows), Java est le seul langage qui avait réussi à faire de l'interopérabilité, mais au prix de la lenteur légendaire d'un langage interprété et au prix d'une complexité et d'une gigantesque incompatibilités entre les VM et les libs diverses. Même si certains de ces défauts sont aujourd'hui gommés, .NET est un framework bien plus cohérent que Java, bien plus moderne, et sa normalisation, qui a permis le projet MONO, démontre toute la viabilité de cette plateforme même au-delà du cercle (déjà très large) de la communauté Windows.

Une fois tous mes tests terminés j'en ferai certainement un article, voir quelques chapitres d'un prochain bouquin à venir..
J'espère en tout cas que ces petits billets vous donneront, à vous aussi, l'envie de tester tout ça. .NET ouvre des portes, des tas de portes, c'est une gourmandise que de toutes les pousser et de découvrir derrière chacune d'elle un monde à s'approprier. Soyez gourmands aussi !

et... Stay Tuned !

C# MONO et OpenSuse, le tout virtualisé. Ou "comment occuper un geek" en une leçon.

Il faut bien se détendre un peu... Et que fait un geek pour se détendre ? Il allume son PC (en fait il ne l'éteint jamais !). Alors pour me changer les idées, je me suis mis en tête d'installer MONO pour "voir" où le projet en est. C'est cette petite aventure que je m'en vais vous conter, ça pourra vous servir...

Mais voilà, avant de jouer avec C# sous Linux il faut d'abord installer Linux (hmmm), choisir l'une de ses distribs et surtout arriver à le faire marcher ! Et comme je suis pervers mais pas fou, hors de question de faire un dual boot (j'en ai déjà un XP/Vista) ni même de reformater l'une de mes machines. Il va donc falloir virtualiser...

Abonné MSDN je dispose de tout plein de softs de Microsoft, dont Virtual PC 2007. J'ai commencé petit joueur par une Mandriva. Impossible à installer, le fameux problème d'écran 24 bits par défaut de Linux... Une vraie cata. J'ai passé des heures à chercher sur le Web, on trouve des trucs, mais aucun ne marche !

Après des heures à tourner en rond face à un écran très élargi et illisible, j'ai renoncé. J'ai donc laissé Virtual PC de côté, super chouette pour virtualiser du DOS, de l'XP ou du Vista, mais visiblement pas très open à Linux...

En farfouinant je suis tombé sur un virtualiseur gratuit écrit par l'ennemi juré de Microsoft, Sun. Du coup j'ai pas essayé de voir si leur machin savait lui gérer la virtualisation de Windows aussi mal que Virtual PC gère mal Linux, mais pour virtualiser du Linux ça semblait un bon choix. J'ai donc télécharger VirtualBox. Ca s'installe facilement, ça fait ce que ça dit et ça marche plutôt vite. Un bon point pour ce produit gratuit.

J'entreprends alors de virtualiser mon Mandriva que je n'avais pu installer sous Virtual PC. Et là, surprise, ça passe facile. Mon coeur s'emplit de joie en voyant Linux tourner dans une fenêtre... Mandriva, hélas, c'est KDE. L'avantage de KDE c'est d'être un clône de Windows XP niveau interface. On s'y retrouve facilement. Mais voilà, je n'y avais pas pensé avant, le kit de développement de MONO réclame visiblement GNOME ! Zut! me suis-je exclamé après toutes ses heures (en réalité c'était pas "zut", mais un truc du même genre en plus .. illustré :-) ). [EDIT:] Il semble que le site de MONO ignore Mandriva, mais depuis le gestionnaire de logiciels de ce dernier on peut accéder au téléchargement de MONO et des outils de développement. Je viens de le faire et oui, ça marche parfaitement. On peut donc utiliser Mandriva sans problème [/EDIT]. [EDIT2:] Comme je suis une quiche sous Linux, je n'avais pas pigé non plus que Mandriva permet de changer de bureau et de système d'affichage. On peut donc être en KDE ou GNOME sans problème la non plus. C'est en bûchant qu'on devient bucheron n'est-il pas... [/EDIT2]

Je farfouine à nouveau... et au final je me décide pour OpenSuse, promu par Novell qui se trouve derrière MONO aussi, ça devrait matcher. Bonne pioche ! Je prend la 11.0 mais ils préviennent que les fichiers de plus 4 Go posent des problèmes en téléchargement HTTP depuis Internet Explorer. J'avais téléchargé des DVD ISO il y a peu de temps avec IE sans problème et je me doutais bien qu'il s'agissait là de médisances de concurrents, mais par précaution j'ai pris l'option téléchargement par Torrent. Deux ou trois heures après j'avais mon ISO.

...Et on est reparti pour la création d'une machine virtuelle. Au passage je me dis, tiens, pourquoi ne pas redonner sa chance à Virtual PC, après tout. Je vous passe les détails, une heure de perdue pour exactement le même problème sans arriver à trouver une solution. Retour à VirtualBox.

Là les choses se passent comme avec Mandriva, c'est à dire bien et facilement.

Installation de OpenSuse. C'est pas mal. J'arrive ensuite à faire marcher l'installeur YaST pour ajouter les références au projet Mono et à installer l'ensemble des paquets.

Cool. J'ai maintenant un joli OpenSuse avec MONO et l'environnement de développement ! Je fais un nouveau projet console et je tape deux ou trois truc pour voir. Pas mal. C'est du niveau C# 2.0 avec les génériques. Je n'ai pas encore creusé plus loin, mais voir mon petit programme s'animer dans une fenêtre Linux sur mon XP, ça fait plaisir...

J'ai voulu aller un peu plus loin en partageant un disque physique de ma machine avec la machine virtuelle. VirtualBox le permet en installant des extensions. Retour à la galère, il faut les sources du noyau, le make et GCC. Trouver ces merveilles, les placer dans YaST et les installer s'est révélé moins dur que je le pensais. Mais la fameuse extension de VirtualBox n'a jamais voulu s'installer... C'est un script (.run) et quand je double cliquait dessus il me disait que j'avais pas les privilège admin ce qui plantait le script visiblement. Pourtant le compte que je me suis créé est dans le group des admins... Mystère de Linux. En changeant de user et en me loggant comme "root" c'est allé un cran plus loin. Mais là c'est un autre problème un peu confus que je n'ai pas pu résoudre qui s'est pointé... VirtualBox et ses extensions ne sont donc pas trop au point si on veut utiliser toutes les astuces. [EDIT] Devant l'impossibilité de partager un disque XP avec la VM j'ai essayé de faire un partage réseau... Mandriva ou OpenSus voient le réseau et Internet, mais malgré tous mes efforts à ce jour, impossible de leur faire voir les autres machines du réseau et encore moins leurs disques, malgré le daemon Lisa, malgré Samba et autres pares-feux aux configurations ésotériques... Si quelqu'un sait... laissez un commentaire![/EDIT]

Conclusion

Les leçons à tirer sont les suivantes : Il faut utilise VirtualBox et non Virtual PC si on veut virtualiser du Linux, Il faut prendre une distrib Linux compatible avec Mono, OpenSuse semble parfaite pour ça [EDIT] Mandriva va très bien aussi [/EDIT]. Sinon pour le reste ça s'installe correctement, YaST simplifiant les choses (mais il faut comprendre comment marche YaST, notamment l'ajout d'une source d'installation, c'est là l'astuce !).

Les dernières moutures de Linux, Suse ou Mandriva sont des pâles copies de Windows XP, en moins chouette visuellement, et en plus complexe à faire marcher. Esthétiquement ça fait un peu Matrix, parfois l'interface graphique disparaît (lancement et extinction par exemple) et on voit des trucs défilés dans une console, très geek mais pas très "end user" donc. Toutefois il faut saluer les gros efforts de ces dernières années pour rendre Linux utilisable, mais on l'impression d'un truc "cheap", on voit bien que c'est gratuit quoi... Surtout quand on utilise Vista, dont on peut dire ce qu'on veut, mais quand on en a pris l'habitude, c'est autrement plus joli et plus agréable que XP ou les distribs Linux. Les fous de la customisations diront qu'ils peuvent installer des bureaux super chouettes sous Linux, c'est vrai. Mais que d'effort pour arriver à faire, de façon compliquée, ce qu'un Vista fait de base de façon simple... Reste que la concurrence Linux est une bonne chose, et les essais que je vous conte ici sont la preuve que j'attache de l'importance à cette alternative même si elle est loin de me convaincre, pour le moment, de reformater toutes mes machines sous Linux.

Mais bon, le truc ce n'était pas de tester Linux ni de donner mon avis sur la question, Linux est un monde fermé, si un "vendu" à la cause Microsoft fait des réserves c'est forcément qu'il est corrompu et donc que son avis n'a pas d'intérêt. Les Linuxiens ne lisent donc de toute façon pas les avis des non linuxiens sur Linux. Non, mon but était d'installer MONO et de faire des tests. De ce côté là c'est concluant. Juste une chose qu'il me reste à creuser, la gestion des fenêtres (l'équilavent de Windows Forms) en tout cas dans la version dont je dispose, utilise un truc vraiment très différent donc pas portable du tout. Il va falloir que je regarde s'il existe une émulation des Windows Forms sinon cela limite la compatibilité entre .NET et MONO aux libs de classes. C'est déjà pas mal, mais ça me semble trop juste. [Edit:] Il y a bien un System.Windows.Forms dans les libs installées, mais point de designer dans l'IDE.. Il semble qu'il en existe un, mais j'ai pas tout pigé comment l'obtenir, est ce un truc à part, un plugin, en tout cas ça demande d'obtenir le source de MONO par svn et de tout recompiler.. Brrr. L'esprit Linux c'est, il faut l'avouer, très très éloigné des "user experiences" de MS ! [/Edit]

Voilà pour cette petite histoire, qui, au fil des paragraphes vous aura peut-être donné l'envie de tenter l'expérience aussi, en utilisant directement les bonnes solutions et sans perdre de temps :-)

avant de lâcher mon traditionnel "Stay Tuned!" voici une petite image, ça fait toujours plaisir :

 

OpenSuse 11.0 avec MonoDevelop

 

Sous les pavés la plage... Chez moi c'est vrai, mais en plus sous la fenêtre OpenSuse il y a aussi le bureau XP avec VirtualBox (et l'icone de lancement de VS 2008 mon compagnon de tous les jours) ! Cool isn't it ?

Alors... pour d'autres aventures : Stay Tuned !

 

[EDIT:] Comme j'ai réussi à faire pareil sous Mandriva, une petite image aussi ![/EDIT]

De l'intérêt d'overrider GetHashCode()

Les utilisateurs de Resharper ont la possibilité en quelques clics de générer un GetHashCode() et d'autres méthodes comme les opérateurs de comparaison pour toute classe en cours d'édition. Cela est extrêment pratique et utile à plus d'un titre. Encore faut-il avoir essayer la fonction de Resharper et s'en servir à bon escient... Mais pour les autres, rien ne vient vous rappeler l'importance de telles fonctions. Pourtant elles sont essentielles au bon fonctionnement de votre code !

GetHashCode()

Cette méthode est héritée de object et retourne une valeur numérique sensée être unique pour une instance. Cette unicité est toute relative et surtout sa répartition dans le champ des valeurs possibles est inconnue si vous ne surchargez pas GetHashCode() dans vos classes et structures ! Il est en effet essentiel que le code retourné soit en rapport direct avec le contenu de la classe / structure. Deux instances ayant des valeurs différentes doivent retourner un hash code différent. Mieux, ce hash code doit être représentatif et générer le minimum de collisions...

Si vous utilsez un structure comme clé d'une Hashtable par exemple, vous risquez de rencontrer des problèmes de performances que vous aurez du mal à vous expliquer si vous n'avez pas conscience de ce que j'expose ici...
Je ne vous expliquerais pas ce qu'est un hash code ni une table Hashtable, mais pour résumer disons qu'il s'agit de créer des clés représentant des objets, clés qui doivent être "harmonieusement" réparties dans l'espace de la table pour éviter les collisions. Car en face des codes de hash, il y a la table qui en interne ne gère que quelques entrées réelles. S'il y a collision, elle chaîne les valeurs.
Moralité, au lieu d'avoir un accès 1->1 (une code hash correspond à une case du tableau réellement géré en mémoire) on obtient plutôt n -> 1, c'est à dire plusieurs valeurs de hash se partageant une même entrée, donc obligation de les chaîner, ce que fait la Hashtable de façon transparente mais pas sans conséquences !

Il découle de cette situation que lorsque vous programmez un accès à la table de hash, au lieu que l'algorithme (dans le cas idéal 1->1) tombe directement sur la cellule du tableau qui correspond à la clé (hash code), il est obligé de parcourir par chaînage avant toutes les entrées correspondantes... De là une dégration nette des performances alors qu'on a généralement choisi une Hashtable pour améliorer les performances (au lieu d'une simple liste qu'il faut balayer à chaque recherche). On a donc, sans trop le savoir, recréé une liste qui est balayée là où on devrait avoir des accès directs...

La solution : surcharger GetHashCode()

Il existe plusieurs stratégies pour générer un "bon" hash code. L'idée étant de répartir le plus harmonieusement les valeurs de sorties dans l'espace de la table pour éviter, justement, les collisions de clés. Ressortez vos cours d'informatique du placard, vous avez forcément traité le sujet à un moment ou un autre ! Pour les paresseux et ceux qui n'ont pas eu de tels cours, je ne me lancerais pas dans la théorie mais voici quelques exemples d'implémentations de GetHashCode() pour vous donner des idées :

La méthode "bourrin"

Quand on ne comprends pas forcément les justifications et raisonnements mathématiques d'un algorithme, le mieux est de faire simple, on risque tout autant de se tromper qu'en faisant compliqué, mais au moins c'est facile à mettre en oeuvre et c'est facile à maintenir :-)

Imaginons une structure simple du genre :

public struct MyStruct
{
   
public int Entier { get; set; }
   
public string Chaine { get; set; }
   
public DateTime LaDate { get; set; }
}

Ce qui différencie une instance d'une autre ce sont les valeurs des champs. Le plus simple est alors de construire une "clé" constituée de toutes les valeurs concaténées et séparées par un séparateur à choisir puis de laisser le framework calculer le hash code de cette chaîne. Toute différence dans l'une des valeurs formera une chaine-clé différente et par conséquence un hash code différent. Ce n'est pas super subtile, mais ça fonctionne. Regardons le code :

public string getKey()
{
return Entier + "|" + Chaine + "|" + LaDate.ToString("yyyyMMMddHHmmss"); } public override int GetHashCode() {return getKey().GetHashCode(); }

J'ai volontairement séparé la chose en deux parties en créant une méthode getKey pour pouvoir l'afficher.

La sortie (dans un foreach) de la clé d'un exemple de 5 valeurs avec leur hash code donne :

1|toto|2008juil.11171952 Code: -236695174
10|toto|2008juil.11171952 Code: -785275536
100|zaza|2008juil.01171952 Code: -684875783
0|kiki|2008sept.11171952 Code: 888726335
0|jojo|2008sept.11171952 Code: 1173518366 

La méthode Resharper

Ce merveilleux outil se propose de générer pour vous la gestion des égalités et du GetHashCode, laissons-le faire et regardons le code qu'il propose (la structure a été au passage réécrite, les propriétés sont les mêmes mais elles utilisent des champs privés) :

D'abord le code de hachage :

public override int GetHashCode()
{
   unchecked
   {
      int result = entier;
      result = (result*397) ^ (chaine !=
null ? chaine.GetHashCode() : 0);
      result = (result*397) ^ laDate.GetHashCode();
      return result;
   }
}

On voit ici que les choix algorithmiques pour générer la valeur sont un peu plus subtiles et qu'ils ne dépendent pas de la construction d'une chaîne pour la clé (ce qui est consommateur de temps et de ressource).

Profitons-en pour regarder comment le code gérant l'équalité a été généré (ainsi que le support de l'interface IEquatable<MyStruct> qui a été ajouté à la définition de la structure)  - A noter, la génération de ce code est optionnel - :

public static bool operator ==(MyStruct left, MyStruct right)
{
return left.Equals(right); }

public static bool operator !=(MyStruct left, MyStruct right)
{
return !left.Equals(right); }

public bool Equals(MyStruct obj)
{ return obj.entier == entier && Equals(obj.chaine, chaine) && obj.laDate.Equals(laDate); }

public override bool Equals(object obj)
{
   
if (obj.GetType() != typeof(MyStruct)) return false;
    return Equals((MyStruct)obj);
}

Bien que cela soit optionel et n'ait pas de rapport direct avec GethashCode, on notera l'intérêt de la redéfinition de l'égalité et des opérateurs la gérant ainsi que le support de IEquatable. Une classe et encore plus une structure se doivent d'implémenter ce "minimum syndical" pour être sérieusement utilisables. Sinon gare aux bugs difficiles à découvrir (en cas d'utilisation d'une égalité même de façon indirecte) !

De même tout code correct se doit de surcharger ToString(), ici on pourrait simplement retourner le champ LaChaine en supposant qu'il s'agit d'un nom de personne ou de chose, d'une description. Tout autre retour est possible du moment que cela donne un résultat lisible. Ce qui est très pratique si vous créez une liste d'instances et que vous assignez cette liste à la propriété DataSource d'un listbox ou d'une combo... Pensez-y !

Conclusion

Créer des classes ou des structures, si on programme sous C# on en a l'habitude puisque aucun code ne peut exister hors de telles constructions. Mais "bien" construire ces classes et structures est une autre affaire. Le framework propose notamment beaucoup d'interfaces qui peuvent largement améliorer le comportement de votre code. Nous avons vu ici comment surcharger des méthodes héritées de object et leur importance, nous avons vu aussi l'interface IEquatable. IDisposable, INotityPropertyChanged, ISupportInitialize, et bien d'autres sont autant d'outils que vous pouvez (devez ?) implémenter pour obtenir un code qui s'intègre logiquement au framework et en tire tous les bénéfices.

Bon dev, et Stay Tuned !

La bombe Parallèle ! PLINQ, et PCP Parallel Computing Platform

Le framework .NET n'en finit pas d'innover ! La bombe du jour c'est PLINQ et PCP.

Vous ne connaissez pas encore ? Normal, ce n'est même pas encore sorti, juste en preview...

Pour résumer en un mot : Révolutionnaire. Comme LINQ.

L'objectif

Vous l'avez certainement remarqué comme tout geek qui se respecte, depuis quelques années nos amis fondeurs ne se battent plus à coup de Ghz pour promouvoir leurs microprocesseurs... Les gammes se multiplient et surtout les coeurs ! Et le temps où le concepteur d'un soft trop lent pouvait toujours dire "je suis en avance c'est tout, avec la dernière génération de processeur mon soft est super rapide!", ce temps là est fini, révolu.

Aujourd'hui un soft lent sur un dual core sera peut être même encore plus lent sur un quad core d'une autre série (il y a plein de séries de processeurs on ne s'y retrouve d'ailleurs plus pour choisir...).

Donc, pour qu'un soft soit plus rapide, inutile de compter sur la prochaine génération de processeur qui doublera la fréquence d'horloge. Aujourd'hui on double le nombre de coeurs, c'est tout. un, puis deux, les quatre coeurs se banalisent, Intel avait annoncé (le tiendront ils) que d'ici peu de temps on atteindrait les 100 coeurs... Mais pour tirer partie de tous ces coeurs finalement pas si rapide, une seule solution le parallélisme !

Parallèlisme et programmation

Seulement voilà, pour bénéficier de tous ces coeurs il faut programmer selon un mode parallèle. Le plus "simple" consiste à faire du multi-threading en laissant l'OS répartir les threads entre les coeurs, mais pour plein de raison (je vous laisse y réfléchir) cela n'est pas forcément optimal. Un simple exemple : une tâche un peu lourde, vous n'avez pas besoin de 50 threads, vous avez une grosse bonne boucle qui traite une image pour faire du morphing par exemple. Allez programmez ça en créant plusieurs threads ! Si vous y arriver sans y engloutir des semaines de mise au point, je veux bien manger mon chapeau (il faudra fournir le chapeau je n'en porte pas, et si vous le fournissez, ça ne sera pas le mien, donc je ne le mangerais pas. On ne m'a pas si facilement...) !

Car en effet, multiplier les threads n'est pas toujours réalisable, en tout cas simplement. Découper une tâche en sous tâches parallélisables est en réalité tellement complexe, surtout de façon générique, que les langages et les OS sont bien à la traîne par rapport aux microprocesseurs. A la traîne ? Pas tous... Car sous .NET il va falloir compter avec PLINQ et PCP !

PLINQ et PCP

En très gros il s'agit d'extensions du framework .NET qui permet de paralléliser à peu près tout traitement sans presque aucune programmation particulière. Oui vous avez bien lu...

Ainsi, PLINQ est une extension parallèle de LINQ. Une requête LINQ comme :

var result = from c in liste where c.champ==5 orderby c.autreChamp select c; // version simple

devient en parallèle :

var result = from c in liste.AsParallel() where c.champ==5 orderby c.autreChamp select c; // parallélisée

Et c'est tout ! D'un seul coup la requête devient parallèle et s'executera sur tous les coeurs disponibles à la fois, le boulot étant découpé, les threads créés, et tout le reste, de façon automatique par PLINQ. Un rêve ? Non, pas besoin de se pincer, c'est déjà en preview à télécharger ! (voir en fin de billet).

Bien entendu il existe beaucoup de variantes et une syntaxe appropriée pour, par exemple, modifier le nombre de coeurs utilisés et plein d'autres nuances subtiles.

La parallélisation de LINQ est une pure merveille, mais il y a aussi PCP qui est le framework plus généraliste permettant de paralléliser des foreach, des for, etc, juste avec un mot clé. Il existe là aussi toute une panoplie de méthodes qu'il va falloir découvrir. Mais vous avouerez que le bond en avant est aussi fantastique que celui de LINQ par exemple. Je ne pense sincèrement pas que quiconque ayant utilisé LINQ ne voudra dans l'avenir d'un langage ne supportant pas une telle extension. Il y a eu un avant LINQ, il y aura forcément un après LINQ, mais on ne traversa pas le temps sans faire référence à cette nouvelle ère, au sens premier, le clou d'airain planté par le prêtre dans la mur du temple de la Rome antique pour marquer un événement crucial d'une telle importance qu'une nouvelle série d'années commençait.

PLINQ et PCP marqueront de la même façon une nouvelle ère.

Faire joujou ?

Je reconnais bien le geek qui sommeille en vous ! Oui c'est possible ! Il existe une CTP sortie en juin et qui est téléchargeable sur le site MSDN consacré à la programmation parallèle. Vous y trouverez de nombreuses informations qui complèteront facilement la très rapide présentation faite ici.

Le site : http://msdn.microsoft.com/fr-fr/concurrency/

... Vous êtes encore là ? Mais foncez je vous dit, c'est l'éclate du geek ce truc ! (mais pensez à revenir hein.. Stay Tuned !) Wink

[EDIT: lire aussi ce billet plus récent qui donne l'adresse d'un article sur PFX et de plusieurs Webcasts]

Quizz C#. Vous croyez connaître le langage ? et bien regardez ce qui suit !

C# est un langage merveilleux, plein de charme... et de surprises !

En effet, s'écartant des chemins battus et intégrant de nombreuses extensions comme Linq aujourd'hui ou les méthodes anonymes hier, il est en perpétuelle évolution. Mais ces "extensions" transforment progressivement un langage déjà subtile à la base (plus qu'il n'y paraît) en une jungle où le chemin le plus court n'est pas celui que les explorateurs que nous sommes auraient envisagé...

Pour se le prouver, 6 quizz qui vous empêcheront de bronzer idiot ou de vous endormir au boulot !

Un petit projet exemple avec les questions et les réponses sera téléchargeable en fin d'article demain...
[EDIT 4-7-08] Le lien se trouve désormais en fin d'article. Le code source contient les réponses au quizz.

Quizz 1

Etant données les déclarations suivantes :

class ClasseDeBase
{
   
public virtual void FaitUnTruc(int x)
    {
Console.WriteLine("Base.FaitUnTruc(int)"); }
}

class ClasseDérivée : ClasseDeBase
{
   
public override void FaitUnTruc(int x)
   { Console.WriteLine("Dérivée.FaitUnTruc(int)"); }

   public void FaitUnTruc(object o)
  {
Console.WriteLine("Dérivée.FaitUnTruc(object)"); }
}

Pouvez-vous prédire l'affiche du code suivant et expliquer la sortie réelle :

    ClasseDérivée d = new ClasseDérivée();
    int i = 10;
    d.FaitUnTruc(i);

Gratt' Gratt' Gratt'.....

Quizz 2

Pouvez-vous prédire l'affichage de cette séquence et expliquer l'affichage réel ?

double d1a = 1.00001;
double d2a = 0.00001;
Console.WriteLine((d1a - d2a) == 1.0);

double d1b = 1.000001;
double d2b = 0.000001;
Console.WriteLine((d1b - d2b) == 1.0);

double d1c = 1.0000001;
double d2c = 0.0000001;
Console.WriteLine((d1c - d2c) == 1.0);

Je vous laisse réfléchir ...

Quizz 3

Toujours le même jeu : prédire ce qui va être affiché et expliquer ce qui est affiché réellement... c'est forcément pas la même chose sinon le quizz n'existerait pas :-)

List<Travail> travaux = new List<Travail>();
Console.WriteLine("Init de la liste de delegates");
for (int i = 0; i < 10; i++)
{ travaux.Add(
delegate { Console.WriteLine(i); }); }

Console.WriteLine("Activation de chaque delegate de la liste");

foreach (Travail travail in travaux) travail();

 Les apparences sont trompeuses, méfiez-vous !

Quizz 4

Ce code compile-t-il ?

public enum EtatMoteur { Marche, Arrêt }

public static void DoQuizz()
{
   
EtatMoteur etat = 0.0;
   
Console.WriteLine(etat);
}

Quizz 5

 Etant données les déclarations suivantes :

private static void Affiche(object o)
{
Console.WriteLine("affichage <object>"); }

private static void Affiche<T>(params T[] items)
{
Console.WriteLine("Affichage de <params T[]>"); }

 Pouvez-vous prédire et expliquer la sortie de l'appel suivant :

  Affiche("Qui va m'afficher ?");

 Je sens que ça chauffe :-)

Quizz 6

 Etant données les déclarations suivantes :

 delegate void FaitLeBoulot(); private static FaitLeBoulot FabriqueLeDélégué()
{
  
Random r = new Random();
  
Console.WriteLine("Fabrique. r = "+r.GetHashCode());

  
return delegate {
                           
Console.WriteLine(r.Next());
                           
Console.WriteLine("delegate. r = "+r.GetHashCode());
                         };

}

 Quelle sera la sortie de la séquence suivante :

    FaitLeBoulot action = FabriqueLeDélégué();
    action();
   
action();

 

Conclusion

C# est un langage d'une grande souplesse et d'une grande richesse. Peut-être qu'à devenir trop subtile il peut en devenir dangereux, comme C++ était dangereux car trop permissif.

Avec C#, même de très bons développeurs peuvent restés interloqués par la sortie réelle d'une séquence qui n'a pourtant pas l'air si compliquée.
Ses avantages sont tels qu'il mérite l'effort de compréhension supplémentaire pour le maîtriser réellement, mais tout développeur C# (les moyens comme ceux qui pensent être très bons!) doit être mis au moins une fois dans sa vie face à un tel quizz afin de lui faire toucher la faiblesse de son savoir et les risques qu'il prend à coder sans vraiment connaître le langage.

Comme toujours l'intelligence est ce bel outil qui nous permet de mesurer à quel point nous ne savons rien.
Nous sommes plutôt habitués à envisager cette question sous un angle métaphysique, le développement nous surprend lorsque lui aussi nous place face à cette réalité...

Pour le projet exemple dont le source contient les réponses cliquez sur le lien en fin de billet.

So (et plus que jamais!) .. Stay Tuned !

Quizz.rar (103,41 kb)

Les class Helpers, enfer ou paradis ?

Class Helpers 

Les class helpers sont une nouvelle feature du langage C# 3.0 (voir mon billet et mon article sur les nouveautés de C# 3.0).

Pour résumer il s'agit de "décorer" une classe existante avec de nouvelles méthodes sans modifier le code de cette classe, les méthodes en questions étant implémentées dans une autre classe.

Le principe lui-même n'est pas récent puisque Borland l'avait "inventé" pour Delphi 8.Net (la première version de Delphi sous .Net) afin de permettre l'ajout des méthodes de TObject à System.Object (qu'ils ne pouvaient pas modifier bien entendu) afin que la VCL puisse être facilement portée sous .Net. Borland avait déposé un brevet pour ce procédé (ils le prétendaient à l'époque en tout cas) et je m'étonne toujours que Microsoft ait pu implémenter exactement la même chose dans C# sans que cela ne fasse de vagues. Un mystère donc, mais là n'est pas la question.

Mauvaises raisons ?

Les deux seules implémentations de cet artifice syntaxique que je connaisse (je ne connais pas tout hélas, si vous en connaissez d'autres n'hésitez pas à le dire en commentaire que l'on puisse comparer) sont donc celle de Borland dans Delphi 8 pour simplifier le portage de la VCL sous .NET et celle de Microsoft dans C# pour faciliter l'intégration de Linq dans le langage.

Deux exemples, deux fois pour la même raison un peu spécieuse à mes yeux : simplifier le boulot du concepteur du langage pour supporter de nouvelles features. Deux fois une mauvaise raison à mes yeux, un peu trop puristes peut-être, qui pensent qu'un élément syntaxique doit se justifier d'une façon plus forte, plus théorique que simplement "pratique".

Résultat, je me suis toujours méfié des class helpers car leur danger est qu'un objet se trouve d'un seul coup affublé de méthodes "sorties d'un chapeau", c'est à dire qu'il semble exposer des méthodes publiques qui ne sont nulles part dans son code. J'ai horreur de ce genre de combines qui, à mon sens, favorise un code immaintenable. Si j'adore la magie, à voir ou à pratiquer, je la déteste lorsque je porte ma casquette d'informaticien... J'ai donc toujours conseillé la plus grande circonspection vis à vis de cet artifice syntaxique, que ce soit à l'époque (déjà lointaine.. le temp passe!) où je faisais du Delphi que maintenant sous C# qui vient d'ajouter cette fioriture à sa palette.

Jusqu'à aujourd'hui, faute d'avoir trouver une utilisation intelligente des class helpers qui ne puissent être mise en oeuvre plus "proprement", les class helpers étaient à mon sens plutôt à classer du côté enfer que paradis. Interface et héritage m'ont toujours semblé une solution préférable à ces méthodes fantomes.

Trouver une justification

Mais il n'y a que les imbéciles qui ne changent pas d'avis, n'est-ce pas... Et je cherche malgré tout toujours à trouver une utilité à un outil même si je le trouve inutile de prime abord, réflexe d'ingénieur qui aime trouver une place à toute chose certainement.
J'ai ainsi essayé plusieurs fois dans des projets de voir si les class helpers pouvaient rendre de vrais services avec une réelle justification, c'est à dire sans être un cache misère ni une façon paresseuse de modifier une classe sans la modifier tout en la modifiant...

Comme j'ai enfin trouvé quelques cas (rares certes) dans lesquels les class helpers me semblent avoir une justification pratique, je me suis dit que vous en toucher deux mots pourraient éventuellement faire avancer votre propre réflexion sur le sujet (même si j'ai bien conscience que je dois être assez seul à me torturer la cervelle pour trouver absolument une utilité aux class helpers :-) ).

Bref, passons à la pratique.

Un cas pratique

Premier cas, les chaînes de caractères. Voilà un type de données vieux comme la programmation qui pourrait être un bon candidat à l'exploitation des possibilités des class helpers. En effet, hors de question de dériver la classe System.String et encore moins de pouvoir modifier le mot clé "string" de C#. Pourtant nous utilisons très souvent les mêmes fonctions "personnelles" sur les chaînes de caractères d'un même projet.

Par exemple, j'ai pour principe que les chaînes exposées par une classe (propriétés de type string donc) ne soient jamais à null. De ce fait, dans toutes les classes qui exposent un string, j'ai, dans le setter, la même séquence qui change l'éventuel null de value à string.Empty. C'est assez casse-pieds à répéter comme ça mécaniquement dans toutes les propriétés string de toutes les classes.

Et là, pas de possibilité de faire supporter une interface à System.String, ni de la dériver comme je le disais plus haut. C'est ici que les class helpers peuvent trouver une première justification pratique pour le développeur en dehors d'avoir facilité la vie à MS pour implémenter Linq.

Prenons le code suivant que nous plaçons dans une unité "Tools" de notre projet :

public static class Utilities
{

     public static string NotNullString(this string s)
      { 
return !string.IsNullOrEmpty(s) ? s.Trim() : string.Empty; }
...
 }

La classe Utilities est une classe statique qui contient tous les petits bouts de code utilisables dans tout le projet. Parmi ces méthodes, on voit ici l'implémentation du class helper "NotNullString". D'après cette dernière, la méthode ne sera visible que sur les instances de la classe "string". Le code lui-même est d'une grande simplicité puisqu'il teste si la chaîne en question est vide ou non, et, selon le cas, retourne string.Empty ou bien un Trim() de la chaîne. J'aime bien faire aussi systématiquement un Trim() sur toutes les propriétés string, je trouve ça plus propre surtout si on doit tester des équalités et que les chaînes proviennent de saisies utilisateurs.

Dans la pratique il suffira maintenant n'importe où dans notre projet d'écrire la chose suivante pour être certain qu'à la sortie de l'affectation la chaîne résultante ne sera jamais nulle et qu'elle ne comportera jamais d'espaces en trop en début ou en fin :

public string BusinessID
{  
get { return _BusinessID; }
    
set if (value != _BusinessID)
            
{ _BusinessID = value.NotNullString().ToUpperInvariant();
                
DoChange("BusinessID");
            
}
         
}
}

On voit ici une propriété string "BusinessID" qui, dans son setter, utilise désormais la nouvelle méthode fantome de la classe string... En fin de séquence nous sommes certains que _BusinessID est soit vide, soit contient un chaîne sans espace de tête ou de queue (et qu'elle est en majuscules, en plus dans cet exemple).

Voici donc une première utilisation "intelligente" des class helpers, la décoration d'une classe du framework (ou d'une lib dont on n'a pas le code source) pour lui ajouter un comportement, éventuellement complexe, dont on a souvent l'utilité dans un projet donné.

On pourrait penser ainsi à une fonction "ProperCase" qui passe automatiquement la casse d'une chaîne en mode "nom de famille", c'est à dire première lettre de chaque mot en majuscule, le reste en minuscule, ou à bien d'autres traitements qu'on aurait besoin d'appliquer souvent à des chaînes dans un projet.

Encore un autre cas

Dans la même veine, une application doit souvent manipuler des données issues d'une base de données et les modifier (en plus d'en insérer de nouvelles). On le sait moins (mais on s'aperçoit vite quand cela bug!) que le framework .NET possède, pour les dates par exemple, ses propres mini et maxi qui ne sont pas compatibles à toutes les bases de données, notamment SQL Server. Si vous attribuer à une date la valeur DateTime.MinValue et que vous essayez d'insérer cette dernière dans un champ Date d'une base SQL Server vous obtiendrez une exception de la part du serveur : la date passée n'est pas dans la fourchette acceptée par le serveur.
Dommage... DateTime.MinValue est bien pratique...

On peut bien entendu fixer une constante dans son projet et l'utiliser en place et lieu de DateTime.MinValue. Voci un exemple pour MaxValue (le problème étant le même):

DateTime MaxiDate = new DateTime(3000, 1, 1, 0, 0, 0);

Il suffira donc d'utiliser MaxiDate à la place de DateTime.MaxValue. La date considérée comme "maxi" est ici arbitraire (comme on le ferait pour la date mini) et est choisie pour représenter une valeur acceptable à la fois pour l'application et pour la base de données. Ici on notera que je prépare le terrain pour le bug de l'an 3000. Moins stupide qu'un coboliste et le bug de l'an 2000, vous remarquerez que je me suis arrangé ne plus être joignable à la date du bug et que mes héritiers sont aussi à l'abris de poursuites judiciaires pour quelques siècles :-)

L'utilisation d'une constante n'a rien de "sale" ou de "moche", c'est un procédé classique en programmation, même la plus éthérée et la plus sophistiquée. Toutefois, Puisque DateTime existe, puisque DateTime est un type complexe (une classe) et non pas un simple emplacement mémoire (comme les types de base en Pascal par exemple), puisque cette classe expose déjà des méthodes, dont Min et MaxValue, il serait finalement plus "linéaire" et plus cohérent d'ajouter notre propre MaxValue à cette dernière en place et lieu d'une constante.

Encore un bon exemple d'utilisation des class helpers. Ici nous homogénéisons notre style de programmation en évitant le mélange entre méthodes de DateTime et utilisation d'une constante. De plus, en ajoutant une méthode spécifique à DateTime, celle-ci sera visible par Intellisense come membre de DateTime, ce qui ne serait pas le cas de la constante. 

Dans la même classe Utilities nous trouverons ainsi :

public static DateTime SQLMaxValue(this DateTime d)
{
return new DateTime(3000, 1, 1, 0, 0, 0); }

 Et nous voici avec la possibilité d'écrire : MaDate = DateTime.SQLMaxValue();

Conclusion

Enfer ou paradis ? Les class helpers, comme tout artifice syntaxique peuvent se ranger dans les deux catégories, ils ne sont qu'un outil à la disposition du développeur. Un simple marteau peut servir à bâtir une maison où vivre heureux ou bien à assassiner sauvagement son voisin...  Les objets inanimés n'ont pas de conscience, ou plutôt, si, ils en ont une : celle de celui qui les utilise. A chacun d'utiliser les outils que la technologie humaine met à sa disposition pour créer un enfer ou un paradis...

Techniquement, je n'enfoncerai pas les portes ouvertes en donnant l'impression d'avoir découvert une utilisation miraculeuse des class helpers. Cela serait stupide, puisque justement cette syntaxe a été créée par Borland puis MS pour justement décorer des classes dont on ne possède pas le source (pas d'ajout de méthode ni d'interface) et qui ne sont pas héritables. En ajoutant des class helpers à string ou DateTime nous ne faisons rien d'autre que d'utiliser les class helpers exactement en conformité avec ce pour quoi ils ont été créés.

L'intérêt de ce billet se situe alors dans deux objectifs : vous permettre de réfléchir à cette nouveauté de C#3.0 que vous ne connaissez peut-être pas ou mal et vous montrer comment, en pratique, cela peut rendre des services non négligeables.

Si l'un ou l'autre de ces objectifs a été atteint, vous tenez alors votre récompense d'avoir tout lu jusqu'ici, et moi d'avoir écrit ce billet :-)

Stay tuned !

[EDIT: voir cet autre billet plus récent sur les class helpers]

ADO.Net Entity Framework - une série d'articles en US

Stefan Cruysberghs a publié (en anglais) une série de trois articles (récents) sur l'Entity Framework qui proposent un tour d'horizon assez intéressant de cette technologie.
En voici le sommaire (avec accès direct aux articles): 

Part 2 :

Part 3 :

Si vous préférez accéder aux trois articles séparément :

Bonne lecture !

Présentation des différentes facettes de LINQ (article à télécharger)

Le voilà enfin ! [Updated ! Version 1.1 en ligne]

un PDF de 32 36 pages et 5 6 projets exemples sous VS 2008 pour vous présenter les différentes facettes de LINQ. Je n'en voyais plus le bout de cet article ! Non par lassitude, bien au contraire, mais parce que LINQ est d'une incroyable richesse et que je voulais vous en dire la maximum.

Sans entrer dans les détails trop techniques de la syntaxe (la doc Microsoft est très complète et n'a nul besoin d'une redite), cet article présente le pourquoi et le comment de LINQ au travers d'explications et d'exemples de code.

  • LINQ to Objects
  • LINQ to SQL
  • LINQ to Dataset
  • LINQ to XML
  • LINQ to Entities

Sans prétendre que toutes ces versions de LINQ n'auront plus de secret pour vous après avoir lu l'article, vous en saurez certainement plus pour mieux comprendre pourquoi il y a eu un avant LINQ et qu'il va y avoir un après LINQ...

Pour télécharger l'article cliquez ici !

Avant de lire cet article il est préférable de connaître les nouveautés syntaxiques de C# 3.0, si ce n'est pas votre cas vous pouvez télécharger mon précédent article.

Pour la liste de tous mes billets sur LINQ cliquez ici.

Note de la version 1.1 : table des matières ajoutée + plus de détails sur Linq to Entities et un projet utilisant la bêta 3.

Entity Framework Application Patterns. Résumé de la conférence DAT303 Teched 2007

Hier je vous ai résumé la conférence DAT201 qui présentait l'Entity Framework. Aujourd'hui je vous parlerai plus brièvement d'une autre conférence. Cette brièveté du résumé ne doit pas vous induire en erreur : la conférence DAT303 de Pablo Castro, Technical Lead, est peut-être la meilleure à laquelle j'ai assitée.

D'abord Pablo est un jeune gars sympa. Ensuite malgré un fort accent cubain ou mexicain il a soutenu une conf à un rythme d'enfer tout en étant d'une grande clareté, une vraie conf technique comme je les aime, faite par un passionné qui connaît son affaire. Enfin, cette conférence dépassait le cadre de la présentation générale pour parler vrai et pratique. Là, Pablo m'a appris des choses sur l'Entity Framework. Des trucs pas simples à découvrir tout seul, une conférence qui fait vraiment gagner du temps et de la compétence. Merci Pablo !

Si le résumé sera court c'est que 95% de la conférence de Pablo était basée sur du code. Vous imaginez bien qu'il n'était pas possible de prendre le tout en sténo dans la pénombre de la salle de conf... J'ai revisionné aujourd'hui la conf (puisque toutes les sessions principales ont été filmées), et franchement même assis dans mon bureau à l'aise il nétait pas possible de noter la totalité des manips, toutes essentielles. J'ai d'ailleurs demandé à Pablo s'il pouvait avoir la gentillesse de m'envoyer le code source de ces démos, et s'il accèpte je les mettrais en téléchargement ici.

Donc de quoi s'agissait-il ?

Bien entendu de l'Entity Framework, depuis mon billet d'hier vous devez savoir de quoi il s'agit (sinon foncez lire ce dernier, ne vous inquiétez je ne bouge pas, vous pourrez revenir ici plus tard :-) ).

Mais il n'était plus question ici de parler en général ou même en détail du fonctionnement de EF, il s'agissait de voir comment s'en servir "en vrai" dans trois conditions :

  • En 2 tiers
  • Dans une application Web
  • Au sein d'une architecture 3-tiers

En effet, le principal problème de toute surcouche est de manger un peu plus de CPU et de mémoire que la couche du dessous. C'est la règle en informatique, plus c'est pratique et puissant, plus la machine doit pédaler. Et même si EF est très efficace et très optimisé, ce qu'il fait en plus possède forcément un coût. Il est donc essentiel de pouvoir maîtriser ce coût en faisant des économies là où cela est possible.

La bonne nouvelle c'est que EF le prévoit et que la mise en oeuvre est simple. La moins bonne nouvelle c'est que la chose devient un poil plus "mystique" et qu'il faut réfléchir un peu plus. Mais un informaticien paresseux des neurones est soit en chômeur en puissance, soit un ex-informaticien au RMI...

Change tracking et Identity resolution

Deux mécanismes peuvent être économisés (et les ressources CPU/RAM qui vont avec) : Le change tracking et l'identity resolution. Le premier, détection des changements, permet à l'EF de savoir quels objets ont été modifiés pour savoir comment appliquer les mises à jour à la base de données. Si certaines entités (ou grappes d'entités) ne seront pas modifiées on peut alors se passer du mécanisme de détection des changements... L'identity resolution, ou résolution des identités est utilisée pour identifier de façon formelle toutes les instances des entités. Il est en effet primordial pour le système de savoir à quel(s) enregistrement(s) de la base de données corespond(ent) l(es) entité(s) sinon il est impossible d'envisager des fonctions comme le refresh, l'update ou le delete...

Heureusement EF est très bien conçu et il est possible de stopper ces mécanismes là où on le désire. Ce court billet (qui devient déjà long...) n'entrera pas dans les détails, je prépare un article sur la question, format plus adapté à un long exposé technique avec exemples de code.

Des entités qui passent les frontières... 

Un autre problème se pose, notamment dans les applications en multi-tiers : lorsqu'une instance d'entité est passée en dehors du système, EF en perd la trace... Imaginons un serveur applicatif qui utilise EF pour accéder à une base de données afin d'offrir des services de type WCF (ex remoting, pour simplifier) ou même des services Web. Les changements dans les objets ont lieu en dehors même de la machine qui utilise EF. Le lien est cassé et si l'objet revient au serveur applicatif qui doit en retour mettre à jour la base de données, EF ne le connaîtra plus et ne pourra pas l'intégrer à son mécanisme de mise à jour de la base.

Là encore EF permet de contourner le problème. La façon de le faire sera aussi décrite dans le papier en cours de préparation, soyez patients !

Compilation LINQ 

Enfin, Pablo a montré comment économiser des ressources du côté de LINQ cette fois-ci en utilisant la possibilité de compiler la requête sous la forme d'un delegate qu'il suffit ensuite d'appeler. L'utilisation des expressions Lambda et l'utilisation des interfaces idoines permettent même d'avoir une requête compilée mais paramétrique dont le résultat peut être réutilisé dans une autre requête LINQ. Pas de magie, et peu de code à taper pour réaliser tout cela. En revanche, une fois encore, cela ne s'improvise pas si on ne connaît pas l'astuce.

Conclusion

Une conférence riche, dense et instructive. Je pourrais en parler encore des pages entières mais sans la contrepartie du code exemple et des explications qui vont avec ce billet deviendrait vite ennuyeux. Je préfère m'arrêter là et réserver les détails d'implémentation pour l'article que je prépare sur ce sujet. Bien entendu il sera annoncé ici et sera téléchargeable gratuitement comme d'habitude.

Une dernière chose, Pablo nous a fait voir un peu Astoria, un procédé permettant d'exposer une (partie d'une) base de données en HTTP dans un formalisme XML. Une sorte de Web service généré automatiquement pour chaque classe et qui, par des GET ou des POST permet d'accéder aux données mais aussi de mettre à jour les données dans un mécanisme de type accès à une page Web ! Je n'ai pas eu le temps de creuser la question ni de faire tourner de bêta de Astoria, mais soyez sûrs que dès que j'en aurais fini avec mon tri de toutes les conférences des TechEd, Astoria sera au programme !

Plein de nouveautés encore à venir, so, stay tuned !

 

Entity Framework - Résumé de la session DAT201 aux TechEd 2007

Cette conférence était l'une de celle que j'attendais le plus. De ce que j'avais déjà vu de LINQ et de ce qui tournait autour de cette nouveauté du framework 3.5 je savais qu'il y avait là un joyau... Les quelques essais que j'avais faits avec la Bêta de VS 2008 n'avaient qu'attisé ma curiosité. Je désirais donc à tout prix voir la session DAT 201 : Entity Framework Introduction pour refaire le tour complet de la technologie pour être sûr de ne pas passer à côté d'un élément essentiel.

Et c'est encore mieux que ce que je pensais. C'est une pure merveille, une avancée aussi spectaculaire que la naissance du framework .NET lui-même. Rien de moins.

L'Entity Framework

La session DAT 201, présentée par Carl Perry, Senior Program Manager Lead chez Microsoft, se voulait une introduction, un tour du propriétaire de l'Entity Framework. Mais kesako ? Le "cadre de travail pour entité" en français. Avec ça vous êtes bien avancé !

Pour faire le plus court possible disons que l'EF (Entity Framework) est une nouvelle couche d'abstraction se posant sur ADO.NET et permettant tout simplement d'accéder aux données de façon totalement objet et surtout dans le respect d'un schéma conceptuel et non plus en travaillant avec la base de données, son schéma physique et son SQL.

Est-ce que vous êtes plus avancé ? j'ai comme un doute... Mais les choses vont s'éclaircir avec la suite de ce billet.

Pour mieux comprendre la différence ne serait-ce que dans le code, comparons les deux types d'accès aux données, ADO.NET 2.0 et EF.

L'accès aux données de ADO.NET 2.0

Voici un code typique d'accès aux données via ADO.NET 2.0 tel qu'on en voit dans toutes les applis ou presque :

On retrouve toute la chaîne d'opérations usuelles, de la création et l'ouverture de la connexion jusqu'à l'itération dans le resultset retournée par le DataReader suite à l'envoi d'une Command dont le contenu est du pur SQL exprimé dans le dialect spécifique de la base de données cible.

Cette méthode est puissante, souple, mais elle possède de nombreux pièges. Par exemple l'accès aux données du DataReader s'effectue par les noms de champ. Une simple coquille à ce niveau et le bug ne sera détecté qu'à l'exécution, peut-être dans 6 mois chez le client quant il utilisera cette fonction particulière du logiciel. Le coût de cette coquille peut être énorme s'il s'agit d'un logiciel diffusé en grand nombre (hot line de maintenance, détection du bug - pas toujours simple, création d'une mise à jour - les sources elles-mêmes ont bougé, problème de versionning, diffusion de la mise à jour et découverte de nouveaux problèmes - incompatibilités d'une des modifications, etc, etc). J'arrête là le scénario catastrophe car selon les lois de murphy je suis encore en dessous de la réalité, et vous le savez comme moi...

Le pire c'est que je n'ai relevé qu'une seule des erreurs possibles, ce code possède bien d'autres pièges qui agiront comme des aimants à bugs ! Un autre exemple, toujours sur l'accès aux données lui-même : Il n'y a aucun contrôle de type. Je passe sur les conséquences que vous pouvez imaginer dans certains cas.

Car il y a plus grave encore : pour obtenir les données il faut taper un ordre SQL spécifique à la base cible... D'une part cela n'est pas portable, d'autre part ce n'est que du texte pour le compilateur. On écrirait "coucou" à la place de l'ordre SELECT ça compilerait tout aussi bien.

Je ne vous refais pas l'énumération des problèmes en cas de coquille (fort probable) dans une longue chaîne SQL. Je vous épargnerais aussi la liste des ennuis si l'admin de la base de données change le nom d'un champ ou le type de l'un d'entre eux ou s'il réorganise une table en la coupant en 2 ou 3 autres pour des raisons d'optimisation. C'est tout le soft qui devra être corrigé à la main sans rien oublier.

Bref, aussi génial que soit le framework .NET, aussi puissant que soit ADO.NET, on en était encore au même style de programmation des accès aux données que ce que ADO Win32 ou dbExpress chez Borland avec Delphi permettaient. Une logique proche, collée devrais-je dire, au schéma physique de la base de données et totalement inféodée à son dialect SQL, le tout avec des accès non typés aux données.

Certes ADO.NET 2.0 permettait d'aller un cran plus loin avec la génération des DataSet typés sous VS 2005. Une réelle avancée, mais qui n'était qu'un "arrangement" avec le modèle physique.

L'accès aux données avec ADO.NET Entity Framework

Dans cet exemple (qui fait exactement la même chose que le précédent) et dont nous comprendrons plus loin les détails on peut distinguer plusieurs choses :

  1. Il n'y a plus de SQL, il a disparu.
  2. On ne s'occupe plus de l'ouverture et de la fermeture de la connexion ni même de sa création.
  3. Les données sont interrogées en C#, bénéficiant d'Intellisense et du contrôle syntaxique.
  4. On itère à travers une liste d'objets dont les propriétés sont typées.

Non, ne cherchez pas l'astuce de ce côté là... si si, vous vous dîtes "le SQL a été tapé ailleurs il est simplement caché maintenant". Non. Dans cette façon d'accéder aux données personne n'a tapé de SQL, personne n'a créé de connexion à la base. Enfin, si, quelqu'un s'est chargé de tout cela de façon transparente : l'Entity Framework.

L'évolution dans les accès aux données

 

Sur le schéma ci-dessus on voit en colonne, respectivement : le niveau d'abstraction, le langage d'interrogation et le résultat des requêtes. En ligne nous trouvons ADO.NET 2.0 et, en dessous, ADO.NET EF.

  • Le niveau d'abstraction de ADO.NET 2.0 est de type bas niveau, c'est à dire qu'il colle au schéma de la base de données (le modèle physique, le MPD).
    Le niveau d'abstraction de EF change totalement puisqu'ici on travaille en haut niveau, celui du schéma conceptuel (le MCD).
  • Côté langage d'interrogation, ADO.NET repose sur le dialecte SQL de la base cible exprimé sous la forme de chaînes de caractères dans le code.
    De son côté EF offre LINQ, ou Requêtage intégré au Langage (Language-Integrated Query) totalement indépendant de la base cible.
  • Enfin, pour les résultats des requêtes, ADO.NET offre un accès non typé source d'erreurs, là où EF offre des objets aux propriétés typées.

On le voit ici clairement, le bon technologique est faramineux, c'est bien au niveau d'un modèle conceptuel qu'on va travailler en ignorant tout du modèle physique et du langage de la base de données !

C'est d'autant plus extraordinaire lorsque, comme moi, on commence à avoir quelques heures de vols et qu'on a connu moultes tentatives couteuses et infructueuses chez plusieurs éditeurs de logiciels de créer des couches d'abstraction pour accéder aux données de différentes bases sans modifier l'application... Ce rêve de pouvoir fournir qui sa compatibilité, qui son logiciel de gestion médical sous SQL Server autant que Oracle ou MySQL en configurant juste l'application mais sans la recompiler ni même en maintenir plusieurs versions, ce rêve est aujourd'hui une réalité, une extension naturelle de .NET !

Pour l'instant tout ceci se trouve au niveau du développeur mais il semble évident qu'on puisse pousser la logique au niveau de l'utilisateur. J'y suis particulièrement sensible puisque, vous le savez certainement, je suis l'auteur et l'éditeur de MK Query Builder, un ensemble de composants permettant à l'utilisateur final d'interroger librement les données d'une application (il s'agit de ce qu'on appelle un requêteur visuel ou Visual Query Builder). Quand je vois le temps, le code nécessaire pour offrir modestement à mon niveau les fonctionnalités avancées de MKQB aux utilisateurs finaux, je ne peux que rester admiratif et ressentir un respect immense pour l'équipe Microsoft qui a développée LINQ et l'Entity Framework !

L'Entity Data Model (EDM)

L'EDM est le schéma conceptuel des données dans EF. C'est un vocabulaire qui décrit le schéma conceptuel pour être exact. Visual Studio 2008 le représente graphiquement sous forme d'entités et de liens comme un diagramme de classe.

Il permet au développeur de fixer le cadre des données qu'il souhaite voir, conceptuellement et sans rapport direct avec le schéma physique de la base de données.

EDM représentent d'une part les entités et des relations.

Les entités sont des types distincts (des classes) qui possèdent des propriétés qui peuvent être simples (scalaires) ou complexes. Les relations décrivent pour leur part la façon dont les entités sont liées. Il s'agit d'une déclaration explicites des noms de relation et des cardinalités.

Mais quand met-on les mains dans le camboui ? Il faut bien qu'à un moment ou un autre ce modèle conceptuel puisse correspondre avec le schéma de la base de données... Oui, bien entendu, mais une fois encore, pas de bas niveau ici.

Le schéma EDM peut être créé et modifié totalement graphiquement par le développeur (sous VS 2008) mais ce dernier possède aussi le moyen de faire le mapping entre les entités "idéales" de EDM et les entités physiques de la base de données. Pour cela deux façons coexistent : la plus simple consiste à faire un drag'n drop entre l'onglet des connexions SGDB de VS 2008 et l'espace de travail du schéma EDM ! On prend des tables, on les pose, et le schéma s'écrit de lui-même, relations comprises.

Par exemple la pattern courante "many to many" qui relie une table de lycéens à la table des options suivies par chacun fait intervenir une troisième table dans le schéma physique, celle qui contient les paires de clés lycéen/option. Ainsi un lycéen peut suivre plusieurs options et une option peut être suivies par plusieurs lycéens. Un classique des classiques. Cette pattern est détectée par EDM qui créé automatiquement une jointure n-n entre les deux entités, comme dans le schéma conceptuel de la base de données.

EDM automatise ainsi 95% de la création du schéma conceptuel, même si celui-ci ressemble encore beaucoup au modèle physique de la base. C'est là qu'interviennent les 5% restant : le développeur peut à sa guise supprimer ou ajouter des champs, des relations, et il indique explicitement à EDM comment les mapper sur les tables existantes dans la base de données.

Prenons l'exemple d'une base de données correctement optimisée et standardisée. Une fiche client pouvant posséder une adresse de livraison et une adresse de facturation, le concepteur de la base à séparer les deux informations. On a d'une part une table des clients et de l'autre une table des adresses plus deux jointures AdresseFacturation et AdresseLivraison qui parte de Client vers Adresse avec une cardinalité 0..1/1..1, c'est à dire qu'un client peut posséder 0 ou 1 adresse de facturation, 0 ou 1 adresse de livraison et que chaque adresse doit correspondre à 1 et 1 seul client (si on ajoute un type dans Adresse pour indiquer Facturation ou Livraison, le MCD pourra d'ailleurs exprimer la cardinalité sous la forme d'un lien dit identifiant avec Client).

Sous EDM, si on place les tables par drag'n drop, par force on trouvera dans le schéma les deux entités Client et Adresse avec ses liens. Toutefois cela n'est pas réellement intéressant. D'un point de vue conceptuel nous allons travailler sur des fiches Client, et ses fiches possèdent une adresse de livraison et une autre de facturation. Pour nous, conceptuellement, ces adresses ne sont que des propriétés de Client. On se moque totalement des problèmes d'optimisation ou des règles de Codd que l'admin de la base de données a ou non suivi pour en arriver à créer deux tables... Cela appartient au modèle physique et nous ne voulons plus nous embarrasser de sa lourdeur et de son manque de pertinence conceptuelle.

Le développeur peut alors ajouter à Client deux propriétés, AdresseFacturation et AdresseLivraison, qui seront des propriétés complexes (retournant une fiche Adresse). Il supprimera aussi l'entité Adresse du schéma et enfin indiquera à EDM le mapping de ces deux nouveaux champs.

A partir de maintenant l'application pourra obtenir, trier, filtrer, des instances de Client qui possèdent, entre autres propriétés, une adresse de livraison et une autre de livraison. Plus aucun lien avec le schéma de la base de données, uniquement des choses qui ont un sens du point de vue conceptuel. C'est EF qui s'occupera de générer les requêtes sur les deux tables pour créer des entités Client.

Cela fonctionne aussi en insertion et en mise à jour... Le rêve est bien devenu réalité. Mieux, on peut personnaliser les requêtes SQL (en langage de la base cible) si vraiment on désire optimiser certains accès. De même on peut utiliser des procédures stockées au lieu de table ou de vues, etc.

L'aspect purement génial de EF c'est qu'il offre un mode automatique totalement transparent pour travailler au niveau conceptuel sans jamais perdre la possibilité, à chaque niveau de sa structure, de pouvoir intervenir manuellement. Qu'il s'agisse des entités, de leur mappings, des relations autant que la possibilité de saisir du code SQL de la base cible. Certes ce genre de choses n'est absolument pas à conseiller, en tout cas pour l'écriture manuelle de SQL, c'est un peu un contre emploi de EF... Mais que la possibilité existe montre surtout l'intelligence de la construction de EF qui n'est pas une "boîte noire" sur laquelle il est impossible d'intervenir et qui, par force, limiterait les possibilités dans certains cas.

Interroger les données

Pour interroger les données, EF nous donne le choix entre deux méthodes :

LINQ to Entities

C'est l'exemple de code donné en début de ce billet. LINQ To Entities est une extension de C# qui possède des mots clés proches de SQL (from, where, select..). Mais soyons clairs : LINQ to Entities permet d'interroger le modèle conceptuel, l'EDM, et non pas la base de données ! C'est le mélange entre LINQ et EDM qui permet à EF de générer lui-même le code SQL nécessaire.

LINQ est d'une extraorinaire puissance. D'abord on reste en C#, on bénéficie donc de Intellisense, du contrôle de type, etc. Ensuite, LINQ to Entities n'est qu'une des émanations de LINQ puisqu'il en existe des variantes comme LINQ to XML qui permet globalement la même chose mais non plus en interrogeant des schémas EDM mais des données XML. Le fonctionnement de LINQ dépasse le cadre de ce billet et j'y reviendrai tellement il y a de choses à en dire.

Entity SQL

Si tout s'arrêtait là, Entity Framework serait déjà énorme. J'en pers un peu mes superlatifs tellement je trouve le concept et son implémentation aboutis. Je suis un fan de EF et de LINQ, vous l'avez remarqué et vous me le pardonnerez certainement une fois que vous l'aurez essayé...

Mais en fait EF propose un second moyen d'interroger les données, la tour de babel SQL, c'est à dire Entity SQL.

Il existe en effet de nombreux cas où LINQ pourrait s'avérer plus gênant que génial. Par exemple dans certains cas où le requêtage doit être dynamique (la requête est construite en fonction de choix de l'utilisateur, un écran de recherche multicritère par exemple). Décrocher de EF pour ces cas (pas si rares) imposerait de revenir à du SQL spécifique de la base cible, écrit dans des strings non testables, recevoir des données non typées, créer et gérer des connexions à la base en parallèle de LINQ utilisé ailleurs dans le code, et plein d'autres horreurs de ce genre.

Heureusement, Entity SQL existe. Il s'agit d'un langage SQL spécial, universel dirons-nous, qui ajoute la sémantique EDM à SQL. Ce SQL là est spécial à plus d'un titre donc. Le premier c'est qu'il permet d'interroger l'EDM, le modèle conceptuel, comme LINQ, mais en SQL. Le second c'est que ce SQL "comprend l'objet" et sait utiliser non pas des tables et des champs mais des entités et des propriétés. Enfin, il est universel puisque lui aussi, comme LINQ, sera traduit par EF en "vrai" SQL de la base cible. Le même code Entity SQL fonctionne donc sans modification ni adaptation que la base soit Oracle, MySQL ou SQL Server !

Le rêve de certains éditeurs de logiciels dont je parlais plus haut s'arrêtait d'ailleurs là : créer un SQL interne à l'application qui serait traduit en SQL cible par un interpréteur lors de l'exécution. L'un des projets comme celui-là qui me reste en tête à coûté une fortune pour ne jamais marcher correctement... Entity SQL lui fonctionne de façon performante et s'intègre à Entity Framework ce qui permet de bénéficier de l'abstration conceptuelle et de l'environnement objet. La réalité dépasse largement le rêve.

La traduction des requêtes

Toutes les requêtes sont exécutées par la base de données. Je le sous-entend depuis le début mais c'est mieux de le (re)dire pour qu'il n'y ait pas de méprise...

Les requêtes LINQ et Entity SQL sont transformées en requêtes SQL de la base cible par Entity Framework grâce à l'EDM qui contient la description des entités, des jointures et du mapping.

LINQ et eSQL fonctionnent sur le même pipeline de requêtag. Il transforme les constructions de EDM en tables, vues et procédures stockées. Ce socle commun est basé sur un modèle de fournisseurs qui autorise la traduction vers différents dialectes SQL (donc des bases cibles différentes).

EntityClient, le fournisseur d'accès aux données de EF

"N'en jetez plus !" pourraient s'écrier certains... Et oui, ce n'est pas fini. En effet, et comme je le soulignais, Entity Framework permet d'intervenir à tous les niveaux, du plus haut niveau d'abstraction au plus bas, dans la cohérence et sans pour autant être obligé de faire des choix ou se passer de tel ou tel avantage.

On a vu que par exemple on pouvait injecter du SQL cible dans le modèle EDM, même si cela n'est pas souhaitable, c'est faisable. On a vu qu'on pouvait préférer eSQL à LINQ dans certains cas. De même on peut vouloir totalement se passer de l'objectivation des entités pour simplement accéder aux données de façon "brutes" mais sans perdre tous les avantages de EF.

Pour ce cas bien particulier EF propose un fournisseur de données appelé EntityClient. Il faut le voir comme n'importe quel fournisseur de données ADO.NET. Il propose le même fonctionnement à base d'objets Connexion, Command et DataReader. On s'en sert comme on se sert d'un fournisseur SQL Server ou OleDB sous ADO.NET 2.0.

Toutefois, EntityClient interroge lui aussi le schéma conceptuel, l'EDM. Le langage naturel de ce provider d'accès est eSQL (Entity SQL) présenté plus haut.

Utiliser EntityClient peut s'avérer utile lorsque l'application n'a pas du tout besoin d'objets, ou lorsqu'elle possède sa propre couche objet (business object layer par exemple) qu'elle souhaite alimenter sans passer par les instances d'entités de EF tout en bénéficiant de l'abstraction de EF (utiliser un SQL universel notamment).

Les métadonnées

L'Entity Framework fournit un service d'accès aux métadonnées. Il s'agit d'une infrastructure commune permettant de décrire le stockage, le modèle, et la structure des objets, les procédures stockées, les informations de mapping et même les annotations personnalisées.

L'API publique permet de créer et d'utiliser des MetadataWorkspace directement, d'obtenir des MetadataWorkspace depuis des ObjectContext, d'utiliser les métadata retournées par les enregistrements de données de EntityClient.

Conclusion

La présentation de Carl Perry était entrecoupée de quelques démos permettant bien entendu de mieux voir et comprendre les concepts exposés. Visual Studio 2008 permet d'utiliser l'Entity Framework de façon simple et naturelle, le voir est toujours préférable à le croire sur parole. En tant que témoin de la session, et ayant "joué" avec la bêta de VS 2008 bien avant et étant depuis hier comme tous les abonnés MSDN l'heureux possesseur de la version finale de VS 2008, je peux vous certifier que tout ce que j'ai dit ici est.. en dessous de la vérité ! Testez vous-mêmes Entity Framework et vous comprendrez à quel point les temps ont changé, vous sentirez par vous-mêmes ce fantastique bond technologique qui relègue toutes les autres tentatives de ce type ou approchant (comme ECO de Borland par exemple) au rang des antiquités d'un autre millénaire... Entity Framework c'est une claque aussi forte que la sortie de .NET lui-même, on ne programmera jamais plus après comme on le faisait avant..