Dot.Blog

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

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..

 

blog comments powered by Disqus