Entity Framework5cf PDF
Entity Framework5cf PDF
[Link] at [Link]
octobre 2012
[Link]
1/171
Table des matières
1 INTRODUCTION......................................................................................................................................................................4
1.1 OBJECTIF....................................................................................................................................................................................4
1.2 LES OUTILS UTILISÉS....................................................................................................................................................................5
1.3 LES CODES SOURCE......................................................................................................................................................................5
1.4 LA MÉTHODE...............................................................................................................................................................................6
1.5 PUBLIC VISÉ.................................................................................................................................................................................7
1.6 ARTICLES CONNEXES SUR DEVELOPPEZ .COM...................................................................................................................................8
2 L'ÉTUDE DE CAS.....................................................................................................................................................................9
2.1 LE PROBLÈME..............................................................................................................................................................................9
2.2 LA BASE DE DONNÉES.................................................................................................................................................................13
2.2.1 LA TABLE [MEDECINS]........................................................................................................................................................13
2.2.2 LA TABLE [CLIENTS]............................................................................................................................................................13
2.2.3 LA TABLE [CRENEAUX]......................................................................................................................................................14
2.2.4 LA TABLE [RV].......................................................................................................................................................................14
3 ETUDE DE CAS AVEC SQL SERVER EXPRESS 2012....................................................................................................16
3.1 INTRODUCTION...........................................................................................................................................................................16
3.2 INSTALLATION DES OUTILS..........................................................................................................................................................16
3.3 LE SERVEUR EMBARQUÉ (LOCALDB)\V11.0...................................................................................................................................22
3.4 CRÉATION DE LA BASE À PARTIR DES ENTITÉS...............................................................................................................................23
3.4.1 L'ENTITÉ [MEDECIN]................................................................................................................................................................25
3.4.2 L'ENTITÉ [CRENEAU]................................................................................................................................................................31
3.4.3 LES ENTITÉS [CLIENT] ET [RV]..................................................................................................................................................35
3.4.4 FIXER LE NOM DE LA BASE.........................................................................................................................................................38
3.4.5 REMPLISSAGE DE LA BASE..........................................................................................................................................................39
3.4.6 MODIFICATION DES ENTITÉS........................................................................................................................................................42
3.4.7 AJOUTER DES CONTRAINTES À LA BASE........................................................................................................................................43
3.4.8 LA BASE DÉFINITIVE..................................................................................................................................................................47
3.5 EXPLOITATION DE LA BASE AVEC ENTITY FRAMEWORK.................................................................................................................50
3.5.1 SUPPRESSION D'ÉLÉMENTS DU CONTEXTE DE PERSISTANCE...............................................................................................................50
3.5.2 AJOUT D'ÉLÉMENTS AU CONTEXTE DE PERSISTANCE........................................................................................................................53
3.5.3 AFFICHAGE DU CONTENU DE LA BASE...........................................................................................................................................55
3.5.4 APPRENTISSAGE DE LINQ AVEC LINQPAD................................................................................................................................61
3.5.5 MODIFICATION D'UNE ENTITÉ ATTACHÉE AU CONTEXTE DE PERSISTANCE............................................................................................73
3.5.6 GESTION DES ENTITÉS DÉTACHÉES...............................................................................................................................................75
3.5.7 LAZY ET EAGER LOADING.........................................................................................................................................................79
3.5.8 CONCURRENCE D'ACCÈS AUX ENTITÉS..........................................................................................................................................82
3.5.9 SYNCHRONISATION DANS UNE TRANSACTION..................................................................................................................................87
3.6 ETUDE D'UNE ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5.............................................................................................90
3.6.1 LE NOUVEAU PROJET.................................................................................................................................................................90
3.6.2 LA CLASSE EXCEPTION..............................................................................................................................................................91
3.6.3 LA COUCHE [DAO].................................................................................................................................................................92
3.6.4 TEST DE LA COUCHE [DAO]...................................................................................................................................................100
3.6.5 CONFIGURATION DE [Link]...............................................................................................................................................103
3.6.6 GÉNÉRATION DE LA DLL DE LA COUCHE [DAO].......................................................................................................................108
3.6.7 LA COUCHE [[Link]].......................................................................................................................................................109
3.7 CONCLUSION............................................................................................................................................................................113
4 ETUDE DE CAS AVEC MYSQL 5.5.28.............................................................................................................................114
4.1 INSTALLATION DES OUTILS........................................................................................................................................................114
4.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................115
4.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................122
4.4 CONCLUSION............................................................................................................................................................................125
5 ETUDE DE CAS AVEC ORACLE DATABASE EXPRESS EDITION 11G RELEASE 2.......................................... 127
5.1 INSTALLATION DES OUTILS........................................................................................................................................................127
5.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................130
5.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................138
6 ETUDE DE CAS AVEC POSTGRESQL 9.2.1..................................................................................................................143
6.1 INSTALLATION DES OUTILS........................................................................................................................................................143
6.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................144
6.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................151
7 ETUDE DE CAS AVEC FIREBIRD 2.1............................................................................................................................ 157
7.1 INSTALLATION DES OUTILS........................................................................................................................................................157
[Link]
2/171
7.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................158
7.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................165
8 CONCLUSION...................................................................................................................................................................... 169
[Link]
3/171
1 Introduction
1.1 Objectif
Entity Framework est un ORM (Object Relational Mapper) initialement créé par Microsoft et maintenant disponible en open
source [juillet 2012, [Link] Dans un cours [Link] j'utilise l'architecture suivante pour une
certaine application web :
SPRING
Le framework NHibernate [[Link] est un ORM apparu avant Entity Framework. C'est un
produit mature permettant de se connecter à diverses bases de données. L'ORM isole la couche [DAO] (Data Access Objects) du
connecteur [Link]. C'est l'ORM qui émet les ordres SQL à destination du connecteur. La couche [DAO] utilise elle l'interface
offerte par l'ORM. Celle-ci dépend de l'ORM. Ainsi changer d'ORM implique de changer la couche [DAO].
Cette architecture résiste bien aux changements de SGBD. Lorsqu'on relie la couche [DAO] directement au connecteur
[Link], changer de SGBD a un impact sur la couche [DAO] :
• les SGBD n'ont pas tous les mêmes types de données ;
• les SGBD n'ont pas les mêmes stratégies de génération des clés primaires ;
• les SGBD ont du SQL propriétaire ;
• la couche [DAO] a pu utiliser des bibliothèques liées à un SGBD précis ;
• ...
Lorsque c'est un ORM qui est relié au connecteur [Link], changer de SGBD revient à changer la configuration de l'ORM pour
l'adapter au nouveau SGBD. La couche [DAO] ne change pas.
Le framework [Link] [[Link] assure l'intégration des couches d'une application. Ci-
dessus :
• l'application [Link] demande à Spring une référence sur la couche [DAO] ;
• Spring exploite un fichier de configuration pour créer cette couche et en rendre la référence.
Cette architecture résiste bien aux changements de couches tant que celles-ci présentent toujours la même interface. Changer la
couche [DAO] ci-dessus, consiste à changer le fichier de configuration de Spring pour que la nouvelle couche soit instanciée à la
place de l'ancienne. Comme celles-ci implémentent la même interface et que la couche [Link] utilise cette interface, la couche
[Link] reste inchangée.
On a donc là une architecture souple et évolutive. Pour le montrer, nous allons remplacer l'ORM NHibernate par Entity
Framework 5 :
SPRING
[Link]
4/171
• on connectera l'application [Link] existante à cette nouvelle couche [DAO].
[Link]
5/171
3
Ce sont des projets Visual Studio 2012 [1], rassemblés dans une solution [2]. Dans un dossier [databases], on trouvera un dossier par
SGBD utilisé. On y trouve les scripts SQL de génération de la base exemple pour ces SGBD.
1.4 La méthode
Pour découvrir Entity Framework 5 Code First, je suis d'abord parti du livre suivant : " Professional [Link] MVC 3 " de
Jon Galloway, Phil Haack, Brad Wilson, Scott Allen aux éditions Wrox. Dans l'application exemple de ce livre, les auteurs utilisent
Entity Framework (EF) comme ORM. Comme je ne connaissais pas, j'ai parcouru le net pour en savoir plus. J'ai ainsi découvert
que la version la plus récente était EF 5 et qu'il y avait des incompatibilités avec EF 4 car le code du livre testé avec EF 5 présentait
des erreurs de compilation.
• Model First : il existe de nombreux articles sur cette approche de EF, par exemple [[Link]
us/data/[Link]]. Cet article est introduit de la façon suivante :
Summary: In this paper we'll look at the new Entity Framework 4 that ships with .NET Framework 4 and Visual Studio 2010.
I'll discuss how you can approach it's usage from a model-first perspective with the premise that you can drive database design from
a model and build both your database as well as your data access layer declaratively from this model. The model contains the description of
your data represented as entities and relationships providing a powerful approach to working with [Link], creating a separation of
concerns through an abstraction between a model definition and its implementation.
Un ORM fait le pont entre des tables de bases de données et des classes.
SPRING
Ci-dessus,
• à gauche de la couche EF5, on a des objets, qu'on appelle des entités ;
• à droite de la couche EF5, on a des tables de base de données.
[Link]
6/171
Couche d'accès Entity Connecteur
aux données Entités Framework 5 [[Link]] SGBD Tables
[DAO2] 1 2
La couche [DAO] travaille avec des objets images des tables de la base de données. Ces objets sont rassemblés dans un
contexte de persistance et sont appelés entités (Entity). Les modifications faites sur les entités sont répercutées, grâce à
l'ORM, sur les tables de la base de données (insertion, modification, suppression). De plus la couche [DAO] dispose d'un
langage de requêtage LINQ to Entity (Language Integrated Query) qui requête sur les entités et non sur les tables. La
méthode Model First consiste à construire les entités avec un outil graphique. On définit chaque entité et les relations qui
la lient avec les autres. Ceci fait, un outil permet de générer :
• les différentes classes reflétant les entités construites graphiquement ;
• la DDL (Data Definition Language) permettant de générer la base de données.
Les exemples que j'ai trouvés sur cette méthode utilisaient tous Visual Studio 2010 Professional et un modèle appelé
[Link] Entity Data Model. J'ai pu tester ce modèle avec Visual Studio 2010 Professional mais lorsque je suis passé
à Visual Studio Express 2012 qui était ma cible, j'ai constaté que ce modèle n'était plus disponible. J'ai donc abandonné
cette approche.
• Database First : le point de départ de cette méthode est une base de données existante. A partir de là, un outil génère
automatiquement les entités images des tables de la base. Là encore les exemples trouvés, par exemple
[[Link] utilisent Visual Studio 2010 Professional et le modèle
[Link] Entity Data Model. J'ai donc abandonné également cette approche qui était pourtant ma préférée. Pour
savoir quelles entités utiliser comme images d'une base de données existante, il était simple de commencer avec un outil
qui les génère.
• Code First : on écrit soi-même les classes qui vont former les entités. Il faut alors avoir un minimum de notions sur le
fonctionnement d'EF. C'est la voie que j'ai suivie parce qu'elle était exploitable avec Visual Studio Express 2012.
• j'écrivais un code pour SQL Server Express 2012 car c'est pour ce SGBD qu'on trouve le plus grand nombre d'exemples ;
• une fois ce code débogué, je le portais sur les autres SGBD (Firebird, Oracle, MySQL, PostgreSQL).
Nous allons ici procéder différemment. Je vais d'abord décrire la totalité des codes pour SQL Server puis je décrirai leur portage
pour les autres SGBD. Dans ce portage, les ajustements suivants ont lieu :
• les bases de données ont des particularités propriétaires. J'ai notamment utilisé des Triggers pour générer le contenu de
certains champs de façon automatique. Chaque SGBD a sa façon propre de gérer cela ;
• les entités images des tables peuvent changer mais c'est alors volontaire. J'aurais pu choisir des entités convenant à toutes
les bases de données ;
• le pilote [Link] du SGBD change ;
• la chaîne de connexion au SGBD change.
[Link]
7/171
Ce document n'est pas un cours sur Entity Framework 5 Code First. Pour cela, on pourra lire par exemple " Programming
Entity Framework: Code First ", de Julie Lerman et Rowan Miller aux éditions O'Reilly. Le document ne vise aucunement
l'exhaustivité mais expose simplement la démarche que j'ai utilisée pour appréhender cet ORM. Je pense que celle-ci peut servir à
d'autres personnes abordant EF5. Mon objectif ne dépasse pas ce cadre.
• " Entity Framework – l'approche Code First ", juin 2012 – par Reward. Cet article et le présent document se recoupent
partiellement. Il va cependant plus loin sur certains points notamment sur le " mapping " héritage de classes <--> tables ;
• " Introduction à Entity Framework ", décembre 2008, par Paul Musso ;
• " Créer un modèle de classes avec Entity Framework ", avril 2009, par Jérôme Lambert ;
• " Mesure des performances de Linq to SQL face à Sql et Entity Framework ", juin 2011, d'Immobilis ;
• " Entity Framework Code First : activer la migration automatique ", juin 2012, par Hinault Romaric ;
• " Création d'une application CRUD avec WebMatrix, Razor et Entity Framework ", mai 2012, par Hinault Romaric ;
• " Entity Framework : à la découverte de Code First Migrations ", juin 2012, par Hinault Romaric ;
Comme indiqué plus haut, le document présent n'est pas exhaustif. On lira avec profit les articles ci-dessus pour combler certaines
lacunes. Ma recherche a pu être incomplète. Que les auteurs que j'ai pu oublier veuillent bien m'excuser.
[Link]
8/171
2 L'étude de cas
2.1 Le problème
Revenons à l'application que nous voulons construire. Nous partons d'une application existante à l'architecture suivante :
SPRING
SPRING
où EF5 a remplacé NHibernate. Cette application est un prétexte pour étudier EF5. Parce que [Link] nous permet facilement
de changer de couche sans tout casser, l'application 2 utilisera la même couche [[Link]] que l'application 1. Parce que ce
document est consacré à EF5, nous n'expliquerons pas l'écriture de cette couche. Nous l'introduirons dans l'application 2 pour
constater que ça marche. Nous expliquerons simplement les changements à opérer dans le fichier de configuration de [Link].
L'étude de cas est la suivante. On souhaite proposer aux médecins un service de prise de rendez-vous fonctionnant sur le principe
suivant :
• un service secrétariat assure les prises de RV pour un grand nombre de médecins. Ce service peut être réduit à une unique
personne. Le salaire de celle-ci est mutualisé entre tous les médecins utilisant le service de RV ;
• le service secrétariat et tous les médecins sont reliés à Internet ;
• les RV sont enregistrés dans une base de données centralisée, accessible par Internet, par le secrétariat et les médecins ;
• la prise de RV est normalement faite par le secrétariat. Elle peut être faite également par les médecins eux-mêmes. C'est le cas
notamment lorsqu'à la fin d'une consultation, le médecin fixe lui-même un nouveau RV à son patient.
[Link]
9/171
Secrétariat
Internet
Médecin 1 Base de données des RV
Médecin 2
...
Les médecins gagnent en efficacité s'ils n'ont plus à gérer les RV. S'ils sont suffisamment nombreux, leur contribution aux frais de
fonctionnement du secrétariat sera faible. Nous appellerons l'application [RdvMedecins]. Nous présentons ci-dessous des copies
d'écran de son fonctionnement.
A partir de cette première page, l'utilisateur (Secrétariat, Médecin) va engager un certain nombre d'actions. Nous les présentons ci-
dessous. La vue de gauche présente la vue à partir de laquelle l'utilisateur fait une demande, la vue de droite la réponse envoyée
par le serveur.
[Link]
10/171
L'utilisateur a sélectionné un médecin et a saisi un jour de RV
On réserve
[Link]
11/171
On la renseigne
[Link]
12/171
Il la retrouve dans son dernier état
Une fois l'opération de prise de RV ou d'annulation de RV
faite, l'utilisateur peut revenir à la page d'accueil
La base de données utilisée par l'application NHibernate est une base de données MySQL5 avec quatre tables :
Elle contient des informations sur les médecins gérés par l'application [RdvMedecins].
Les clients des différents médecins sont enregistrés dans la table [CLIENTS] :
[Link]
13/171
• ID : n° identifiant le client - clé primaire de la table
• VERSION : n° identifiant la version de la ligne dans la table. Ce nombre est incrémenté de 1 à chaque fois qu'une modification
est apportée à la ligne.
• NOM : le nom du client
• PRENOM : son prénom
• TITRE : son titre (Melle, Mme, Mr)
La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le créneau n° 2 commence à 8 h 20 et se
termine à 8 h 40 et appartient au médecin n° 1 (Mme Marie PELISSIER).
[Link]
14/171
Elle liste les RV pris pour chaque médecin :
Cette table a une contrainte d'unicité sur les valeurs des colonnes jointes (JOUR, ID_CRENEAU) :
Si une ligne de la table[RV] a la valeur (JOUR1, ID_CRENEAU1) pour les colonnes (JOUR, ID_CRENEAU), cette valeur ne peut
se retrouver nulle part ailleurs. Sinon, cela signifierait que deux RV ont été pris au même moment pour le même médecin. D'un
point de vue programmation Java, le pilote JDBC de la base lance une SQLException lorsque ce cas se produit.
La ligne d'id égal à 7 (cf [1] ci-dessus) signifie qu'un RV a été pris pour le créneau n° 10 et le client n° 2 le 10/09/2006. La table
[CRENEAUX] nous apprend que le créneau n° 10 correspond au créneau horaire 11 h - 11 h 20 et appartient au médecin n° 1
(Mme Marie PELISSIER). La table [CLIENTS] nous apprend que le client n° 2 est Mme Christine GERMAN.
Cette étude de cas a fait l'objet d'un article Java [[Link] dans lequel on utilise l'ORM
Hibernate pour Java.
[Link]
15/171
3 Etude de cas avec SQL Server Express 2012
3.1 Introduction
Les exemples trouvés sur le net pour Entity Framework sont dans leur majorité des exemples avec SQL Server. C'est assez normal.
Il est probable que c'est le SGBD le plus répandu dans le monde .NET des entreprises. Nous allons suivre cette tendance. Les
exemples seront ensuite étendus à toutes les bases de données citées au paragraphe 1.2, page 5.
• [1] : dans le Menu Démarrer, lancer le " Gestionnaire de configuration SQL Server " ;
• [2] : dans ce gestionnaire, lancer le serveur ;
• [3] : il est lancé.
[Link]
16/171
1
• [1] : dans le Menu Démarrer, lancer " SQL Server Management Studio " ;
• [2] : l'outil d'administration.
3
1 4
2
5
[Link]
17/171
6
[Link]
18/171
• [9] : on édite les propriétés de l'utilisateur sa (system administrator) ;
10
• en [10], on lui fixe un mot de passe. Dans la suite du document, celui-ci est msde ;
10 12
11
2
1 3
4
• en [1], on se reconnecte ;
• en [2], en authentification SQL Server ;
• en [3], l'utilisateur est sa ;
• en [4], son mot de passe est msde ;
• en [5], on se connecte ;
[Link]
19/171
6
2
1
[Link]
20/171
5
7
6
8 9
10
11
[Link]
21/171
12
Nous en savons assez pour l'instant sur l'utilisation de l'outil d'administration de SQL Server.
4
5
6
[Link]
22/171
• en [4], on se connecte au serveur (localdb)\v11.0 ;
• en [5], avec une authentification Windows ;
• en [6], la connexion réussie affiche les bases de données du serveur. On pourrait comme précédemment créer une
nouvelle base.
Tous nos projets vont avoir besoin de la DLL d'Entity Framework 5. Nous l'ajoutons :
[Link]
23/171
2
3
[Link]
24/171
2
Nous garderons cette habitude par la suite de mettre la définition de nos entités dans le dossier [Models].
Pour construire nos entités, nous allons nous aider de la définition de la base de données MySQL 5 utilisée dans le projet
NHibernate. Rappelons le rôle des entités EF :
Les entités doivent refléter les tables de la base de données. La couche d'accès aux données utilise ces entités au lieu de travailler
directement avec les tables. Commençons par la table [MEDECINS] :
1. using System;
2.
3. namespace [Link]
4. {
5. public class Client
6. {
7. // data
[Link]
25/171
8. public virtual int Id { get; set; }
9. public virtual string Titre { get; set; }
10. public virtual string Nom { get; set; }
11. public virtual string Prenom { get; set; }
12. }
Si les champs proposés sont naturels, les mots clés virtual ne le sont pas. C'est une contrainte imposée à la fois par NHibernate et
EF5 : les champs des entités doivent être des propriétés publiques virtuelles. Nous mettons cette classe dans un fichier
[[Link]] [1]. C'est là que nous placerons toutes nos entités.
1. using [Link];
2. using [Link];
3.
4. namespace [Link]
5. {
6.
7. // le contexte
8. public class RdvMedecinsContext : DbContext
9. {
10. // les médecins
11. public DbSet<Medecin> Medecins { get; set; }
12.
13. // initialisation des noms de tables
14. protected override void OnModelCreating(DbModelBuilder modelBuilder)
15. {
16. [Link](modelBuilder);
17. [Link]<Medecin>().ToTable("MEDECINS", "dbo");
18. }
19.
20. }
21.
22. // initialisation de la base
23. public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
24. {
25. }
26. }
• ligne 8 : la classe [RdvMedecinsContext] va représenter le contexte de persistance, c.-à-d. l'ensemble des entités gérées par
l'ORM. Il doit dériver de la classe [[Link]] ;
• ligne 11 : le champ [Medecins] va représenter les entités de type [Medecin] du contexte de persistance. Il est de type
DbSet<Medecin>. On trouvera généralement autant de [DbSet] que de tables dans la base, un par table ;
• ligne 14 : on peut intervenir sur le " mapping " entités <--> tables en redéfinissant la méthode
[DbContext].OnModelCreating ;
• ligne 17 : on précise la table associée à l'entité [Medecin]. Elle sera dans le schéma " dbo " de SQL Server et elle se
nommera [MEDECINS]. Si on ne fait rien, la table sera supposée porter le nom de l'entité associée ;
• ligne 23 : on définit une classe [RdvMedecinsInitializer] pour initialiser la base créée. Ici elle dérive de la classe
[DropCreateDataBaseAlways] qui comme son nom l'indique supprime la base si elle existe déjà puis la recrée. C'est
pratique en phase de développement de la BD. Le paramètre de la classe [DropCreateDataBaseAlways] est le type de
[Link]
26/171
contexte de persistance associée à la base. On peut utiliser d'autres classes mères que [DropCreateDataBaseAlways] pour
la classe d'initialisation :
• [DropCreateDatabaseIfModelChanges] : recrée la base si les entités ont changé,
• [CreateDatabaseIfNotExists] : crée la base si elle n'existe pas ;
1. using System;
2. using [Link];
3. using [Link];
4.
5. namespace RdvMedecins_01
6. {
7. class CreateDB_01
8. {
9. static void Main(string[] args)
10. {
11. // on crée la base
12. [Link](new RdvMedecinsInitializer());
13. using (var context = new RdvMedecinsContext())
14. {
15. [Link](false);
16. }
17. }
18. }
19. }
• ligne 12 : [[Link]] est une classe offrant des méthodes statiques pour gérer la base associée à un
contexte de persistance. La méthode statique [SetInitializer] permet de préciser la classe d'initialisation de la base. Cela ne
lance pas l'initialisation ;
• ligne 13 : pour travailler avec un contexte de persistance, il faut instancier celui-ci. C'est ce qui est fait ici. On utilise une
clause using afin que le contexte soit automatiquement fermé à la sortie de la clause. Don c ligne 17, le contexte est
fermé ;
• ligne 15 : on lance explicitement la génération de la base associée au contexte de persistance [RdvMedecinsContext]. Le
paramètre false indique que cette opération ne doit pas être faite si elle a déjà été faite pour ce contexte. Ici, on aurait tout
aussi bien mettre true.
Lorsqu'on travaille avec une base de données, les paramètres de connexion sont généralement inscrits dans le fichier [[Link]].
Constatons que pour l'instant, ils n'y sont pas :
Les éléments ci-dessus ont été inscrits dans [[Link]] lorsqu'on a ajouté la dépendance Entity Framework aux références du
projet.
Exécutons le projet (Ctrl-F5) après avoir lancé SQL Server Express (c'est important) :
[Link]
27/171
L'exécution doit se terminer sans erreur. Ouvrons maintenant l'outil d'administration de SQL Server et rafraîchissons l'affichage :
On constate qu'une base portant le nom complet de la classe [RdvMedecinsContext] a été créée et qu'elle contient une table
[[Link]] (c'est le nom qu'on lui avait donné) avec des champs qui reprennent les noms des champs de l'entité [Medecin].
Si le code s'est bien exécuté et que la base ci-dessus n'apparaît pas, il faut regarder le serveur embarqué (localdb)\v11.0 (cf. page
22). Avec VS 2012 pro, ce serveur est utilisé si SQL server n'est pas actif au moment de l'exécution du code. Avec VS 2012
Express, non.
[Link]
28/171
1
En [1], on voit que la clé primaire est de type [Identité], ce qui signifie que sa valeur est automatiquement générée par SQL Server.
Nous adopterons cette stratégie avec tous les SGBD.
Nous allons laisser moins de part au hasard en utilisant des annotations. Le code de l'entité dans [[Link]] devient le suivant :
1. using System;
2. using [Link];
3. using [Link];
4.
5. namespace [Link]
6. {
7. public class Medecin
8. {
9. // data
10. [Key]
11. [Column("ID")]
12. public virtual int Id { get; set; }
13. [Required]
14. [MaxLength(5)]
15. [Column("TITRE")]
16. public virtual string Titre { get; set; }
17. [Required]
18. [MaxLength(30)]
19. [Column("NOM")]
20. public virtual string Nom { get; set; }
21. [Required]
22. [MaxLength(30)]
23. [Column("PRENOM")]
24. public virtual string Prenom { get; set; }
25. [Required]
26. [Column("VERSION")]
27. public virtual int Version { get; set; }
28. }
29. }
• lignes 2 et 3 : les annotations sont trouvées dans les espaces de noms [[Link]] (Key,
Required, MaxLength] et [[Link]] (Column). On trouvera d'autres
annotations à l'URL [[Link]
• ligne 10 : [Key] désigne la clé primaire ;
• ligne 11 : [Column] fixe le nom de la colonne correspondant au champ ;
• ligne 13 : [Required] indique que le champ est obligatoire (SQL NOT NULL) ;
• ligne 14 : [MaxLength] fixe la taille maxi de la chaîne de caractères, [MinLength] sa taille mini ;
Exécutons le projet avec cette nouvelle définition de l'entité [Medecin]. La base créée est alors la suivante :
[Link]
29/171
• les colonnes ont le nom qu'on leur a fixé ;
• l'annotation [Required] a été traduite par un SQL NOT NULL ;
• l'annotation [MaxLength(N)] a été traduite par un type SQL nvarchar(N).
Dans l'application NHibernate, la colonne [VERSION] était là pour prévenir les accès concurrents à une même ligne d'une table.
Le principe est le suivant :
• un processus P1 lit une ligne L de la table [MEDECINS] au temps T1. La ligne a la version V1 ;
• un processus P2 lit la même ligne L de la table [MEDECINS] au temps T2. La ligne a la version V1 parce que le processus
P1 n'a pas encore validé sa modification ;
• le processus P1 valide sa modification de la ligne L. La version de la ligne L passe alors à V2=V1+1 ;
• le processus P2 valide sa modification de la ligne L. L'ORM lance alors une exception car le processus P2 a une version
V1 de la ligne L différente de la version V2 trouvée en base.
On appelle cela la gestion optimiste des accès concurrents. Avec EF 5, un champ jouant ce rôle doit avoir l'un des deux attributs
[Timestamp] ou [ConcurrencyCheck]. SQL server a un type [timestamp]. Une colonne ayant ce type voit sa valeur
automatiquement générée par SQL Server à toute insertion / modification d'une ligne. Une telle colonne peut alors servir à gérer la
concurrence d'accès. Pour reprendre l'exemple précédent, le processus P2 trouvera un timestamp différent de celui qu'il a lu, car
entre-temps la modification faite par le processus P1 aura modifié ce timestamp.
1. using System;
2. using [Link];
3. using [Link];
4.
5. namespace [Link]
6. {
7. public class Medecin
8. {
9. // data
10. [Key]
11. [Column("ID")]
12. public virtual int Id { get; set; }
13. [Required]
14. [MaxLength(5)]
15. [Column("TITRE")]
16. public virtual string Titre { get; set; }
17. [Required]
18. [MaxLength(30)]
19. [Column("NOM")]
20. public virtual string Nom { get; set; }
21. [Required]
22. [MaxLength(30)]
23. [Column("PRENOM")]
24. public virtual string Prenom { get; set; }
25. [Column("TIMESTAMP")]
26. [Timestamp]
27. public virtual byte[] Timestamp { get; set; }
[Link]
30/171
28. }
29. }
• lignes 25-27 : la nouvelle colonne avec l'attribut [Timestamp] de la ligne 27. Le type du champ doit être byte[] (ligne 28).
Le nom du champ peut être quelconque. On ne lui met pas l'attribut [Required] car ce n'est pas l'application qui fournira
cette valeur mais le SGBD lui-même.
Si on exécute le projet avec cette nouvelle entité, la base évolue comme suit :
Il nous reste à régler un dernier point. Le contexte de persistance " sait " qu'une entité doit faire l'objet d'une insertion en base parce
qu'alors elle a sa clé primaire égale à null. C'est l'insertion en base qui va lui donner une valeur à la clé primaire. Ici, le type int donné
à la clé primaire [Id] ne convient pas parce que ce type n'accepte pas la valeur null. On lui donne alors le type int? qui accepte les
valeurs int plus le pointeur null. L'entité [Medecin] utilisée sera donc la suivante :
Il nous reste à voir comment représenter dans une entité le concept de clé étrangère entre tables.
[Link]
31/171
1
La seconde ligne de la table [CRENEAUX] (cf [1] ci-dessus) indique, par exemple, que le créneau n° 2 commence à 8 h 20 et se
termine à 8 h 40 et appartient au médecin n° 1 (Mme Marie PELISSIER).
Avec ce que nous savons, nous pouvons définir l'entité [Creneau] comme suit dans [[Link]] :
La seule nouveauté réside en lignes 19-20. Le fait que la table [CRENEAUX] ait une clé étrangère sur la table [MEDECINS] est
reflété dans l'entité [Creneau] par la présence d'une référence sur l'entité [Medecin], ligne 20. Le nom du champ importe peu, seul le
type est important.
Pour tester la nouvelle entité, il nous faut faire quelques modifications dans [[Link]] :
1. using [Link];
2. using [Link];
3.
4. namespace [Link]
5. {
6.
[Link]
32/171
7. // le contexte
8. public class RdvMedecinsContext : DbContext
9. {
10. // les entités
11. public DbSet<Medecin> Medecins { get; set; }
12. public DbSet<Creneau> Creneaux { get; set; }
13.
14. // initialisation des noms de tables
15. protected override void OnModelCreating(DbModelBuilder modelBuilder)
16. {
17. [Link](modelBuilder);
18. [Link]<Medecin>().ToTable("MEDECINS", "dbo");
19. [Link]<Creneau>().ToTable("CRENEAUX", "dbo");
20. }
21. }
22.
23. // initialisation de la base
24. public class RdvMedecinsInitializer :
DropCreateDatabaseIfModelChanges<RdvMedecinsContext>
25. {
26. }
27. }
Les lignes 12 et 19 reflètent le fait que le contexte a une entité de plus à gérer. Lorsque nous exécutons le projet, nous obtenons la
nouvelle base suivante :
La table [CRENEAUX] a bien été créée et la nouveauté est la présence d'une clé étrangère [1] et [2]. Son nom a été généré à partir
du nom du champ correspondant dans l'entité (Medecin) suffixé par " _Id ". Pour connaître les propriétés de cette clé étrangère, on
essaie de la modifier [3].
[Link]
33/171
La copie d'écran ci-dessus que [Medecin_Id] est clé étrangère de la table [CRENEAUX] et qu'elle référence la clé primaire [ID] de
la table [MEDECINS].
Si on crée les entités pour une base existante, la colonne clé étrangère ne s'appellera pas forcément [Medecin_Id]. Pour les autres
colonnes, on avait vu que l'annotation [Column] réglait ce problème. Bizarrement c'est plus compliqué pour une clé étrangère. Il
faut procéder de la façon suivante :
• lignes 5-7 : on crée un champ du type de la clé étrangère (int). A l'aide de l'attribut [Column], on précise le nom de la
colonne qui sera clé étrangère dans la table associée à l'entité ;
• ligne 9 : on ajoute l'annotation [ForeignKey] au champ de type [Medecin]. L'argument de cette annotation est le nom du
champ (pas de la colonne) qui est associé à la colonne clé étrangère de la table.
Ci-dessus, la colonne clé étrangère porte bien le nom qu'on lui a donné. Il faut noter que les champs :
1. [Required]
2. [Column("MEDECIN_ID")]
3. public virtual int MedecinId { get; set; }
4. [Required]
5. [ForeignKey("MedecinId")]
6. public virtual Medecin Medecin { get; set; }
n'ont donné naissance qu'à une seule colonne, la colonne [MEDECIN_ID]. Néanmoins, la présence du champ [MedecinId] est
importante. Lors de la lecture d'une ligne de la table [CRENEAUX], elle recevra la valeur de la colonne [MEDECIN_ID], c'-à-d. la
valeur de la clé étrangère sur la table [MEDECINS]. Cela est parfois utile.
Le champ [Medecin] ci-dessus reflète la relation plusieurs à un qui lie l'entité [Creneau] à l'entité [Medecin]. Plusieurs objets
[Creneau] sont reliés à un même [Medecin]. La relation inverse, un objet [Medecin] est associé à plusieurs objets [Creneau] peut être
modélisée par un champ supplémentaire dans l'entité [Medecin] :
[Link]
34/171
9. [Column("TIMESTAMP")]
10. [Timestamp]
11. public virtual byte[] Timestamp { get; set; }
Ligne 8, on a rajouté le champ [Creneaux] qui est une collection d'objets [Creneau]. Ce champ nous donnera accès à tous les
créneaux horaires du médecin.
Lorsqu'on exécute de nouveau le projet, on constate que la table [MEDECINS] n'a pas bougé :
Aucune colonne n'a été rajoutée. La relation de clé étrangère qui existe entre la table [CRENEAUX] et la table [MEDECINS] est
suffisante pour que EF sache générer les champs liés à celle-ci :
Nous savons l'essentiel. Nous pouvons terminer avec la création des deux autres entités.
[Link]
35/171
• PRENOM : son prénom
• TITRE : son titre (Melle, Mme, Mr)
La classe [Client] est quasi identique à la classe [Medecin]. On pourrait les faire dériver d'une même classe parent. La nouveauté est
ligne 20. Elle reflète le fait qu'un client peut avoir plusieurs rendez-vous et dérive de la présence d'une clé étrangère de la table
[RVS] vers la table [CLIENTS].
1. public class Rv
2. {
3. // data
4. [Key]
5. [Column("ID")]
6. public virtual int? Id { get; set; }
7. [Required]
8. [Column("JOUR")]
9. public virtual DateTime Jour { get; set; }
10. [Column("CLIENT_ID")]
[Link]
36/171
11. virtual public int ClientId { get; set; }
12. [ForeignKey("ClientId")]
13. [Required]
14. public virtual Client Client { get; set; }
15. [Column("CRENEAU_ID")]
16. virtual public int CreneauId { get; set; }
17. [ForeignKey("CreneauId")]
18. [Required]
19. public virtual Creneau Creneau { get; set; }
20. [Column("TIMESTAMP")]
21. [Timestamp]
22. public virtual byte[] Timestamp { get; set; }
23. }
Ligne 17, on voit une relation plusieurs à un : à un créneau horaire peuvent correspondre plusieurs rendez-vous (pas le même jour).
La relation inverse peut être reflétée dans l'entité [Creneau] :
Les tables [MEDECINS] et [CRENEAUX] n'ont pas changé. Les tables [CLIENTS] et [RVS] sont les suivantes :
[Link]
37/171
• remplir la base avec des données.
[Link]
38/171
Application Couche d'accès Entity Connecteur
[Link] aux données Framework 5 [[Link]] SGBD BD
[DAO] 1 2
SPRING
En fait, ces lignes sont inutiles pour SQL Server mais j'ai du les ajouter pour les autres SGBD. Aussi c'est pour mémoire
que je les mets là. Elles ne gênent pas. Le seul point important est la version de la ligne 27. C'est celle de la DLL
[[Link]] présente dans les références du projet :
Voilà. Nous sommes prêts. Nous exécutons le projet et nous obtenons la base [rdvmedecins-ef] suivante :
Ce sera notre base définitive. Il nous reste à mettre des données dedans.
[Link]
39/171
2. {
3. // initialisation de la base
4. public class RdvMedecinsInitializer : DropCreateDatabaseAlways<RdvMedecinsContext>
5. {
6. protected override void Seed(RdvMedecinsContext context)
7. {
8. [Link](context);
9. // on initialise la base
10. // les clients
11. Client[] clients ={
12. new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
13. new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
14. new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
15. new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
16. };
17. foreach (Client client in clients)
18. {
19. [Link](client);
20. }
21. // les médecins
22. Medecin[] medecins ={
23. new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
24. new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
25. new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
26. new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
27. };
28. foreach (Medecin medecin in medecins)
29. {
30. [Link](medecin);
31. }
32. // les créneaux horaires
33. Creneau[] creneaux ={
34. new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
35. new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
36. new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
37. new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
38. new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
39. new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
40. new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
41. new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
42. new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
43. new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
44. new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
45. new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
46. new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
47. new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
48. new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
49. new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
50. new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
51. new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
52. new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
53. new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
54. new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
55. new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
56. new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
57. new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
58. new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
59. new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
60. new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
61. new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
62. new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
63. new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
64. new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
[Link]
40/171
65. new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
66. new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
67. new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
68. new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
69. new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
70. };
71. foreach (Creneau creneau in creneaux)
72. {
73. [Link](creneau);
74. }
75. // les Rdv
76. [Link](new Rv { Jour = new [Link](2012, 10, 8), Client =
clients[0], Creneau = creneaux[0] });
77. }
78.
79. }
80. }
• ligne 6 : l'initialisation se passe dans la méthode [Seed]. Celle-ci existe dans la classe mère. Elle est ici redéfinie. L'argument
est le contexte de persistance [RdvMedecinsContext] de l'application ;
• ligne 8 : l'argument est passé à la classe mère ; il est probable que celle-ci ouvre le contexte de persistance qu'on lui a passé
car cette ouverture n'est plus nécessaire par la suite ;
• lignes 11-16 : création de 4 clients ;
• lignes 17-20 : ceux-ci sont ajoutés au contexte de persistance, plus exactement aux médecins de celui-ci. On notera la
méthode [Add] qui permet cela. Il faut se rappeler ici la définition du contexte :
On dit aussi que les clients ont été attachés au contexte, c'-à-d. qu'ils sont désormais gérés par EF. Avant ils en étaient
détachés. Ils existaient en tant qu'objets mais n'étaient pas gérés par EF ;
• lignes 21-27 : création de 4 médecins ;
• lignes 28-31 : on les met dans le contexte de persistance ;
• lignes 33-70 : création de créneaux horaires. Lignes 34-57, pour le médecin medecins[0], lignes 58-69, pour le médecin
medecins[1]. Les autres médecins sont sans créneaux horaires ;
• lignes 71-74 : on met ces créneaux dans le contexte de persistance ;
• ligne 76 : création d'un rendez-vous pour le 1er client avec le 1er créneau horaire et sa mise dans le contexte de
persistance.
[Link]
41/171
3.4.6 Modification des entités
Actuellement, les classes [Medecin] et [Client] sont quasi identiques. En fait si on enlève les champs ajoutés pour la gestion de la
persistance avec EF 5, elles sont identiques. Nous allons les faire dériver d'une classe [Personne]. Ces deux entités deviennent alors
les suivantes :
1. // une personne
2. public abstract class Personne
3. {
4. // data
5. [Key]
6. [Column("ID")]
7. public virtual int? Id { get; set; }
8. [Required]
9. [MaxLength(5)]
10. [Column("TITRE")]
11. public virtual string Titre { get; set; }
12. [Required]
13. [MaxLength(30)]
14. [Column("NOM")]
15. public virtual string Nom { get; set; }
16. [Required]
17. [MaxLength(30)]
18. [Column("PRENOM")]
19. public virtual string Prenom { get; set; }
20. [Column("TIMESTAMP")]
21. [Timestamp]
22. public virtual byte[] Timestamp { get; set; }
23.
24. // signature
25. public override string ToString()
26. {
27. return [Link]("[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom,
dump(Timestamp));
28. }
29. // signature courte
30. public string ShortIdentity()
31. {
32. ...
33. }
34.
35. // utilitaire
36. private string dump(byte[] timestamp)
37. {
38. ...
39. }
40.
41. }
42.
43. public class Medecin : Personne
44. {
45. // les créneaux horaires du médecin
46. public ICollection<Creneau> Creneaux { get; set; }
47. // signature
48. public override string ToString()
49. {
50. return [Link]("Medecin {0}", [Link]());
51. }
52. }
53.
54. public class Client : Personne
55. {
56. // les Rvs du client
57. public ICollection<Rv> Rvs { get; set; }
[Link]
42/171
58. // signature
59. public override string ToString()
60. {
61. return [Link]("Client {0}", [Link]());
62. }
63. }
Lorsqu'on exécute le projet, on obtient la même base. EF 5 a mappé les classes les plus basses de l'héritage chacune dans une table.
En fait, EF 5 a différentes stratégies de génération de tables pour représenter l'héritage d'entités. Nous ne les présenterons pas ici.
On pourra lire par exemple " Entity Framework Code First Inheritance : Table Per Hierarchy and Table Per Type ", à l'URL
[[Link]
Il nous reste un détail à régler. La table [RVS] des rendez-vous est la suivante :
Cette table doit avoir une contrainte d'unicité : pour un jour donné, un créneau horaire d'un médecin ne peut être réservé qu'une
fois pour un rendez-vous. En termes de table, cela signifie que le couple (JOUR,CRENEAU_ID) doit être unique. Je ne sais pas si
cette contrainte peut être exprimée directement dans le code, soit sur les entités soit sur le contexte. C'est probable mais je n'ai pas
vérifié. Nous allons prendre une autre démarche. Nous allons utiliser un client d'administration de SQL Server pour ajouter cette
contrainte.
Avec " SQL Server Management Studio ", je n'ai pas trouvé de méthode simple pour ajouter cette contrainte hormis exécuter
l'ordre SQL qui la crée :
3
1
Il existe d'autres outils d'administration de SQL Server. Nous allons utiliser ici l'outil EMS SQL Manager for SQL Server Freeware
[[Link] Une fois installé, nous le lançons :
[Link]
43/171
1
3
4
5
7
9
10
8
11
" SQL Manager Lite for SQL Server " permet de créer la contrainte d'unicité sur la table [RVS].
[Link]
44/171
2 3
1 3
[Link]
45/171
5
• en [7], on confirme ;
• en [8], le nouvel indice est apparu.
L'interface offerte par " SQL Manager Lite for SQL server " est analogue à celle offerte par " SQL Server Management Studio ".
On peut trouver des interfaces analogues pour les SGBD Oracle, PostgreSQL, Firebird, MySQL. Aussi continueron-nous
désormais avec cette famille d'outils d'administration de SGBD.
Pour avoir accès aux informations d'une table, il suffit de double-cliquer dessus :
Les informations sur la table sélectionnée sont disponibles dans des onglets. Ci-dessus, on voit l'onglet [Fields] de la table
[CLIENTS]. L'onglet [Data] affiche le contenu de la table :
[Link]
46/171
3.4.8 La base définitive
Nous avons notre base définitive. Nous exportons son script SQL afin de pouvoir la régénérer si besoin est.
2
3
4 6
[Link]
47/171
7
Le script a été généré et chargé dans l'éditeur de script. Vous pouvez consulter le code SQL généré. Nous allons reconstruire la base
de données à partir de ce script.
1 2
• en [4], on s'authentifie ;
[Link]
48/171
• en [5], on exécute le script SQL de création de la base ;
9
8
10
9
[Link]
49/171
11 12
14
• en [14], nous retrouvons la contrainte d'unicité que nous avions créée pour la table [RVS].
Nous allons désormais travailler avec cette base existante. Si elle est détruite ou détériorée, nous savons la régénérer.
[Link]
50/171
1
1. using [Link];
2.
3. namespace RdvMedecins_01
4. {
5. class Erase
6. {
7. static void Main(string[] args)
8. {
9. using (var context = new RdvMedecinsContext())
10. {
11. // on vide la base actuelle
12. // les clients
13. foreach (var client in [Link])
14. {
15. [Link](client);
16. }
17. // les médecins
18. foreach (var medecin in [Link])
19. {
20. [Link](medecin);
21. }
22. // on sauve le contexte de persistance
23. [Link]();
24. }
25. }
26. }
27. }
• ligne 9 : les opérations sur un contexte de persistance se font toujours dans une clause [using]. Cela assure qu'à la sortie du
[using], le contexte a été fermé ;
• ligne 13 : on parcourt le contexte des clients [[Link]]. Tous les clients de la base vont être mis dans le contexte de
persistance ;
• ligne 15 : pour chacun d'eux on fait l'opération [Remove] qui les supprime du contexte. En fait, ils sont toujours dans le
contexte mais dans un état " supprimé " ;
• lignes 18-21 : on fait la même chose pour les médecins ;
• ligne 23 : on sauvegarde le contexte de persistance en base.
• qui ont une clé primaire null font l'objet d'une opération SQL INSERT ;
• dans un état " supprimé " font l'objet d'une opération SQL DELETE ;
• dans un état " modifié " font l'objet d'une opération SQL UPDATE ;
Comme nous le constaterons ultérieurement, ces opérations SQL se font à l'intérieur d'une transaction. Si l'une d'elles échoue, tout
ce qui a été fait précédemment est défait.
[Link]
51/171
Faisons du programme [Erase] le nouvel objet de démarrage du projet [1] puis exécutons le projet.
Vérifions la base. On constatera que toutes les tables sont vides [2]. C'est étonnant, car nous avions demandé simplement la
suppression des médecins et des clients. C'est par le jeu des clés étrangères que les autres tables ont été vidées en cascade.
La définition de la clé étrangère de la table [CRENEAUX] vers la table [MEDECINS] a été définie comme suit par le provider
d'EF 5 :
1
3
4
5
Les contraintes de clé étrangères de la table [RVS] sont définies de façon analogue :
• lignes 1-6 : supprimer un client supprimera là également les rendez-vous qui lui sont associés ;
[Link]
52/171
4. ON UPDATE NO ACTION
5. ON DELETE CASCADE
6. GO
• lignes 1-6 : supprimer un créneau supprimera également tous les rendez-vous qui lui sont associés.
1. using [Link];
2. using [Link];
3.
4. namespace RdvMedecins_01
5. {
6. class Fill
7. {
8. static void Main(string[] args)
9. {
10. using (var context = new RdvMedecinsContext())
11. {
12. // on vide la base actuelle
13. foreach (var client in [Link])
14. {
15. [Link](client);
16. }
17. foreach (var medecin in [Link])
18. {
19. [Link](medecin);
20. }
21. // on la réinitialise
22. // les clients
23. Client[] clients ={
24. new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
25. new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
26. new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
27. new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
28. };
29. foreach (Client client in clients)
30. {
31. [Link](client);
32. }
33. // les médecins
34. Medecin[] medecins ={
35. new Medecin { Titre = "Mme", Nom = "Pelissier", Prenom = "Marie" },
36. new Medecin { Titre = "Mr", Nom = "Bromard", Prenom = "Jacques" },
37. new Medecin { Titre = "Mr", Nom = "Jandot", Prenom = "Philippe" },
38. new Medecin { Titre = "Melle", Nom = "Jacquemot", Prenom = "Justine" }
[Link]
53/171
39. };
40. foreach (Medecin medecin in medecins)
41. {
42. [Link](medecin);
43. }
44. // les créneaux horaires
45. Creneau[] creneaux ={
46. new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[0]},
47. new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[0]},
48. new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[0]},
49. new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[0]},
50. new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[0]},
51. new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[0]},
52. new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[0]},
53. new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[0]},
54. new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[0]},
55. new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[0]},
56. new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[0]},
57. new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[0]},
58. new Creneau{ Hdebut=14,Mdebut=0,Hfin=14,Mfin=20,Medecin=medecins[0]},
59. new Creneau{ Hdebut=14,Mdebut=20,Hfin=14,Mfin=40,Medecin=medecins[0]},
60. new Creneau{ Hdebut=14,Mdebut=40,Hfin=15,Mfin=0,Medecin=medecins[0]},
61. new Creneau{ Hdebut=15,Mdebut=0,Hfin=15,Mfin=20,Medecin=medecins[0]},
62. new Creneau{ Hdebut=15,Mdebut=20,Hfin=15,Mfin=40,Medecin=medecins[0]},
63. new Creneau{ Hdebut=15,Mdebut=40,Hfin=16,Mfin=0,Medecin=medecins[0]},
64. new Creneau{ Hdebut=16,Mdebut=0,Hfin=16,Mfin=20,Medecin=medecins[0]},
65. new Creneau{ Hdebut=16,Mdebut=20,Hfin=16,Mfin=40,Medecin=medecins[0]},
66. new Creneau{ Hdebut=16,Mdebut=40,Hfin=17,Mfin=0,Medecin=medecins[0]},
67. new Creneau{ Hdebut=17,Mdebut=0,Hfin=17,Mfin=20,Medecin=medecins[0]},
68. new Creneau{ Hdebut=17,Mdebut=20,Hfin=17,Mfin=40,Medecin=medecins[0]},
69. new Creneau{ Hdebut=17,Mdebut=40,Hfin=18,Mfin=0,Medecin=medecins[0]},
70. new Creneau{ Hdebut=8,Mdebut=0,Hfin=8,Mfin=20,Medecin=medecins[1]},
71. new Creneau{ Hdebut=8,Mdebut=20,Hfin=8,Mfin=40,Medecin=medecins[1]},
72. new Creneau{ Hdebut=8,Mdebut=40,Hfin=9,Mfin=0,Medecin=medecins[1]},
73. new Creneau{ Hdebut=9,Mdebut=0,Hfin=9,Mfin=20,Medecin=medecins[1]},
74. new Creneau{ Hdebut=9,Mdebut=20,Hfin=9,Mfin=40,Medecin=medecins[1]},
75. new Creneau{ Hdebut=9,Mdebut=40,Hfin=10,Mfin=0,Medecin=medecins[1]},
76. new Creneau{ Hdebut=10,Mdebut=0,Hfin=10,Mfin=20,Medecin=medecins[1]},
77. new Creneau{ Hdebut=10,Mdebut=20,Hfin=10,Mfin=40,Medecin=medecins[1]},
78. new Creneau{ Hdebut=10,Mdebut=40,Hfin=11,Mfin=0,Medecin=medecins[1]},
79. new Creneau{ Hdebut=11,Mdebut=0,Hfin=11,Mfin=20,Medecin=medecins[1]},
80. new Creneau{ Hdebut=11,Mdebut=20,Hfin=11,Mfin=40,Medecin=medecins[1]},
81. new Creneau{ Hdebut=11,Mdebut=40,Hfin=12,Mfin=0,Medecin=medecins[1]},
82. };
83. foreach (Creneau creneau in creneaux)
84. {
85. [Link](creneau);
86. }
87. // les Rdv
88. [Link](new Rv { Jour = new [Link](2012, 10, 8), Client =
clients[0], Creneau = creneaux[0] });
89. // on sauve le contexte de persistance
90. [Link]();
91. }
92. }
93. }
94. }
[Link]
54/171
• ligne 90 : les changements opérés sur le contexte sont synchronisés avec la base. Celle-ci va faire l'objet d'une série
d'opérations SQL DELETE suivie d'une série d'opérations SQL INSERT ;
On fait du programme [Fill], le nouvel objet de démarrage du projet [1] puis on exécute ce dernier.
Pour exister, LINQ s'appuie sur de nombreuses extensions faites aux langages .NET. Celles-ci peuvent être utilisées en-dehors de
LINQ. Nous n'allons pas les présenter mais simplement donner deux références où le lecteur trouvera une description approfondie
de LINQ :
• LINQ in Action, Fabrice Marguerie, Steve Eichert, Jim Wooley aux éditions Manning ;
• LINQ pocket reference, Joseph et Ben Albahari aux éditions O'Reilly.
J'ai lu le premier et l'ai trouvé excellent. Je n'ai pas lu le second mais ai lu des mêmes auteurs " C# 3.0 in a nutshell " à la sortie de
LINQ. J'ai trouvé ce livre très au-dessus de la moyenne des livres que j'ai l'habitude de lire. Il semble que les autres livres de ces
deux auteurs soient du même niveau. Nous allons par ailleurs utiliser LINQPad, un outil d'apprentissage de LINQ écrit par Joseph
Albahari.
Nous allons afficher les entités présentes dans la base. Pour cela, nous ajoutons à leurs classes deux méthodes d'affichage.
Commençons par l'entité [Medecin] :
1. // un médecin
2. public class Medecin
3. {
4. // data
5. [Key]
6. [Column("ID")]
7. public virtual int? Id { get; set; }
8. [Required]
9. [MaxLength(5)]
10. [Column("TITRE")]
11. public virtual string Titre { get; set; }
12. [Required]
13. [MaxLength(30)]
14. [Column("NOM")]
15. public virtual string Nom { get; set; }
16. [Required]
17. [MaxLength(30)]
18. [Column("PRENOM")]
[Link]
55/171
19. public virtual string Prenom { get; set; }
20. // les créneaux horaires du médecin
21. public ICollection<Creneau> Creneaux { get; set; }
22. [Column("TIMESTAMP")]
23. [Timestamp]
24. public virtual byte[] Timestamp { get; set; }
25.
26. // signature
27. public override string ToString()
28. {
29. return [Link]("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom,
dump(Timestamp));
30. }
31. // signature courte
32. public string ShortIdentity()
33. {
34. return ToString();
35. }
36.
37. // utilitaire
38. private string dump(byte[] timestamp){
39. string str = "";
40. foreach (byte b in timestamp)
41. {
42. str += b;
43. }
44. return str;
45. }
46. }
• lignes 27-30 : la méthode ToString de la classe. On notera qu'elle n'affiche pas la collection de la ligne 21 ;
• lignes 32-37 : la méthode ShortIdentity qui fait la même chose.
Il nous faut ici expliquer les notions de Lazy et Eager Loading pour mesurer l'impact des deux méthodes précédentes. Nous
avons vu qu'une entité pouvait avoir des dépendances sur une autre entité. Elles sont de deux natures :
• de un à plusieurs, comme ci-dessus où un médecin est relié à plusieurs créneaux horaires ;
• de plusieurs à un, comme dans l'entité [Creneau] ci-dessous où un plusieurs créneaux sont reliés au même médecin ;
Lorsque les dépendances sont chargées en même temps que les entités auxquelles elles sont attachées, on parle d' Eager Loading.
Sinon, on parle de Lazy Loading : les dépendances ne sont chargées que lorsqu'elles sont référencées la première fois. Par défaut,
EF 5 utilise le Lazy Loading : les dépendances ne sont pas chargées en même temps que l'entité.
[Link]
56/171
7. return [Link]("Medecin[{0},{1},{2},{3},{4}]", Id, Titre, Prenom, Nom,
dump(Timestamp));
8. }
9. // signature courte
10. public string ShortIdentity()
11. {
12. return ToString();
13. }
La méthode [ToString] n'affiche pas la dépendance [Creneaux] de la ligne 2. Si elle l'avait fait, elle aurait alors forcé le chargement
de tous les créneaux du médecin avant son exécution. C'est pour éviter ce chargement coûteux que la dépendance n'a pas été
incluse dans la signature de l'entité. De façon générale, nous allons inclure deux signatures dans chaque entité :
• une méthode ToString qui affichera l'entité et ses éventuelles dépendances plusieurs à un. Comme il vient d'être
expliqué cela provoquera le chargement de la dépendance ;
• une méthode ShortIdentity qui ne référencera aucune dépendance. Il n'y aura donc aucun chargement de dépendance ;
L'entité [Client] :
L'entité [Creneau] :
[Link]
57/171
17. }
18. // signature courte
19. public string ShortIdentity()
20. {
21. return [Link]("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut, Mdebut,
Hfin, Mfin, Timestamp, MedecinId, dump(Timestamp));
22. }
23. }
• ligne 16 : la méthode [ToString] référence la dépendance de la ligne 9. Cela va forcer son chargement ;
• ligne 11 : la dépendance [Rvs] n'est pas référencée. Elle ne sera pas chargée ;
• lignes 21-22 : la méthode [ShortIdentity] ne référence plus la référence [Medecin] de la ligne 9. Celle-ci ne sera donc pas
chargée.
L'entité [Rv] :
1. public class Rv
2. {
3. // data
4. ...
5. [Column("CLIENT_ID")]
6. virtual public int ClientId { get; set; }
7. [ForeignKey("ClientId")]
8. [Required]
9. public virtual Client Client { get; set; }
10. [Column("CRENEAU_ID")]
11. virtual public int CreneauId { get; set; }
12. [ForeignKey("CreneauId")]
13. [Required]
14. public virtual Creneau Creneau { get; set; }
15.
16. // signature
17. public override string ToString()
18. {
19. return [Link]("Rv[{0},{1},{2},{3},{4}]", Id, Jour, Client, Creneau,
dump(Timestamp));
20. }
21. // signature courte
22. public string ShortIdentity()
23. {
24. return [Link]("Rv[{0},{1},{2},{3},{4}]", Id, Jour, ClientId, CreneauId,
dump(Timestamp));
25. }
26.
27. }
• lignes 17-20 : la méthode [ToString] référence les dépendances des lignes 9 et 14. Cela va forcer leur chargement ;
• lignes 17-20 : la méthode [ShortIdentity] évite cela et donc les dépendances ne seront pas chargées.
En conclusion, on prêtera attention aux méthodes ToString des entités. Si on n'y prête pas attention, afficher une table peut charger
la moitié de la base si la table a de nombreuses dépendances.
1. using [Link];
2. using [Link];
3. using System;
4. using [Link];
5.
6. namespace RdvMedecins_01
7. {
8. class Dump
9. {
[Link]
58/171
10. static void Main(string[] args)
11. {
12. // dump de la base
13. using (var context = new RdvMedecinsContext())
14. {
15. // les clients
16. [Link]("Clients--------------------------------------");
17. var clients = from client in [Link] select client;
18. foreach (Client client in clients)
19. {
20. [Link](client);
21. }
22. // les médecins
23. [Link]("Médecins--------------------------------------");
24. var medecins = from medecin in [Link] select medecin;
25. foreach (Medecin medecin in medecins)
26. {
27. [Link](medecin);
28. }
29. // les créneaux horaires
30. [Link]("Créneaux horaires--------------------------------------");
31. var creneaux = from creneau in [Link] select creneau;
32. foreach (Creneau creneau in creneaux)
33. {
34. [Link](creneau);
35. }
36. // les Rdvs
37. [Link]("Rendez-vous--------------------------------------");
38. var rvs = from rv in [Link] select rv;
39. foreach (Rv rv in rvs)
40. {
41. [Link](rv);
42. }
43. }
44. }
45. }
46. }
Nous allons expliquer les lignes 17-21 qui affiche les entités [Client]. L'explication donnée vaudra pour les autres entités.
1. // les clients
2. [Link]("Clients--------------------------------------");
3. var clients = from client in [Link] select client;
4. foreach (Client client in clients)
5. {
6. [Link](client);
7. }
• ligne 3 : le mot clé var a été introduit avec C# 3.0. Il permet d'éviter d'indiquer le type précis d'une variable. Le
compilateur déduit alors celui-ci du type de l'expression affectée à la variable ;
• ligne 3 : l'expression affectée à la variable clients est une requête LINQ to Entity. On y reconnaît des mots clés du langage
SQL portés dans LINQ. La syntaxe utilisée ici est la suivante :
La collection va être parcourue et pour chaque élément de celle-ci, la variable va être évaluée. Ceci n'est fait que lorsque la
variable [clients] de la ligne 3 va être énumérée par le for / each des lignes 4-7. Tant que ceci n'est pas fait, la variable
[clients] n'est qu'une requête non évaluée ;
• ligne 4 : la requête [clients] est énumérée. Cela va forcer l'évaluation de la requête. Les lignes de la table [CLIENTS] vont
être amenées tour à tour dans le contexte de persistance ;
[Link]
59/171
• ligne 6 : la méthode [ToString] de l'entité [Client] est utilisée pour l'affichage. Il n'y a aucun chargement de dépendances ;
• lignes 24-28 : les lignes de la table [MEDECINS] sont amenées dans le contexte de persistance et affichées. Il n'y a aucun
chargement de dépendances ;
• lignes 31-35 : les lignes de la table [CRENEAUX] sont amenées dans le contexte de persistance et affichées. Nous avons
vu que la méthode [ToString] de cette entité affichait la dépendance [Medecin]. Or celle-ci est déjà chargée. Il n'y aura
donc pas de nouveau chargement ;
• lignes 38-42 : les lignes de la table [RVS] sont amenées dans le contexte de persistance et affichées. Nous avons vu que la
méthode [ToString] de cette entité affichait les dépendances [Client] et [Creneau]. Or celles-ci sont déjà chargées. Il n'y
aura donc pas de nouveaux chargements.
On notera que l'ordre d'affichage n'est pas neutre. Si on avait voulu afficher d'abord les entités [R v], la méthode [ToString] de celle-
ci aurait provoqué le chargement des entités [Client] et [Creneau] liées à ces rendez-vous. Les autres n'auraient pas été chargées. Elle
l'auraient été plus tard dans un autre affichage. Cela a un impact sur les performances. Le code précédent a besoin de quatre ordres
SQL pour faire afficher toutes les entités. Supposons maintenant qu'on exploite d'abord la tables [RVS] des rendez-vous. Une
première requête SQL est nécessaire pour la table [RVS]. Ensuite, la méthode [ToString] de l'entité [Rv] va provoquer le
chargement éventuel des entités [Client] et [Creneau] associées. Il faut une requête SQL pour chacune. En supposant qu'il y a N2
clients et N3 créneaux et que toutes ces entités sont référencées dans la table [RVS], l'affichage de celle-ci nécessitera 1+N2+N3
requêtes SQL. Donc, on n'est moins performant que dans la version étudiée. Pour afficher la table [RVS] avec ses dépendances, une
jointure entre tables serait nécessaire. Il est possible de la réaliser avec LINQ. Nous y reviendrons sur un exemple. Pour l'instant,
nous nous rappellerons que nous devons prêter attention aux requêtes SQL sous-jacentes à notre code LINQ.
On paramètre le projet pour exécuter ce nouveau code [1] et [2] puis on l'exécute :
2
1
1. Clients--------------------------------------
2. Client[9,Mr,Jules,Martin,000000844]
3. Client[10,Mme,Christine,German,000000845]
4. Client[11,Mr,Jules,Jacquard,000000846]
5. Client[12,Melle,Brigitte,Bistrou,000000847]
6. Médecins--------------------------------------
7. Medecin[9,Mme,Marie,Pelissier,000000848]
8. Medecin[10,Mr,Jacques,Bromard,000000873]
9. Medecin[11,Mr,Philippe,Jandot,000000886]
10. Medecin[12,Melle,Justine,Jacquemot,000000887]
11. Créneaux horaires--------------------------------------
12. Creneau[73,8,0,8,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000849]
13. Creneau[74,8,20,8,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000850]
14. Creneau[75,8,40,9,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000851]
15. Creneau[76,9,0,9,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000852]
16. Creneau[77,9,20,9,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000853]
17. Creneau[78,9,40,10,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000854]
18. Creneau[79,10,0,10,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000855]
19. Creneau[80,10,20,10,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000856]
20. Creneau[81,10,40,11,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000857]
21. Creneau[82,11,0,11,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000858]
22. Creneau[83,11,20,11,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000859]
23. Creneau[84,11,40,12,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000860]
24. Creneau[85,14,0,14,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000861]
25. Creneau[86,14,20,14,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000862]
[Link]
60/171
26. Creneau[87,14,40,15,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000863]
27. Creneau[88,15,0,15,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000864]
28. Creneau[89,15,20,15,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000865]
29. Creneau[90,15,40,16,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000866]
30. Creneau[91,16,0,16,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000867]
31. Creneau[92,16,20,16,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000868]
32. Creneau[93,16,40,17,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000869]
33. Creneau[94,17,0,17,20, Medecin[9,Mme,Marie,Pelissier,000000848],000000870]
34. Creneau[95,17,20,17,40, Medecin[9,Mme,Marie,Pelissier,000000848],000000871]
35. Creneau[96,17,40,18,0, Medecin[9,Mme,Marie,Pelissier,000000848],000000872]
36. Creneau[97,8,0,8,20, Medecin[10,Mr,Jacques,Bromard,000000873],000000874]
37. Creneau[98,8,20,8,40, Medecin[10,Mr,Jacques,Bromard,000000873],000000875]
38. Creneau[99,8,40,9,0, Medecin[10,Mr,Jacques,Bromard,000000873],000000876]
39. Creneau[100,9,0,9,20, Medecin[10,Mr,Jacques,Bromard,000000873],000000877]
40. Creneau[101,9,20,9,40, Medecin[10,Mr,Jacques,Bromard,000000873],000000878]
41. Creneau[102,9,40,10,0, Medecin[10,Mr,Jacques,Bromard,000000873],000000879]
42. Creneau[103,10,0,10,20, Medecin[10,Mr,Jacques,Bromard,000000873],000000880]
43. Creneau[104,10,20,10,40, Medecin[10,Mr,Jacques,Bromard,000000873],000000881]
44. Creneau[105,10,40,11,0, Medecin[10,Mr,Jacques,Bromard,000000873],000000882]
45. Creneau[106,11,0,11,20, Medecin[10,Mr,Jacques,Bromard,000000873],000000883]
46. Creneau[107,11,20,11,40, Medecin[10,Mr,Jacques,Bromard,000000873],000000884]
47. Creneau[108,11,40,12,0, Medecin[10,Mr,Jacques,Bromard,000000873],000000885]
48. Rendez-vous--------------------------------------
49. Rv[3,08/10/2012 [Link],Client[9,Mr,Jules,Martin,000000844],Creneau[73,8,0,8,20
50. , Medecin[9,Mme,Marie,Pelissier,000000848],000000849],000000888]
51. Appuyez sur une touche pour continuer...
LINQPad est disponible à l'URL suivante [[Link] Une fois installé, nous le lançons [1] :
3
1 4
2
Le débutant LINQ pourra s'initier avec les exemples de l'onglet [Samples] [2] qui montrent de très nombreux exemples.
Sélectionnons le [3] qui s'affiche alors dans une autre fenêtre [4]. Le code complet de l'exemple est celui-ci :
Les lignes 3-5 sont un exemple de requête LINQ to Object. La requête LINQ suit la syntaxe :
• variable désigne l'élément courant de la collection. Dans notre exemple, cette collection est la liste de mots résultats de la
chaîne splittée ;
• la collection est ordonnée selon le paramètre élément1 de orderby. Dans notre exemple, la collection de mots sera
ordonnée selon leur longueur ;
[Link]
61/171
• le mot clé select désigne ce qu'on veut retirer de l'élément courant variable de la collection. Dans notre exemple, ce sera le
mot.
3
1
• en [1] : une expression LINQ est exécutée par [F5] ou bien via le bouton d'exécution ;
• en [2] : l'affichage. Les mots sont affichés dans l'ordre de leur longueur. Ce simple exemple montre la puissance de LINQ ;
• en [3], il est possible de télécharger d'autres exemples, notamment ceux du livre " LINQ in action " [4] ;
4 5
[Link]
62/171
• ligne 4 : une nouvelle requête LINQ avec de nouveaux mots clés ;
• ligne 5 : la collection requêtée est le tableau de mots de la ligne 1 ;
• ligne 6 : la collection est triée dans l'ordre alphabétique des mots ;
• ligne 7 : la collection est regroupée dans (mot clé into) une nouvelle collection lengthGroups. [Link] représente le
facteur de regroupement (mot clé by), ici la longueur des mots. lengthGroups rassemble les mots ayant le même facteur de
regroupement donc la même longueur ;
• ligne 8 : la collection lengthGroups est ordonnée par clé de regroupement descendant, donc ici par taille décroissante des
mots ;
• ligne 9 : de cette collection, on produit de nouveaux objets (classes anonymes) ayant deux champs :
• Length : la longueur des mots,
• Words : les mots ayant cette longueur ;
Ici, on voit particulièrement l'intérêt du mot clé var de la ligne 4. Parce qu'on a utilisé une classe anonyme ligne 9, on ne
sait pas désigner le type de la variable groups. Le compilateur lui va donner un nom interne à la classe anonyme et va typer
avec, la variable groups. Il sera capable ensuite de dire si la variable groups est utilisée correctement
• ligne 12 : parcours de la requête de la ligne 4. Ce n'est qu'à ce moment qu'elle est évaluée. On se rappelle que son
exécution va produire une collection d'objets précisés ligne 9 ;
• ligne 14 : on affiche la propriété Length de l'élément courant, donc une longueur de mots ;
• lignes 15-17 : on affiche chaque élément de la collection de la propriété Words, donc l'ensemble des mots ayant la longueur
affichée précédemment.
Lorsque nous exécutons cette requête, nous obtenons le résultat suivant dans LINQPad :
Maintenant que nous avons vu quelques exemples de requêtes LINQ to Object, voyons des requêtes LINQ to Entity qui vont nous
permettre de requêter des bases de données. Nous allons tout d'abord nous connecter à la base de données SQL Server que nous
avons créée et remplie :
1
2
[Link]
63/171
• en [1], on ajoute une connexion à une base de données ;
• en [2], les moyens d'accès à la source de données. Pour accéder à la base SQL Server, nous utiliserons [LINQPad Driver] ;
• en [3], il est également possible de récupérer un contexte de persistance [DbContext] défini dans un assembly .exe ou .dll
(option 3). Malheureusement, à ce jour (8 octobre 2012), Entity Framework 5 n'est pas supporté ;
• en [4], il est possible de télécharger des pilotes pour d'autres SGBD que SQL Server ;
• en [5], on téléchargera le driver pour les SGBD MySQL et Oracle ;
[Link]
64/171
13
8
10
11 12
Les entités ont été créées à partir de la table [rdvmedecins-ef]. Ce sont les suivantes :
[Link]
65/171
3
On notera que les noms des propriétés ci-dessus sont différentes des noms que nous avons utilisés jusqu'à maintenant. Cela importe
peu. Nous voulons juste apprendre les principes de base du requêtage sur base de données.
Voyons comment nous pouvons requêter cette base d'entités. Par exemple, nous voulons la liste des médecins ordonnée par leur
TITRE et NOM :
[Link]
66/171
4
Lire le code lambda équivalent au code texte que nous tapons habituellement est une bonne façon de l'apprendre ;
• en [5], l'ordre SQL émis sur la base. Là encore, on lira attentivement ce code. Il permet d'évaluer le coût réel d'une requête
LINQ.
Dans la suite, nous présentons quelques exemples de requête LINQ. A chaque fois, nous montrons les résultats affichés et les codes
lambda et SQL équivalents. Pour comprendre ces requêtes, il faut rappeler les relations plusieurs à un qui relient les entités les unes
aux autres. C'est par elles qu'on navigue d'une entité à l'autre. On les appelle parfois des propriétés navigationnelles.
MEDECINS CLIENTS
MEDECIN CLIENT
CRENEAU RVS
CRENEAUXes
// les clients dont le titre est Mr classés par ordre décroissant des noms
Résultats :
[Link]
67/171
LINQ from client in CLIENTS where [Link]=="Mr" orderby [Link] descending
select client
Lambda CLIENTS
.Where (client => ([Link] == "Mr"))
.OrderByDescending (client => [Link])
SQL -- Region Parameters
DECLARE @p0 NVarChar(1000) = 'Mr'
-- EndRegion
SELECT [t0].[ID], [t0].[TITRE], [t0].[NOM], [t0].[PRENOM], [t0].[TIMESTAMP]
FROM [CLIENTS] AS [t0]
WHERE [t0].[TITRE] = @p0
ORDER BY [t0].[NOM] DESC
Résultats (partiels) :
[Link]
68/171
LINQ from creneau in CRENEAUXes select new { hd=[Link], md=[Link],
hf=[Link], mf=[Link], medecin=[Link]}
Lambda
Résultats :
[Link]
69/171
LINQ from rv in RVS select new { rv=[Link], medecin=[Link]}
Lambda
Résultats :
LINQ
Lambda
Il n'y a pas de requête LINQ pour cette demande. Il faut passer par des expressions lambda. Celle-ci se lit de la façon suivante : je
prends la collection des médecins et je ne garde (Where) que les médecins tels que je ne suis pas capable de trouver dans la
collection des rendez-vous un rendez-vous avec ce médecin.
[Link]
70/171
// créneaux horaires de Mme Pélissier
Résultats (partiels) :
Résultats :
[Link]
71/171
LINQ (from rv in RVS where [Link]=="Pelissier" && [Link]==new
DateTime(2012,10,08) select rv).Count()
Lambda
SQL -- Region Parameters
DECLARE @p0 NVarChar(1000) = 'Pelissier'
DECLARE @p1 DateTime = '2012-10-08 [Link].000'
-- EndRegion
SELECT COUNT(*) AS [value]
FROM [RVS] AS [t0]
INNER JOIN [CRENEAUX] AS [t1] ON [t1].[ID] = [t0].[CRENEAU_ID]
INNER JOIN [MEDECINS] AS [t2] ON [t2].[ID] = [t1].[MEDECIN_ID]
WHERE ([t2].[NOM] = @p0) AND ([t0].[JOUR] = @p1)
// liste des clients ayant pris Rdv avec Mme Pélissier le 08/10/2012
Résultats :
Résultats :
[Link]
72/171
LINQ from creneau in CRENEAUXes
group creneau by [Link] into creneauxMedecin
select new { nom=[Link], prenom=[Link],
nbRv=[Link]()}
Lambda
[Link]
73/171
Le code [ModifyAttachedEntity] illustre la modification d'une entité attachée au contexte :
1. using System;
2. using [Link];
3. using [Link];
4. using [Link];
5. using [Link];
6.
7. namespace RdvMedecins_01
8. {
9. class ModifyAttachedEntity
10. {
11. static void Main(string[] args)
12. {
13. Client client1, client2, client3;
14. // 1er contexte
15. using (var context = new RdvMedecinsContext())
16. {
17. // on vide la base actuelle
18. foreach (var client in [Link])
19. {
20. [Link](client);
21. }
22. foreach (var medecin in [Link])
23. {
24. [Link](medecin);
25. }
26. // on ajoute un client
27. client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
28. [Link](client1);
29. // suivi
30. [Link]("client1--avant");
31. [Link](client1);
32. // sauvegarde contexte
33. [Link]();
34. // suivi
35. [Link]("client1--après");
36. [Link](client1);
37. }
38. // 2ième contexte
39. using (var context = new RdvMedecinsContext())
40. {
41. // on récupère client1 dans client2
42. client2 = [Link]([Link]);
43. // suivi
44. [Link]("client2");
45. [Link](client2);
46. // on modifie client2
47. [Link] = "yy";
48. // sauvegarde contexte
49. [Link]();
50. }
51. // 3ième contexte
52. using (var context = new RdvMedecinsContext())
53. {
54. // on récupère client2 dans client3
55. client3 = [Link]([Link]);
56. // suivi
57. [Link]("client3");
58. [Link](client3);
59. }
60. }
[Link]
74/171
61. }
62. }
1. client1--avant
2. Client[,xx,xx,xx,]
3. client1--après
4. Client[16,xx,xx,xx,000000132209]
• ligne 37 : le contexte de persistance est fermé. Les entités qu'il contenait deviennent " détachées ". Elles existent en tant
qu'objets mais pas en tant qu'entités attachées à un contexte de persistance ;
• ligne 39 : on redémarre un nouveau contexte vide ;
• ligne 42 : on récupère le client directement dans la base via sa clé primaire. Il est alors amené dans le contexte. S'il n'est pas
trouvé, la méthode Find rend le pointeur null ;
• lignes 48-49 : on l'affiche ;
1. client2
2. Client[16,xx,xx,xx,000000132209]
• ligne 47 : on le modifie ;
• ligne 49 : on synchronise le contexte avec la base. EF va détecter que certains éléments du contexte ont été modifiés
depuis qu'ils y ont été amenés. Pour ces éléments, il va générer des ordres SQL UPDATE sur la base. Donc ici, la
synchronisation va consister en un unique ordre UPDATE ;
• ligne 50 : le deuxième contexte est fermé. L'entité client2 qui était attachée au contexte devient maintenant détachée de
celui-ci ;
• ligne 52 : on ouvre un troisième contexte vide ;
• ligne 55 : on y amène de nouveau l'unique client de la base. On veut voir si la modification faite sur lui dans le contexte a
été répercutée dans la base ;
• lignes 57-58 : on affiche le client. Cela donne le résultat suivant :
1. client3
2. Client[16,xx,xx,yy,000000132210]
Le nom du client a bien été modifié en base. On notera avec intérêt que son timestamp a été mis à jour.
• ligne 59 : on ferme le contexte. Au passage, on notera que contrairement aux deux fois précédentes, on n'a pas eu besoin
auparavant de synchroniser le contexte avec la base (SaveChanges) car le contexte n'avait pas été modifié.
[Link]
75/171
Application Couche d'accès Framework Connecteur
[Link] aux données [EF5] [[Link]] SGBD BD
[DAO] 1 2
SPRING
La couche [DAO] utilise l'ORM EF5 pour accéder aux données. Nous avons les briques de base de cette couche. Chaque méthode
ouvrira un contexte de persistance, fera dessus les opérations nécessaires (insertion, modification, suppression, requêtage) puis le
fermera. Les entités gérées par la couche [DAO] vont remonter jusqu'à la couche web. Dans cette couche, elles sont hors contexte
de persistance donc détachées. Dans la couche web, un utilisateur peut modifier ces entités (ajout, modification, suppression).
Lorsqu'elles reviennent à la couche [DAO], elles sont toujours détachées. Or la couche [DAO] va devoir répercuter les
modifications faites par l'utilisateur dans la base. Il va donc devoir travailler avec des entités détachées. Voyons les trois cas
possibles :
C'est le cas normal pour un ajout. Il suffit d'ajouter (Add) l'entité détachée au contexte en s'assurant qu'elle a une clé primaire égale
à null.
[DbContext].Entry(entitédétachée).State=[Link] ;
• ligne 1 : on met dans le contexte l'entité de même clé primaire que l'entité détachée ;
• ligne 2 : on la supprime :
On notera que cela nécessite en base un SELECT suivi d'un DELETE alors que normalement le seul DELETE est suffisant. On
peut suivre également l'exemple de la modification d'une entité détachée et écrire :
[DbContext].Entry(entitédétachée).State=[Link] ;
Comme je n'ai pas pu mettre en oeuvre de logs sur les opérations SQL faites sur la base, je ne sais pas si une méthode est à
conseiller plutôt que l'autre.
Voici un exemple :
[Link]
76/171
Le code du programme [ModifyDetachedEntities] est le suivant :
1. using System;
2. using [Link];
3. using [Link];
4. using [Link];
5.
6. namespace RdvMedecins_01
7. {
8. class ModifyDetachedEntities
9. {
10. static void Main(string[] args)
11. {
12. Client client1;
13.
14. // on vide la base actuelle
15. Erase();
16. // on ajoute un client
17. using (var context = new RdvMedecinsContext())
18. {
19. // création client
20. client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
21. // ajout du client au contexte
22. [Link](client1);
23. // on sauvegarde le contexte
24. [Link]();
25. }
26. // affichage base
27. Dump("1-----------------------------");
28. // client1 n'est pas dans le contexte - on le modifie
29. [Link] = "y";
30. // nouveau contexte
31. using (var context = new RdvMedecinsContext())
32. {
33. // ici, on a un contexte vide
34. // on met client1 dans le contexte dans un état modifié
35. [Link](client1).State = [Link];
36. // on sauvegarde le contexte
37. [Link]();
38. }
39. // affichage base
40. Dump("2-----------------------------");
41. // suppression entité hors contexte
42. using (var context = new RdvMedecinsContext())
43. {
44. // ici, on a un nouveau contexte vide
45. // on met client1 dans le contexte dans un état supprimé
46. [Link](client1).State = [Link];
[Link]
77/171
47. // on sauvegarde le contexte
48. [Link]();
49. }
50. // affichage base
51. Dump("3-----------------------------");
52. }
53.
54. static void Erase()
55. {
56. // vide la base
57. using (var context = new RdvMedecinsContext())
58. {
59. foreach (var client in [Link])
60. {
61. [Link](client);
62. }
63. foreach (var medecin in [Link])
64. {
65. [Link](medecin);
66. }
67. // on sauvegarde le contexte
68. [Link]();
69. }
70. }
71.
72. static void Dump(string str)
73. {
74. [Link](str);
75. // affiche la base
76. using (var context = new RdvMedecinsContext())
77. {
78. foreach (var rv in [Link])
79. {
80. [Link](rv);
81. }
82. foreach (var creneau in [Link])
83. {
84. [Link](creneau);
85. }
86. foreach (var client in [Link])
87. {
88. [Link](client);
89. }
90. foreach (var medecin in [Link])
91. {
92. [Link](medecin);
93. }
94. }
95. }
96. }
97. }
1-----------------------------
Client[20,x,x,x,0000011209]
• après la ligne 25, le contexte de perssitance n'existe plus. Il n'y a donc plus d'entités attachées. L'entité client1 est passée
dans l'état " détaché " ;
• ligne 29 : on modifie le nom de l'entité détachée ;
• ligne 31 : on ouvre un nouveau contexte vide ;
• ligne 35 : l'entité détachée client1 est mise dans le contexte dans un état " modifié " ;
[Link]
78/171
• ligne 37 : le contexte est synchronisé avec la base ;
• ligne 38 : il est fermé ;
• ligne 40 : la base est affichée ;
2-----------------------------
Client[20,x,x,y,0000011210]
Le nom du client a bien été modifié en base. On notera que le timestamp a été mis à jour ;
3-----------------------------
Maintenant, nous voyons les deux modes de chargement des dépendances d'une entité : Lazy et Eager Loading.
Medecin Client
Medecin Client
Creneau Rv
Creneau
Ci-dessus, l'entité [Creneau] a une propriété navigationnelle [[Link]] vers l'entité [Medecin]. On appelle cela une
dépendance. Nous avons vu qu'il y avait également des dépendances un à plusieurs. Le principe qui va être expliqué s'applique
également à elles.
Par défaut, EF 5 est en mode Lazy Loading : lorsqu'il amène une entité dans le contexte de persistance depuis la base, il n'amène
pas ses dépendances. Celles-ci seront amenées lorsqu'elles seront utilisées la première fois. C'est une mesure de bon sens. Si ce
n'était pas le cas, amener dans le contexte tous les rendez-vous amènerait d'après les dépendances ci-dessus :
• les entités [Creneau] liées aux rendez-vous ;
• les entités [Medecin] liées à ces créneaux ;
• les entités [Clients] liées aux rendez-vous.
Parfois cependant, on a besoin d'une entité et de ses dépendances. Nous allons illustrer les deux modes de chargement.
[Link]
79/171
Le code de [LazyEagerLoading] est le suivant :
1. using [Link];
2. using [Link];
3. using System;
4. using [Link];
5.
6. namespace RdvMedecins_01
7. {
8. class LazyEagerLoading
9. {
10. // les entités
11. static Medecin[] medecins;
12. static Client[] clients;
13. static Creneau[] creneaux;
14.
15. static void Main(string[] args)
16. {
17. // on initialise la base
18. InitBase();
19. [Link]("Initialisation terminée");
20. // eager loading
21. Creneau creneau;
22. int idCreneau = (int)creneaux[0].Id;
23. using (var context = new RdvMedecinsContext())
24. {
25. // creneau n° 0
26. creneau = [Link]("Medecin").Single<Creneau>(c => [Link] ==
idCreneau);
27. [Link]([Link]());
28. }
29. // affichage dépendance
30. try
31. {
32. [Link]("Médecin={0}", [Link]);
33. }
34. catch (Exception e)
35. {
36. [Link]("L'erreur 1 suivante s'est produite : {0}", e);
37. }
38. // lazy loading - mode par défaut
39. using (var context = new RdvMedecinsContext())
40. {
41. // creneau n° 0
42. creneau = [Link]<Creneau>(c => [Link] == idCreneau);
43. [Link]([Link]());
44. }
[Link]
80/171
45. // affichage dépendance
46. try
47. {
48. [Link]("Médecin={0}", [Link]);
49. }
50. catch (Exception e)
51. {
52. [Link]("L'erreur 2 suivante s'est produite : {0}", e);
53. }
54.
55. }
56.
57. static void InitBase()
58. {
59. // on initialise la base
60. using (var context = new RdvMedecinsContext())
61. {
62. // on vide la base actuelle
63. ...
64. // on initialise la base
65. // les clients
66. clients = new Client[] {
67. new Client { Titre = "Mr", Nom = "Martin", Prenom = "Jules" },
68. new Client { Titre = "Mme", Nom = "German", Prenom = "Christine" },
69. new Client { Titre = "Mr", Nom = "Jacquard", Prenom = "Jules" },
70. new Client { Titre = "Melle", Nom = "Bistrou", Prenom = "Brigitte" }
71. };
72. ...
73. // les Rdv
74. [Link](new Rv { Jour = new [Link](2012, 10, 8), Client =
clients[0], Creneau = creneaux[0] });
75. // on sauve le contexte de persistance
76. [Link]();
77. }
78. }
79. }
80. }
• ligne 18 : on part d'une base connue, celle utilisée jusqu'à maintenant. Après cette opération, les tableaux des lignes 11-13
sont remplis d'entités détachées ;
• lignes 21-22 : on s'intéresse au premier créneau et au médecin associé ;
• ligne 23 : nouveau contexte ;
• ligne 26 : on met le créneau dans le contexte avec sa dépendance (eager loading). Parce que ce n'est pas le mode par
défaut, il faut demander cette dépendance. C'est la méthode Include qui permet cela. Son paramètre est le nom de la
dépendance dans l'entité amenée dans le contexte. La requête qui amène l'entité dans le contexte utilise des expressions
lambda. La méthode Single permet de préciser une condition permettant de ramener une unique entité. Ici, on recherche
en base l'entité [Creneau] qui a la clé primaire du créneau n° 0 ;
• ligne 27 : on affiche l'entité ramenée. Rappelons les deux méthodes d'écriture utilisées dans les entités :
1. // signature
2. public override string ToString()
3. {
4. return [Link]("Creneau[{0},{1},{2},{3},{4}, {5},{6}]", Id, Hdebut, Mdebut,
Hfin, Mfin, Medecin, dump(Timestamp));
5. }
6.
7. // signature courte
8. public string ShortIdentity()
9. {
10. return [Link]("Creneau[{0},{1},{2},{3},{4}, {5}, {6}]", Id, Hdebut,
Mdebut, Hfin, Mfin, MedecinId, dump(Timestamp));
11. }
[Link]
81/171
• lignes 2-5 : la méthode [ToString] affiche la dépendance [Medecin]. Si celle-ci n'est pas déjà dans le contexte, elle sera
cherchée en base pour l'y mettre ;
• lignes 8-11 : la méthode [ShortIdentity] n'affiche pas la dépendance [Medecin]. Elle ne sera donc pas recherchée en base si
elle n'est pas dans le contexte ;
Initialisation terminée
Creneau[181,8,0,8,20, 21, 00000195150]
Médecin=Medecin[21,Mme,Marie,Pelissier,00000195149]
• lignes 39-44 : dans le cadre d'un nouveau contexte, le créneau n° 0 est de nouveau cherché en base et amené dans le
contexte. Ici, la dépendance [Medecin] n'est pas demandée explicitement. Elle ne sera donc pas amenée (Lazy Loading) ;
• ligne 43 : l'affichage de l'identité courte du créneau est la suivante :
Ici, il est important d'utiliser ShortIdentity au lieu de ToString pour afficher l'entité. Si on utilise ToString, la dépendance
[Medecin] va être affichée et pour cela elle va être cherchée en base. Or on ne veut pas cela.
EF a trouvé la dépendance [Medecin] absente. Il a voulu la charger mais le contexte étant fermé, cette opération n'était
plus possible. On mémorisera cette exception [[Link]] car est elle caractéristique du
chargement d'une dépendance en-dehors d'un contexte ouvert.
[Link]
82/171
7. [Required]
8. [MaxLength(5)]
9. [Column("TITRE")]
10. public virtual string Titre { get; set; }
11. [Required]
12. [MaxLength(30)]
13. [Column("NOM")]
14. public virtual string Nom { get; set; }
15. [Required]
16. [MaxLength(30)]
17. [Column("PRENOM")]
18. public virtual string Prenom { get; set; }
19. // les Rvs du client
20. public ICollection<Rv> Rvs { get; set; }
21. [Column("TIMESTAMP")]
22. [Timestamp]
23. public virtual byte[] Timestamp { get; set; }
24.
25. // signature
26. ...
27. }
Nous allons nous intéresser au champ [Timestamp] de la ligne 23. Nous savons que sa valeur est générée par le SGBD. Nous avons
dit également que l'annotation [Timestamp] de la ligne 22 faisait que EF 5 utilisait le champ annoté pour gérer les concurrences
d'accès aux entités. Rappelons ce qu'est une gestion de concurrence d'accès :
• un processus P1 lit une ligne L de la table [MEDECINS] au temps T1. La ligne a le timestamp TS1 ;
• un processus P2 lit la même ligne L de la table [MEDECINS] au temps T2. La ligne a le timestamp TS1 parce que le
processus P1 n'a pas encore validé sa modification ;
• le processus P1 valide sa modification de la ligne L. Le timestamp de la ligne L passe alors à TS2 ;
• le processus P2 valide sa modification de la ligne L. L'ORM lance alors une exception car le processus P2 a un timestamp
TS1 de la ligne L différent du timestamp TS2 trouvé en base.
On appelle cela la gestion optimiste des accès concurrents. Avec EF 5, un champ jouant ce rôle doit avoir l'un des deux attributs
[Timestamp] ou [ConcurrencyCheck]. SQL server a un type [timestamp]. Une colonne ayant ce type voit sa valeur
automatiquement générée par SQL Server à toute insertion / modification d'une ligne. Une telle colonne peut alors servir à gérer la
concurrence d'accès.
Nous allons illustrer cette concurrence d'accès avec deux threads qui vont en même temps modifier une même entité [Client] en
base. Le projet évolue comme suit :
1. using System;
[Link]
83/171
2. using [Link];
3. using [Link];
4. using [Link];
5. using [Link];
6. using [Link];
7.
8. namespace RdvMedecins_01
9. {
10.
11. // objet échangé avec les threads
12. class Data
13. {
14. public int Duree { get; set; }
15. public string Nom { get; set; }
16. public Client Client { get; set; }
17. }
18.
19. // programme de test
20. class AccèsConcurrents
21. {
22.
23. static void Main(string[] args)
24. {
25. Client client1;
26. using (var context = new RdvMedecinsContext())
27. {
28. // thread principal
29. [Link] = "main";
30. // on vide la base actuelle
31. foreach (var client in [Link])
32. {
33. [Link](client);
34. }
35. foreach (var medecin in [Link])
36. {
37. [Link](medecin);
38. }
39. // on ajoute un client
40. client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
41. [Link](client1);
42. // suivi
43. [Link]("{0} client1--avant sauvegarde du contexte",
[Link]);
44. [Link]([Link]());
45. // sauvegarde
46. [Link]();
47. // suivi
48. [Link]("{0} client1--après sauvegarde du contexte",
[Link]);
49. [Link]([Link]());
50. }
51. // on va modifier client1 avec deux threads
52. // thead t1
53. Thread t1 = new Thread(Modifie);
54. [Link] = "t1";
55. [Link](new Data { Duree = 5000, Nom = "yy", Client = client1 });
56. // thread t2
57. Thread t2 = new Thread(Modifie);
58. [Link] = "t2";
59. [Link](new Data { Duree = 5000, Nom = "zz", Client = client1 });
60. // on attend la fin des 2 threads
61. [Link]("Thread {0} -- début attente fin des deux threads",
[Link]);
[Link]
84/171
62. [Link]();
63. [Link]();
64. [Link]("Thread {0} -- fin attente fin des deux threads",
[Link]);
65. // on affiche la modification - une seule a du réussir
66. using (var context = new RdvMedecinsContext())
67. {
68. // on récupère client1 dans client2
69. Client client2 = [Link]([Link]);
70. [Link]("Thread {0} client2", [Link]);
71. [Link]("Thread {0} {1}", [Link],
[Link]());
72. }
73. }
74.
75. // thread
76. static void Modifie(object infos)
77. {
78. ...
79. }
Voyons maintenant, ce que font les deux threads t1 et t2. Ils exécutent la méthode [Modifie] suivante :
[Link]
85/171
9. [Link]("Début Thread {0}", [Link]);
10. // on récupère client1 dans client2
11. Client client2 = [Link]([Link]);
12. [Link]("Thread {0} client2", [Link]);
13. [Link]("Thread {0} {1}", [Link],
[Link]());
14. // on modifie client2
15. [Link] = [Link];
16. // on attend un peu
17. [Link]([Link]);
18. // on sauvegarde les changements
19. [Link]();
20. }
21. }
22. catch (Exception e)
23. {
24. // exception
25. [Link]("Thread {0} {1}", [Link], e);
26. }
27. // fin du thread
28. [Link]("Fin Thread {0}", [Link]);
29. }
[Link]
86/171
• ligne 4 : le client dans la base ;
• ligne 9 : le client tel qu'il est lu par le thread t2 ;
• ligne 11 : le client tel qu'il est lu par le thread t1. Les deux threads ont donc lu la même chose ;
• ligne 12 : le thread t2 se termine le premier. Il a donc pu faire sa mise à jour. Le nom a du passer à " zz " ;
• ligne 13 : le thread t1 lance une exception de type [[Link]]. EF a détecté qu'il
n'avait pas le bon timestamp ;
• ligne 21 : le thread t1 se termine à son tour ;
• ligne 22 : le thread principal a terminé son attente ;
• ligne 24 : le thread principal affiche le client en base. C'est bien le thread t2 qui a gagné. Le nom est " zz ". On notera que
le timestamp a changé.
Maintenant, examinons un autre aspect : la transaction qui encadre la synchronisation du contexte de persistance avec la base.
Nous allons procéder de la façon suivante : nous allons ajouter en même temps deux rendez-vous pour le même médecin, le même
jour et le même créneau horaire. On va voir ce qui se passe.
1. using System;
2. using [Link];
3. using [Link];
4. using [Link];
5.
6. namespace RdvMedecins_01
7. {
8.
9. // programme de test
10. class SynchronisationTransaction
11. {
12.
13. static void Main(string[] args)
14. {
15. using (var context = new RdvMedecinsContext())
16. {
[Link]
87/171
17. // on vide la base actuelle
18. foreach (var client in [Link])
19. {
20. [Link](client);
21. }
22. foreach (var medecin in [Link])
23. {
24. [Link](medecin);
25. }
26. [Link]();
27. }
28.
29. // on crée un client
30. Client client1 = new Client { Nom = "xx", Prenom = "xx", Titre = "xx" };
31. // on crée un médecin
32. Medecin medecin1 = new Medecin { Nom = "xx", Prenom = "xx", Titre = "xx" };
33. // on crée un créneau pour ce médecin
34. Creneau creneau1 = new Creneau { Hdebut = 8, Mdebut = 20, Hfin = 8, Mfin = 40,
Medecin = medecin1 };
35. // on crée deux Rv pour ce médecin et ce client, même jour, même créneau
36. Rv rv1 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10,
18) };
37. Rv rv2 = new Rv { Client = client1, Creneau = creneau1, Jour = new DateTime(2012, 10,
18) };
38. try
39. {
40. // on met tout ce petit monde dans le contexte de persistance
41. using (var context = new RdvMedecinsContext())
42. {
43. [Link](client1);
44. [Link](creneau1);
45. [Link](medecin1);
46. [Link](rv1);
47. [Link](rv2);
48. // on sauvegarde le contexte - on doit avoir une exception
49. // car la BD sous-jacente a une contrainte d'unicité empêchant
50. // d'avoir deux RDV même jour, même créneau
51. [Link]();
52. }
53. }
54. catch (Exception e)
55. {
56. [Link]("Erreur : {0}", e);
57. }
58. // si la sauvegarde se passe dans une transaction alors rien n'a du être inséré dans
la base
59. // à cause de l'exception précédente - on vérifie
60.
61. using (var context = new RdvMedecinsContext())
62. {
63. // les clients
64. [Link]("Clients--------------------------------------");
65. var clients = from client in [Link] select client;
66. foreach (Client client in clients)
67. {
68. [Link](client);
69. }
70. // les médecins
71. [Link]("Médecins--------------------------------------");
72. var medecins = from medecin in [Link] select medecin;
73. foreach (Medecin medecin in medecins)
74. {
75. [Link](medecin);
[Link]
88/171
76. }
77. // les créneaux horaires
78. [Link]("Créneaux horaires--------------------------------------");
79. var creneaux = from creneau in [Link] select creneau;
80. foreach (Creneau creneau in creneaux)
81. {
82. [Link](creneau);
83. }
84. // les Rdvs
85. [Link]("Rendez-vous--------------------------------------");
86. var rvs = from rv in [Link] select rv;
87. foreach (Rv rv in rvs)
88. {
89. [Link](rv);
90. }
91. }
92. }
93. }
94. }
Il y aurait sans doute d'autres choses à explorer dans EF 5. Mais nous en savons assez pour revenir sur notre étude d'une
architecture multi-couche. Le lecteur trouvera au début de ce document des références d'articles et de livres lui permettant
d'approfondir sa connaissance de EF 5.
[Link]
89/171
3.6 Etude d'une architecture multi-couche s'appuyant sur EF 5
Nous revenons à notre étude de cas décrite au paragraphe 2, page 9. Il s'agit d'une application web [Link] structurée comme
suit :
SPRING
Nous allons commencer par construire la couche [DAO] d'accès aux données. Cette couche s'appuiera sur EF5.
1 2
Nous lui ajoutons quatre dossiers [2] dans lesquels nous allons répartir nos codes. Le dossier [Entites] est une recopie du dossier
[Entites] du projet précédent. Après cette recopie, apparaissent des erreurs dues au fait que nous n'avons pas les bonnes références.
Il nous faut ajouter une référence à Entity Framework 5. On suivra pour cela, la méthode expliquée au paragraphe 3.4, page 23. La
liste des références devient la suivante [3] :
A ce stade, le projet ne doit plus présenter d'erreurs de compilation. Du projet précédent, nous recopions également le fichier
[[Link]] qui configure la connexion à la base de données :
[Link]
90/171
9. </startup>
10. <entityFramework>
11. <defaultConnectionFactory type="[Link],
EntityFramework" />
12. </entityFramework>
13.
14. <!-- chaîne de connexion sur la base -->
15. <connectionStrings>
16. <add name="monContexte"
17. connectionString="Data Source=localhost;Initial Catalog=rdvmedecins-ef;User
Id=sa;Password=msde;"
18. providerName="[Link]" />
19. </connectionStrings>
20. <!-- le factory provider -->
21. <[Link]>
22. <DbProviderFactories>
23. <add name="SqlClient Data Provider"
24. invariant="[Link]"
25. description=".Net Framework Data Provider for SqlServer"
26. type="[Link], [Link],
27. Version=[Link], Culture=neutral, PublicKeyToken=b77a5c561934e089"
28. />
29. </DbProviderFactories>
30. </[Link]>
31.
32. </configuration>
SPRING
La couche [DAO] arrêtera toutes les exceptions qui remonteront jusqu'à elles et les encapsulera dans une exception de type
[RdvMedecinsException]. Cette exception sera la suivante :
1. using System;
2.
3. namespace [Link]
4. {
5. public class RdvMedecinsException : Exception
6. {
7.
8. // propriétés
9. public int Code { get; set; }
10.
11. // constructeurs
12. public RdvMedecinsException()
13. : base()
14. {
15. }
[Link]
91/171
16.
17. public RdvMedecinsException(string message)
18. : base(message)
19. {
20. }
21.
22. public RdvMedecinsException(int code, string message)
23. : base(message)
24. {
25. Code = code;
26. }
27.
28. public RdvMedecinsException(int code, string message, Exception ex)
29. : base(message, ex)
30. {
31. Code = code;
32. }
33.
34. // identité
35. public override string ToString()
36. {
37. if (InnerException == null)
38. {
39. return [Link]("RdvMedecinsException[{0},{1}]", Code, [Link]);
40. }
41. else
42. {
43. return [Link]("RdvMedecinsException[{0},{1},{2}]", Code, [Link],
[Link]);
44. }
45. }
46. }
47. }
[Link]
92/171
Application Couche d'accès Framework Connecteur
[Link] aux données [EF5] [[Link]] SGBD BD
[DAO] 1 2
SPRING
La couche [DAO] offre une interface à la couche [[Link]]. Pour identifier celle-ci, il faut regarder les pages web de l'application :
2
1
• en [1] ci-dessus, la liste déroulante a été remplie avec la liste des médecins. La couche [DAO] fournira cette liste ;
• en [2], la couche [DAO] fournira ;
• la liste des rendez-vous d'un médecin pour tel jour,
• la liste des créneaux horaires d'un médecin,
• des informations complémentaires sur le médecin sélectionné ;
[Link]
93/171
3
On réserve
• en [3], la liste déroulante des clients sera fournie par la couche [DAO] ;
On la renseigne
• en [4], l'utilisateur valide un rendez-vous. La couche [DAO] doit pouvoir l'ajouter à la base. Elle doit pouvoir également
donner des informations complémentaires sur le client sélectionné ;
[Link]
94/171
5
Avec ces informations, l'interface [IDao] de la couche [DAO] pourrait être la suivante :
1. using System;
2. using [Link];
3. using [Link];
4.
5. namespace [Link]
6. {
7. public interface IDao
8. {
9. // liste des clients
10. List<Client> GetAllClients();
11. // liste des médecins
12. List<Medecin> GetAllMedecins();
13. // liste des créneaux horaires d'un médecin
14. List<Creneau> GetCreneauxMedecin(int idMedecin);
15. // liste des RV d'un médecin donné, un jour donné
16. List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour);
17. // ajouter un RV
18. int AjouterRv(DateTime jour, int idCreneau, int idClient);
19. // supprimer un RV
20. void SupprimerRv(int idRv);
21. // trouver une entité T via sa clé primaire
22. T Find<T>(int id) where T : class;
23. }
24. }
Les méthodes des lignes 10-20 découlent de l'étude qui vient d'être faite. La méthodes de la ligne 22 est là pour remédier au fait
qu'on travaille en Lazy Loading. Si dans la couche [[Link]] on a besoin d'une dépendance d'une entité, on ira la chercher en
base avec cette méthode.
1. using System;
2. using [Link];
3. using [Link];
4. using [Link];
5. using [Link];
[Link]
95/171
6. using [Link];
7.
8. namespace [Link]
9. {
10. public class Dao : IDao
11. {
12.
13. //liste des clients
14. public List<Client> GetAllClients()
15. {
16. // liste des clients
17. List<Client> clients = null;
18. try
19. {
20. // ouverture contexte de persistance
21. using (var context = new RdvMedecinsContext())
22. {
23. // liste des clients
24. clients = [Link]();
25. }
26.
27. }
28. catch (Exception ex)
29. {
30. throw new RdvMedecinsException(1, "GetAllClients", ex);
31. }
32. // on rend le résultat
33. return clients;
34. }
35.
36. // liste des médecins
37. public List<Medecin> GetAllMedecins()
38. {
39. // liste des médecins
40. List<Medecin> medecins = null;
41. try
42. {
43. // ouverture contexte de persistance
44. using (var context = new RdvMedecinsContext())
45. {
46. // liste des médecins
47. medecins = [Link]();
48. }
49.
50. }
51. catch (Exception ex)
52. {
53. throw new RdvMedecinsException(2, "GetAllMedecins", ex);
54. }
55. // on rend le résultat
56. return medecins;
57. }
58.
59. // liste des créneaux horaires d'un médecin donné
60. public List<Creneau> GetCreneauxMedecin(int idMedecin)
61. {
62. ...
63. }
64.
65. // liste des RV d'un médecin pour un jour donné
66. public List<Rv> GetRvMedecinJour(int idMedecin, DateTime jour)
67. {
68. ...
[Link]
96/171
69. }
70.
71. // ajouter un RV
72. public int AjouterRv(DateTime jour, int idCreneau, int idClient)
73. {
74. ...
75. }
76.
77. // supprimer un RV
78. public void SupprimerRv(int idRv)
79. {
80. ...
81. }
82.
83. // trouver un client
84. public Client FindClient(int id)
85. {
86. ...
87. }
88.
89. // trouver un créneau
90. public Creneau FindCreneau(int id)
91. {
92. ...
93. }
94.
95. // trouver un médecin
96. public Medecin FindMedecin(int id)
97. {
98. ....
99. }
100.
101. // trouver un Rv
102. public Rv FindRv(int id){
103. ...
104. }
105.
106. }
107. }
Explicitons la méthode [GetAllClients] qui doit rendre la liste de tous les clients :
• lignes 18-31 : la recherche des clients se fait dans un try / catch. Il en sera de même de toutes les méthodes à suivre ;
• ligne 21 : ouverture d'un nouveau contexte ;
• ligne 24 : les entités [Client] sont chargés dans le contexte et mises dans une liste.
La méthode [GetAllMedecins] qui doit rendre la liste de tous les médecins est analogue (lignes 37-57).
[Link]
97/171
13. return [Link]<Creneau>();
14. }
15. }
16. catch (Exception ex)
17. {
18. throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
19. }
20. }
La méthode [GetRvMedecinJour] doit rendre la liste des rendez-vous d'un médecin pour un jour donné. Son code pourrait être le
suivant :
La méthode [AjouterRv] doit ajouter un rendez-vous en base et doit rendre la clé primaire de l'élément inséré. Son code pourrait
être le suivant :
1. // ajouter un RV
2. public int AjouterRv(DateTime jour, int idCreneau, int idClient)
3. {
4. // n° du Rdv ajouté
5. int idRv;
6. try
7. {
8. // ouverture contexte de persistance
[Link]
98/171
9. using (var context = new RdvMedecinsContext())
10. {
11. // on récupère le créneau
12. Creneau creneau = [Link](idCreneau);
13. if (creneau == null)
14. {
15. throw new RdvMedecinsException(5, [Link]("Créneau [{0}] inexistant",
idCreneau));
16. }
17. // on récupère le client
18. Client client = [Link](idClient);
19. if (client == null)
20. {
21. throw new RdvMedecinsException(6, [Link]("Client [{0}] inexistant",
idCreneau));
22. }
23. // création créneau
24. Rv rv = new Rv { Jour = jour, Client = client, Creneau = creneau };
25. // ajout dans le contexte
26. [Link](rv);
27. // sauvegarde du contexte
28. [Link]();
29. // on récupère la clé primaire du rv ajouté
30. idRv = (int)[Link];
31. }
32. }
33. catch (Exception ex)
34. {
35. throw new RdvMedecinsException(7, "AjouterRv", ex);
36. }
37. // résultat
38. return idRv;
39. }
La méthode [SupprimerRv] doit supprimer un rendez-vous dont on lui passe la clé primaire.
1. // supprimer un RV
2. public void SupprimerRv(int idRv)
3. {
4. try
5. {
6. // ouverture contexte de persistance
7. using (var context = new RdvMedecinsContext())
8. {
9. // on récupère le Rv
10. Rv rv = [Link](idRv);
11. if (rv == null)
12. {
13. throw new RdvMedecinsException(5, [Link]("Rv [{0}] inexistant",
idRv));
14. }
15. // suppression Rv
[Link]
99/171
16. [Link](rv);
17. // sauvegarde du contexte
18. [Link]();
19. }
20. }
21. catch (Exception ex)
22. {
23. throw new RdvMedecinsException(8, "SupprimerRv", ex);
24. }
25. }
La méthode [Find<T>] permet de rechercher en base une entité de type T, via sa clé primaire. Son code pourrait être le suivant :
• ligne 8 : la méthode Set<T> permet de récupérer un DbSet<T> sur lequel on peut appliquer les méthodes habituelles.
[Link]
100/171
Programme Couche d'accès Framework Connecteur
console aux données [EF5] [[Link]] SGBD BD
[DAO] 1 2
SPRING
Un programme console demande à [Link], d'instancier la couche [DAO]. Ceci fait, il teste les différentes fonctionnalités de
l'interface de la couche [DAO]. Plutôt qu'un programme console, il aurait été préférable d'écrire un programme de test de type
NUnit. Un programme de test de la couche [DAO] pourrait être le suivant :
1. using System;
2. using [Link];
3. using [Link];
4. using [Link];
5. using [Link];
6. using [Link];
7.
8. namespace [Link]
9. {
10. class Program
11. {
12. public static void Main()
13. {
14. IDao dao = null;
15. try
16. {
17. // instanciation couche [dao] via Spring
18. dao = [Link]().GetObject("rdvmedecinsDao") as IDao;
19.
20. // affichage clients
21. List<Client> clients = [Link]();
22. DisplayClients("Liste des clients :", clients);
23.
24. // affichage médecins
25. List<Medecin> medecins = [Link]();
26. DisplayMedecins("Liste des médecins :", medecins);
27.
28. // liste des créneaux horaires du médecin n° 0
29. List<Creneau> creneaux = [Link]((int)medecins[0].Id);
30. DisplayCreneaux([Link]("Liste des créneaux horaires du médecin {0}",
medecins[0]), creneaux);
31.
32. // liste des Rv d'un médecin pour un jour donné
33. DisplayRvs([Link]("Liste des RV du médecin {0}, le 23/11/2013 :",
medecins[0]), [Link]((int)medecins[0].Id, new DateTime(2013, 11, 23)));
34.
35. // ajouter un RV au médecin n°1 dans créneau n° 0
36. [Link]([Link]("Ajout d'un RV au médecin {0} avec client {1} le
23/11/2013", medecins[0], clients[0]));
37. int idRv1 = [Link](new DateTime(2013, 11, 23), (int)creneaux[0].Id,
(int)clients[0].Id);
38. [Link]("Rdv ajouté");
39. DisplayRvs([Link]("Liste des RV du médecin {0}, le 23/11/2013 :",
medecins[0]), [Link]((int)medecins[0].Id, new DateTime(2013, 11, 23)));
40.
41. // ajouter un Rv dans un créneau déjà occupé - doit provoquer une exception
42. int idRv2;
43. [Link]("Ajout d'un RV dans un créneau déjà occupé");
44. try
45. {
[Link]
101/171
46. idRv2 = [Link](new DateTime(2013, 11, 23), (int)creneaux[0].Id,
(int)clients[0].Id);
47. [Link]("Rdv ajouté");
48. DisplayRvs([Link]("Liste des RV du médecin {0}, le 23/11/2013 :",
medecins[0]), [Link]((int)medecins[0].Id, new DateTime(2013, 11, 23)));
49. }
50. catch (RdvMedecinsException ex)
51. {
52. [Link]([Link]("L'erreur suivante s'est produite : {0}", ex));
53. }
54.
55. // supprimer un Rv
56. [Link]([Link]("Suppression du RV n° {0}", idRv1));
57. [Link](idRv1);
58. DisplayRvs([Link]("Liste des RV du médecin {0}, le 23/11/2013 :",
medecins[0]), [Link]((int)medecins[0].Id, new DateTime(2013, 11, 23)));
59. }
60. catch (Exception ex)
61. {
62. [Link]([Link]("L'erreur suivante s'est produite : {0}", ex));
63. }
64. //pause
65. [Link]();
66. }
67.
68. // méthodes utilitaires - affiche des listes
69. public static void DisplayClients(string Message, List<Client> clients)
70. {
71. [Link](Message);
72. foreach (Client c in clients)
73. {
74. [Link]([Link]());
75. }
76. }
77. public static void DisplayMedecins(string Message, List<Medecin> medecins)
78. {
79. ...
80. }
81. public static void DisplayCreneaux(string Message, List<Creneau> creneaux)
82. {
83. ...
84. }
85. public static void DisplayRvs(string Message, List<Rv> rvs)
86. {
87. ...
88. }
89. }
90. }
• ligne 14 : la référence sur la couche [DAO]. Pour rendre le test indépendant de l'implémentation réelle de celle-ci, cette
référence est du type de l'interface [IDao] et non du type de la classe [Dao] ;
• ligne 18 : la couche [DAO] est instanciée par Spring. Nous reviendrons sur la configuration nécessaire pour que cela soit
possible. Nous castons la référence d'objet rendue par Spring en une référence du type de l'interface [IDao] ;
• lignes 21-22 : affichent les clients ;
• lignes 25-26 : affichent les médecins ;
• lignes 29-30 : affichent la liste des créneaux du médecin n° 0 ;
• ligne 33 : affiche les rendez-vous du médecin n° 0 à la date du 23/11/2013. On doit en avoir aucun ;
• ligne 37 : ajoute un rendez-vous au médecin n° 0 pour le 23/11/2013 ;
• ligne 39 : affiche les rendez-vous du médecin n° 0 à la date du 23/11/2013. On doit en avoir un ;
• ligne 46 : on ajoute une seconde fois le même rendez-vous. On doit avoir une exception ;
• ligne 57 : on supprime l'unique rendez-vous ajouté ;
• ligne 58 : affiche les rendez-vous du médecin n° 0 à la date du 23/11/2013. On doit en avoir aucun.
[Link]
102/171
3.6.5 Configuration de [Link]
Dans le programme de test ci-dessus, nous sommes passés rapidement sur l'instruction qui instancie la couche [DAO] :
La classe [ContextRegistry] est une classe de Spring dans l'espace de noms [[Link]]. Pour pouvoir utiliser Spring, il
nous faut ajouter sa DLL dans les références du projet. Nous procédons de la façon suivante :
3
2
4
Le paquetage [[Link]] avait une dépendance sur le paquetage [[Link]]. Celui-ci a été chargé également. A ce stade,
le projet ne doit plus présenter d'erreurs.
Ce n'est pas pour cela qu'il va marcher. Il nous faut configurer d'abord Spring dans le fichier [[Link]]. C'est la partie la plus
délicate du projet. Le nouveau fichier [[Link]] est le suivant :
[Link]
103/171
Version=[Link], Culture=neutral, PublicKeyToken=b77a5c561934e089"
requirePermission="false" />
6. <!-- spring -->
7. <sectionGroup name="spring">
8. <section name="context" type="[Link], [Link]" />
9. <section name="objects" type="[Link],
[Link]" />
10. </sectionGroup>
11. <!-- common logging-->
12. <section name="logging" type="[Link],
[Link]" />
13. </configSections>
14. <startup>
15. <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
16. </startup>
17. <!-- Entity Framework -->
18. <entityFramework>
19. <defaultConnectionFactory
type="[Link], EntityFramework">
20. <parameters>
21. <parameter value="v11.0" />
22. </parameters>
23. </defaultConnectionFactory>
24. </entityFramework>
25. <!-- Chaînes de connexion -->
26. <connectionStrings>
27. <add name="monContexte" connectionString="Data Source=localhost;Initial
Catalog=rdvmedecins-ef;User Id=sa;Password=msde;" providerName="[Link]" />
28. </connectionStrings>
29. <[Link]>
30. <DbProviderFactories>
31. <add name="SqlClient Data Provider" invariant="[Link]"
description=".Net Framework Data Provider for SqlServer"
type="[Link], [Link], Version=[Link],
Culture=neutral, PublicKeyToken=b77a5c561934e089" />
32. </DbProviderFactories>
33. </[Link]>
34. <!-- configuration Spring -->
35. <spring>
36. <context>
37. <resource uri="config://spring/objects" />
38. </context>
39. <objects xmlns="[Link]
40. <object id="rdvmedecinsDao" type="[Link],RdvMedecins-SqlServer-02" />
41. </objects>
42. </spring>
43. <!-- configuration [Link] -->
44. <logging>
45. <factoryAdapter type="[Link],
[Link]">
46. <arg key="showLogName" value="true" />
47. <arg key="showDataTime" value="true" />
48. <arg key="level" value="DEBUG" />
49. <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
50. </factoryAdapter>
51. </logging>
52. </configuration>
Commençons par enlever tout ce qui est déjà connu : Entity Framework, chaînes de connexion, ProviderFactory. Le fichier évolue
comme suit :
[Link]
104/171
3. <configSections>
4. <!-- For more information on Entity Framework configuration, visit
[Link] -->
5. <section name="entityFramework" ... />
6. <!-- spring -->
7. <sectionGroup name="spring">
8. <section name="context" type="[Link], [Link]" />
9. <section name="objects" type="[Link],
[Link]" />
10. </sectionGroup>
11. <!-- common logging-->
12. <sectionGroup name="common">
13. <section name="logging" type="[Link],
[Link]" />
14. </sectionGroup>
15. </configSections>
16. ...
17. <!-- configuration Spring -->
18. <spring>
19. <context>
20. <resource uri="config://spring/objects" />
21. </context>
22. <objects xmlns="[Link]
23. <object id="rdvmedecinsDao" type="[Link],RdvMedecins-SqlServer-02" />
24. </objects>
25. </spring>
26. <!-- configuration [Link] -->
27. <common>
28. <logging>
29. <factoryAdapter type="[Link],
[Link]">
30. <arg key="showLogName" value="true" />
31. <arg key="showDataTime" value="true" />
32. <arg key="level" value="DEBUG" />
33. <arg key="dateTimeFormat" value="yyyy/MM/dd HH:mm:ss:fff" />
34. </factoryAdapter>
35. </logging>
36. </common>
37. </configuration>
[Link]
105/171
• lignes 27-36 : la configuration de " Common Logging " est stable. On peut être amenés à modifier le niveau d'information,
ligne 32. Après la phase de débogage, on peut passer le niveau à INFO.
Au final, complexe au premier abord, le fichier de configuration de Spring s'avère simple. Il n'y a à modifier que :
• les lignes 22-24 qui définissent les objets à instancier ;
• ligne 32 : le niveau de logs.
[ContextRegistry] est une classe de Spring qui exploite la configuration de Spring faite dans un fichier [[Link]] ou [[Link]].
Ici, elle va exploiter la section suivante du fichier [[Link]] :
1. <spring>
2. <context>
3. <resource uri="config://spring/objects" />
4. </context>
5. <objects xmlns="[Link]
6. <object id="rdvmedecinsDao" type="[Link],RdvMedecins-SqlServer-02" />
7. </objects>
8. </spring>
• [Link]() exploite le contexte des lignes 2-4. La ligne 3 signifie que les objets Spring sont
définis dans la section spring/objects du fichier de configuration. Cette section est lignes 5-7 ;
• [Link]().GetObject("rdvmedecinsDao") exploite la section des lignes 5-7. Elle ramène
une référence sur l'objet qui a l'attribut id= "rdvmedecinsDao ". C'est l'objet défini ligne 6. Spring va alors instancier la
classe définie par l'attribut type en utilisant son constructeur sans paramètres. Celui-ci doit donc exister. Ceci fait, la
référence de l'objet créé est rendue au code appelant. Si l'objet est demandé une seconde fois dans le code, Spring se
contente de rendre une référence sur le premier objet créé. C'est le modèle de conception (Design Pattern) appelé
singleton.
La construction de l'objet peut être plus complexe. On peut utiliser un constructeur avec paramètres ou préciser l'initialisation de
certains champs de l'objet une fois celui-ci créé. Pour plus d'informations sur ce sujet, on pourra lire l'article " Tutoriel Spring IOC
pour .NET ", à l'URL [[Link]
Ceci fait, nous pouvons exécuter l'application. Les résultats écran sont les suivants :
[Link]
106/171
34. Creneau[240,17,20,17,40, 26, 000001189108]
35. Creneau[241,17,40,18,0, 26, 000001189109]
36. Liste des RV du médecin Medecin[26,Mme,Marie,Pelissier,00000118985], le 23/11/2013 :
37. Ajout d'un RV au médecin Medecin[26,Mme,Marie,Pelissier,00000118985] avec client
Client[35,Mr,Jules,Martin,00000118981] le 23/11/2013
38. Rdv ajouté
39. Liste des RV du médecin Medecin[26,Mme,Marie,Pelissier,00000118985], le 23/11/2013 :
40. Rv[28,23/11/2013 [Link],35,218,00000289145]
41. Ajout d'un RV dans un créneau déjà occupé
42. L'erreur suivante s'est produite : RdvMedecinsException[7,AjouterRv,Une erreur s'est produite lors
de la mise à jour des entrées. Pour plus d'informations, consultez l'exception interne.]
43. Suppression du RV n° 28
44. Liste des RV du médecin Medecin[26,Mme,Marie,Pelissier,00000118985], le 23/11/2013 :
Les résultats sont conformes à ce qui était attendu. On considèrera désormais que notre couche [DAO] est valide. Le tutoriel
pourrait s'arrêter là. Nous avons montré jusqu'à maintenant :
• les bases de l'ORM Entity Framework 5 ;
• une couche [DAO] utilisant cet ORM.
Rappelons notre étude de cas décrite au début de ce document. Nous partons d'une application existante à l'architecture suivante :
SPRING
SPRING
où EF5 a remplacé NHibernate. Nous venons de construire la couche [DAO2]. En fait elle ne présente pas la même interface que
la couche [DAO1] dont l'interface était plus réduite :
[Link]
107/171
L'ajout de cette méthode vient du fait que l'ORM EF 5 travaille par défaut en mode Lazy Loading. Les entités arrivent dans la
couche [[Link]] sans leurs dépendances. La méthode ci-dessus nous permet de les récupérer si on en a besoin et dans certains
cas on en a besoin. Je n'en avais pas eu besoin dans la version avec NHibernate car cet ORM travaille par défaut en Eager
Loading. Les entités arrivent dans la couche [[Link]] avec leurs dépendances.
Nous allons terminer le portage de l'application [Link] / NHibernate vers l'application [Link] / EF 5. Mais comme cela ne
concerne plus EF5, nous ne commenterons pas le code web. Nous expliquerons simplement comment mettre en place l'application
web et la tester. Celle-ci est disponible sur le site de ce tutoriel.
SPRING
la couche [[Link]] aura à sa disposition les couches à sa droite sous la forme de DLL. Nous construisons donc la DLL de la
couche [DAO].
3
2
1
• en [1], on sélectionne le programme de test et en [2] on ne l'inclut pas dans la DLL qui va être générée ;
• en [3], dans les propriétés du projet, on indique que l'assembly à créer est une DLL ;
• en [4], dans le menu de VS, on indique qu'on va générer un assembly de type [Release] qui contient moins d'informations
qu'un assembly de type [Debug] ;
5 6
[Link]
108/171
7 8
• en [7], la DLL du projet de la couche [DAO]. C'est celle-ci que le projet web [Link] utilisera ;
• en [8], nous rafraîchissons l'affichage du projet ;
9 10
• en [9], les DLL du dossier [Release] sont rassemblées dans un dossier [lib] externe [10]. C'est là que le projet web ira
chercher ses références.
Nous allons travailler à partir du projet web existant créé avec VS 2010.
4
2
5
3
1
[Link]
109/171
6 7
10
9
• en [8], nous sélectionnons toutes les DLL du projet n° 2 mises précédemment dans le dossier [lib] ;
• en [9], un récapitulatif que nous validons ;
• en [10], le projet web avec ses nouvelles références.
1
2
• en [1], le code de gestion des pages web est réparti sur les deux fichiers [[Link]] et [[Link]]. Du code utilitaire a
été placé dans le dossier [Entites]. Enfin l'application est configurée par le fichier [[Link]] ;
• en [2], nous générons l'assembly du projet ;
• en [3], des erreurs apparaissent.
et son explication :
[Link]
110/171
Le type de [[Link]] est int? alors que la méthode [GetCreneauxMedecin] est de type int. Il faut donc un cast. Cette erreur est
récurrente dans tout le code car les entités du projet [Link] / NHibernate avaient des clés primaires de type int alors que celles
du projet [Link] / EF 5 sont de type int?. On corrige toutes les erreurs de ce type et on régénère le projet. Il n'y en alors plus.
Il nous reste un détail à régler avant d'exécuter le projet : l'instanciation de la couche [DAO] par le framework Spring. Celle-ci est
faite dans [[Link]] :
Dans le programme de test de la couche [DAO], celui-ci instanciait la couche [DAO] de la façon suivante :
Les deux méthodes sont identiques. On se rappelle que cette instanciation de la couche [DAO] s'appuyait sur une configuration
faite dans [[Link]]. On remplace alors le contenu actuel [[Link]] du projet web par celui de [[Link]] du projet de la
couche [DAO] afin d'avoir la même configuration.
Nous sommes prêts pour une première exécution. La page d'accueil est affichée [1] :
[Link]
111/171
3
Lorsqu'on examine le texte d'erreur affiché par la page, on s'aperçoit que l'exception signalée est celle du Lazy Loading : on a
essayé de charger une dépendance d'un objet alors que le contexte de persistance qui le gère a été fermé. L'objet est maintenant
dans un état " détaché ". Cette erreur est due au fait que par défaut NHibernate travaille en Eager Loading alors que EF 5
travaille par défaut en Lazy Loading. Sur la ligne en rouge ci-dessus :
• rdv représente un objet [Rv] qui a été chargé sans ses dépendances ;
• pour évaluer [Link], l'application essaie de charger la dépendance [Link]. Mais comme on n'est plus dans
le contexte, ce n'est pas possible, d'où l'exception.
Ici, la solution est simple. Ligne 108, On crée une entrée dans un dictionnaire avec pour clé laclé primaire du créneau d'un rendez-
vous. Or il se trouve que l'entité [Rv] encapsule la clé primaire du créneau associé. On écrit donc :
dicoRvPris[(int)[Link]] = rdv;
L'erreur est analogue. Ligne 132, on essaie de charger la dépendance [Client] d'un objet [Rv] dans la couche [Link], donc hors
contexte. Il faut aller chercher l'objet [Client] en base. C'est pour remédier à ce problème, que l'interface [IDao] a été enrichie de la
méthode suivante :
Elle va permettre d'aller chercher les dépendances. Ainsi la ligne erronée ci-dessus va être réécrite de la façon suivante :
[Link]
112/171
De nouveau on notera l'intérêt que les entités embarquent leurs clés étrangères. Ici, l'entité [Rv] nous donne accès à la clé étrangère
de la dépendance [Creneau] associée. Ces deux corrections faites, l'application marche. Le lecteur est invité à tester l'application
[RdvMedecins-SqlServer-03] présente dans les téléchargements des exemples du site web de cet article.
3.7 Conclusion
Nous avons mené à bien le portage d'une application [Link] / NHibernate :
SPRING
SPRING
Alors que cette architecture aurait du nous permettre de garder intacte la couche [[Link]], nous avons du la modifier pour deux
raisons :
• les entités n'étaient pas exactement les mêmes. Le type des clés primaires des entités NHibernate était int alors que celui
de EF 5 était int?. Cela nous a amenés à introduire des cast dans le code web ;
• le mode de chargement par défaut des entités n'était pas le même pour les deux ORM : Eager Loading pour NHibernate,
Lazy loading pour EF 5. Cela nous a amenés à enrichir l'interface de la couche [DAO] avec une méthode générique
permettant d'aller chercher une entité via sa clé primaire.
Néanmoins, le portage s'est révélé plutôt simple justifiant de nouveau, si besoin était, l'architecture en couches et l'injection de
dépendances avec Spring ou un autre framework d'injection de dépendances.
Nous allons mesurer maintenant l'impact d'un changement de SGBD sur l'architecture précédente. Nous allons porter l'ensemble
des projets précédents vers quatre autres SGBD :
Les codes ne vont plus changer. Seuls les éléments suivants vont changer :
• la définition dans les entités du champ utilisé pour contrôler la concurrence d'accès à une entité ;
• les fichiers de configuration [[Link]] ou [[Link]] ;
• la définition du schéma des tables dans [[Link]].
[Link]
113/171
4 Etude de cas avec MySQL 5.5.28
• le SGBD : [[Link] ;
• un outil d'administration : EMS SQL Manager for MySQL Freeware
[[Link]
Dans les exemples qui suivent, l'utilisateur root a le mot de passe root.
Lançons MySQL5. ici, nous le faisons à partir de la fenêtre des services Windows [1]. En [2], le SGBD est lancé.
1 2
Nous lançons maintenant l'outil [SQL Manager Lite for MySQL] avec lequel on va administrer le SGBD [3].
[Link]
114/171
5
8 9
• en [7], la base a été créée. Elle doit maintennat être enregistrée dans [EMS Manager]. Les informations sont bonnes. On
fait [OK] ;
• en [8], on s'y connecte ;
• en [9], [EMS Manager] affiche la base, pour l'instant vide.
[Link]
115/171
1
4
3
• en [5], on recommence pour ajouter cette fois [[Link]] qui est un connecteur [Link] pour Entity
Framework. Pour trouver le paquetage, on peut se faire aider par la zone de recherche [6] ;
• en [7], apparaissent deux références [[Link]] et [[Link]], la dernière étant une dépendance de la
première.
[Link]
116/171
3
1 2
A ce stade, la génération du projet doit réussir. Maintenant, nous allons modifier le fichier de configuration [[Link]] qui
configure la chaîne de connexion à la base de données et le DbProviderFactory. Il devient le suivant :
• ligne 17 : la chaîne de connexion à la base MySQL [rdvmedecins-ef] que nous avons créée ;
[Link]
117/171
• ligne 24 : la version doit correspondre à celle de la référence [[Link]] du projet [1] :
Il y a un peu de configuration également dans le fichier [[Link]] où on précise le nom des tables ainsi que le schéma auquel elles
appartiennent. Celui-ci peut changer selon les SGBD. C'est le cas ici, où il n'y aura pas de schéma. Le fichier [[Link]] évolue
comme suit :
1. Exception non gérée : [Link]: Le schéma spécifié n'est pas valide. Erreurs :
2. (11,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs
peuvent être utilisés sans qualification.
3. (23,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs
peuvent être utilisés sans qualification.
4. (33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs
peuvent être utilisés sans qualification.
5. (43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs
peuvent être utilisés sans qualification.
6. à [Link]
7. ()
8. ....
9. à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
10. vp\Entity Framework\RdvMedecins\RdvMedecins-MySQL-01\CreateDB_01.cs:ligne 15
La même erreur apparaît quatre fois (lignes 2-5). Le type rowversion fait penser au champ ayant l'annotation [Timestamp] dans les
entités :
[Column("TIMESTAMP")]
[Timestamp]
public virtual byte[] Timestamp { get; set; }
[Link]
118/171
[ConcurrencyCheck]
[Column("VERSIONING")]
public DateTime? Versioning { get; set; }
On change le type de la colonne qui passe de byte[] à DateTime?. On fait cela parce que MySQL a un type [TIMESTAMP] qui
représente une date / heure et qu'une colonne ayant ce type est automatiquement mise à jour par MySQL à chaque fois que la ligne
est mise à jour. Cela nous permettra de gérer les accès concurrents.
L'annotation [Timestamp] ne peut s'appliquer qu'à une colonne de type byte[]. On la remplace par l'annotation
[ConcurrencyCheck]. Ces deux annotations gèrent toutes les deux la concurrence d'accès. Nous faisons cela pour les quatre entités
puis nous réexécutons l'application. Nous obtenons alors l'erreur suivante :
1. Exception non gérée : [Link]: You have an error in your SQL syntax; check the
manual that corresponds to your MySQL server version for the right syntax to use near 'NOT NULL,
`ProductVersion` mediumtext NOT NULL);
2.
3. ALTER TABLE `__MigrationH' at line 5
4. à [Link]()
5. à [Link](Int32& affectedRow, Int32& insertedId)
6. ...
7. à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
8. vp\Entity Framework\RdvMedecins\RdvMedecins-MySQL-01\CreateDB_01.cs:ligne 15
La ligne 1 indique une erreur de syntaxe sur le SQL exécuté par MySQL. Comme celui-ci n'a pas été généré par nous mais par le
provider [Link] de MySQL, nous ne pouvons pas corriger ce point. Néanmoins, on peut constater que des tables ont été crées
[1] ci-dessous :
Comme nous l'avons fait avec SQL Server, il nous faut donc modifier la base générée. Nous ne montrons pas comment faire les
modifications. Nous donnons simplement le script de création de la base :
[Link]
119/171
7.
8. /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
9. /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
10. /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
11. /*!40101 SET NAMES utf8 */;
12.
13. SET FOREIGN_KEY_CHECKS=0;
14.
15. USE `rdvmedecins-ef`;
16.
17. #
18. # Structure for the `clients` table :
19. #
20.
21. CREATE TABLE `clients` (
22. `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
23. `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
24. `PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
25. `TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
26. `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
27. PRIMARY KEY USING BTREE (`ID`) COMMENT ''
28. )ENGINE=InnoDB
29. AUTO_INCREMENT=96 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
30. COMMENT=''
31. ;
32.
33. #
34. # Structure for the `medecins` table :
35. #
36.
37. CREATE TABLE `medecins` (
38. `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
39. `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
40. `PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
41. `TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
42. `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
43. PRIMARY KEY USING BTREE (`ID`) COMMENT ''
44. )ENGINE=InnoDB
45. AUTO_INCREMENT=56 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
46. COMMENT=''
47. ;
48.
49. #
50. # Structure for the `creneaux` table :
51. #
52.
53. CREATE TABLE `creneaux` (
54. `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
55. `HDEBUT` INTEGER(11) NOT NULL,
56. `MDEBUT` INTEGER(11) NOT NULL,
57. `HFIN` INTEGER(11) NOT NULL,
58. `MFIN` INTEGER(11) NOT NULL,
59. `MEDECIN_ID` INTEGER(11) NOT NULL,
60. `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
61. PRIMARY KEY USING BTREE (`ID`) COMMENT '',
62. INDEX `MEDECIN_ID` USING BTREE (`MEDECIN_ID`) COMMENT '',
63. CONSTRAINT `creneaux_ibfk_1` FOREIGN KEY (`MEDECIN_ID`) REFERENCES `medecins` (`ID`) ON
DELETE CASCADE ON UPDATE NO ACTION
64. )ENGINE=InnoDB
65. AUTO_INCREMENT=472 AVG_ROW_LENGTH=455 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
66. COMMENT=''
67. ;
68.
[Link]
120/171
69. #
70. # Structure for the `rvs` table :
71. #
72.
73. CREATE TABLE `rvs` (
74. `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
75. `JOUR` DATE NOT NULL,
76. `CRENEAU_ID` INTEGER(11) NOT NULL,
77. `CLIENT_ID` INTEGER(11) NOT NULL,
78. `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
79. PRIMARY KEY USING BTREE (`ID`) COMMENT '',
80. UNIQUE INDEX `CRENEAU_ID_JOUR` USING BTREE (`JOUR`, `CRENEAU_ID`) COMMENT '',
81. INDEX `CRENEAU_ID` USING BTREE (`CRENEAU_ID`) COMMENT '',
82. INDEX `CLIENT_ID` USING BTREE (`CLIENT_ID`) COMMENT '',
83. CONSTRAINT `rvs_ibfk_2` FOREIGN KEY (`CLIENT_ID`) REFERENCES `clients` (`ID`) ON DELETE
CASCADE ON UPDATE NO ACTION,
84. CONSTRAINT `rvs_ibfk_1` FOREIGN KEY (`CRENEAU_ID`) REFERENCES `creneaux` (`ID`) ON DELETE
CASCADE ON UPDATE NO ACTION
85. )ENGINE=InnoDB
86. AUTO_INCREMENT=28 AVG_ROW_LENGTH=16384 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
87. COMMENT=''
88. ;
• lignes 22, 38, 54, 74 : les clés primaires ID des tables sont de type AUTO_INCREMENT donc générées par MySQL ;
• lignes 26, 42, 60, 78 : la colonne VERSIONING est de type TIMESTAMP et est mise à jour lors d'un INSERT ou d'un
UPDATE ;
• ligne 63 : la clé étrangère de la table [creneaux] vers la table [medecins] avec la clause ON DELETE CASCADE ;
• ligne 80 : la contrainte d'unicité de la table [rvs] ;
• ligne 83 : la clé étrangère de la table [rvs] vers la table [creneaux] avec la clause ON DELETE CASCADE ;
• ligne 84 : la clé étrangère de la table [rvs] vers la table [clients] avec la clause ON DELETE CASCADE ;
Le script de génération des tables de la base MySQL [rvmedecins-ef] a été placé dans le dossier [RdvMedecins / databases / mysql].
Le lecteur pourra le charger et l'exécuter pour créer ses tables.
Ceci fait, les différents programmes du projet peuvent être exécutés. Ils donnent les mêmes résultats qu'avec SQL Server sauf pour
le programme [ModifyDetachedEntities] qui plante. Pour comprendre pourquoi, on peut regarder le résultat du programme
[ModifyAtttachedEntities] :
1. client1--avant
2. Client [,xx,xx,xx,]
3. client1--après
4. Client [86,xx,xx,xx,]
5. client2
6. Client [86,xx,xx,xx,11/10/2012 [Link]
7. client3
8. Client [86,xx,xx,yy,11/10/2012 [Link]
1. using System;
2. ...
3.
4. namespace RdvMedecins_01
5. {
6. class ModifyDetachedEntities
7. {
8. static void Main(string[] args)
9. {
10. Client client1;
11.
12. // on vide la base actuelle
[Link]
121/171
13. Erase();
14. // on ajoute un client
15. using (var context = new RdvMedecinsContext())
16. {
17. // création client
18. client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
19. // ajout du client au contexte
20. [Link](client1);
21. // on sauvegarde le contexte
22. [Link]();
23. }
24. // affichage base
25. Dump("1-----------------------------");
26. // client1 n'est pas dans le contexte - on le modifie
27. [Link] = "y";
28. // modification entité hors contexte
29. using (var context = new RdvMedecinsContext())
30. {
31. // ici, on a un nouveau contexte vide
32. // on met client1 dans le contexte dans un état modifié
33. [Link](client1).State = [Link];
34. // on sauvegarde le contexte
35. [Link]();
36. }
37. ...
38. }
39.
40. static void Erase()
41. {
42. ...
43. }
44.
45. static void Dump(string str)
46. {
47. ...
48. }
49. }
50. }
• ligne 20 : un client est sauvegardé. Il a alors sa clé primaire mais par sa version ;
• ligne 33 : une modification est faite avec client1. Elle échoue car il n'a pas la version qui est en base.
Désormais, l'entité [client1] a même version qu'en base et peut donc être utilisé pour une mise à jour de la ligne en base.
[Link]
122/171
Application Couche d'accès Framework Connecteur
[Link] aux données [EF5] [[Link]] MySQL BD
[DAO] 1 2
SPRING
Nous allons commencer par construire la couche [DAO] d'accès aux données. Pour ce faire, nous créons le projet console VS 2012
[RdvMedecins-MySQL-02] [1] :
1 3
2
• en [2], les références [[Link], EntityFramework, [Link], [Link], [Link]] sont ajoutées
avec Nuget ;
• en [3], le dossier [Models] est recopié du projet [RdvMedecins-MySQL-01] ;
6
4 5
• en [4], les dossiers [Dao, Exception, Tests] et le fichier [[Link]] sont recopiés du projet [RdvMedecins-SqlServer-02] ;
• en [5], le fichier [[Link]] a été supprimé ;
• en [6], le projet est configuré pour exécuter le programme de test de la couche [DAO].
Dans le fichier [[Link]], on remplace les informations de la base SQL Server par celles de la base MySQL. On les trouve dans
le fichier [[Link]] du projet [RdvMedecins-MySQL-01] :
[Link]
123/171
14. </[Link]>
Ceci fait, nous sommes prêts à exécuter le test de la couche [DAO]. Il faut auparavant prendre soin de remplir la base (programme
[Fill] du projet [RdvMedecins-MySQL-01]). Le programme de test réussit.
Nous créons la DLL du projet comme il a été fait pour le projet [ RdvMedecins-SqlServer-02] et nous rassemblons l'ensemble
des DLL du projet dans un dossier [lib] créé dans [ RdvMedecins-MySQL-02]. Ce seront les références du projet web
[RdvMedecins-MySQL-03] qui va suivre.
Nous sommes désormais prêts pour construire la couche [[Link]] de notre aplication :
SPRING
Nous allons partir du projet [RdvMedecins-SqlServer-03]. Nous dupliquons le dossier de ce projet dans [RdvMedecins-MySQL-03]
[1] :
[Link]
124/171
1
3
• en [2], avec VS 2012 Express pour le web, nous ouvrons la solution du dossier [RdvMedecins-MySQL-03] ;
• en [3], nous changeons et le nom de la solution et le nom du projet ;
4 5 6
Il ne nous reste plus qu'à modifier le fichier [[Link]] On remplace son contenu actuel par le contenu du fichier [[Link]] du
projet [RdvMedecins-MySQL-02]. Ceci fait, on exécute le projet web. Il marche.
4.4 Conclusion
Récapitulons ce qui a été fait pour passer du SGBD SQL server au SGBD MySQL :
• le champ qui servait à gérer la concurrence d'accès aux entités a été changé. Sa version SQL Server était :
[Column("TIMESTAMP")]
[Timestamp]
public virtual byte[] Timestamp { get; set; }
[ConcurrencyCheck]
[Column("VERSIONING")]
public DateTime? Versioning { get; set; }
avec MySQL ;
• la chaîne de connexion à la base et le [DbProviderFactory] ont été modifiés dans les fichiers de configuration [[Link]]
et [[Link]] ;
[Link]
125/171
• après sauvegarde en base, une entité SQL Server avait à la fois sa clé primaire et son Timestamp. Avec MySQL, elle avait
seulement sa clé primaire. Cela a amené la modification d'un code ;
• les schémas des tables dans [[Link]] ont été modifiés.
Au final, c'est assez peu de modifications mais il a quand même fallu revoir du code. Nous recommençons la même démarche pour
trois autres SGBD :
[Link]
126/171
5 Etude de cas avec Oracle Database Express Edition 11g Release 2
• le SGBD : [[Link] ;
• un outil d'administration : EMS SQL Manager for Oracle Freeware
[[Link] ;
• un client Oracle pour .NET : ODAC 11.2 Release 5 ([Link].20) with Oracle Developer Tools for Visual Studio :
[[Link]
Dans les exemples qui suivent, l'utilisateur system a le mot de passe system.
Lançons Oracle [1] puis l'outil [SQL Manager Lite for Oracle] avec lequel on va administrer le SGBD [2].
3
2
1
6
4
[Link]
127/171
8
9 10
12
14
11 13
15
[Link]
128/171
16 17
18
19
19 21
22
20
[Link]
129/171
22 25
24
23
26 27
Maintenant que nous avons un utilisateur avec suffisamment de droits, nous allons créer le projet VS 2012 qui va créer les tables du
schéma [RDVMEDECINS-EF] à partir de la définition des entités.
[Link]
130/171
1
[Link]
131/171
• en [6], on change son nom en [RdvMedecins-Oracle-01]
• en [7], on rajoute un autre projet à la solution. Celui-ci est pris dans le dossier [RdvMedecins-SqlServer-01] du projet que
nous avons supprimé de la solution précédemment ;
• en [8], le projet [RdvMedecins-SqlServer-01] a réintégré la solution.
Le projet [RdvMedecins-Oracle-01] est identique au projet [RdvMedecins-SqlServer-01]. Il nous faut faire quelques modifications.
Dans [[Link]], nous allons modifier la chaîne de connexion et le [DbProviderFactory] qu'on doit adapter à chaque SGBD.
[Link]
132/171
2
1
3
Dans le fichier [[Link]], il faut adapter le schéma des tables qui vont être générées :
[Link]
133/171
1 2
A ce stade, il n'y a pas d'erreurs de compilation. Exécutons le programme [CreateDB_01]. On obtient l'exception suivante :
On se rappelle avoir eu la même erreur avec MySQL. C'est lié au type du champ Timestamp des entités. Nous faisons la même
modification. Dans les entités, nous remplaçons les trois lignes
[Column("TIMESTAMP")]
[Timestamp]
public virtual byte[] Timestamp { get; set; }
[ConcurrencyCheck]
[Column("VERSIONING")]
public int? Versioning { get; set; }
On change donc le type de la colonne qui passe de byte[] à int?. On se rappelle qu'aussi bien pour SQL server que MySQL, la
colonne des tables qui servait à gérer la concurrence d'accès recevait une valeur du SGBD à chaque fois qu'une ligne était insérée ou
modifiée. A partir de maintenant, nous allons utiliser un champ d'entité qui sera un entier. Dans le SGBD, nous utiliserons des
procédures stockées pour incrémenter cet entier d'une unité à chaque fois qu'une ligne sera insérée ou modifiée.
Nous faisons la modification précédente pour les quatre entités puis nous réexécutons l'application. Nous obtenons alors l'erreur
suivante :
La ligne 1 indique que le connecteur [Link] d'Oracle n'est pas capable de supprimer la base existante. Rappelons ce qui se
passe. Le code de [CreateDB_01.cs] est le suivant :
1. using System;
2. using [Link];
[Link]
134/171
3. using [Link];
4.
5. namespace RdvMedecins_01
6. {
7. class CreateDB_01
8. {
9. static void Main(string[] args)
10. {
11. // on crée la base de données
12. [Link](new RdvMedecinsInitializer());
13. using (var context = new RdvMedecinsContext())
14. {
15. [Link](false);
16. }
17. }
18. }
19. }
La ligne 15 enclenche l'exécution de la classe [RdvMedecinsInitializer] (ligne 12). Celle-ci est la suivante :
Elle dérive de la classe [DropCreateDatabaseAlways] qui essaie de supprimer puis de recréer la base. Nous changeons la défintion
de la classe en :
La création de la base n'est faite que si elle n'existe pas. On réexécute [CreateDB_01.cs] et là il n'y a plus d'erreurs. Mais dans [EMS
Manager], on constate que la base [RDVMEDECINS-EF] est restée vide. Parce qu'EF 5 a trouvé une base existante, il n'a rien fait.
Il ne fait quelque chose que si la base n'existe pas. A partir de là, on tourne en rond. En effet, la chaîne de connexion au SGBD est
la suivante :
1. <connectionStrings>
2. <add name="monContexte" connectionString="Data
Source=(DESCRIPTION=(ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521)))
(CONNECT_DATA=(SERVER=DEDICATED)(SERVICE_NAME=XE)));User Id=RDVMEDECINS-
EF;Password=rdvmedecins;" providerName="[Link]" />
3. </connectionStrings>
Ligne 2, la chaîne de connexion utilise non pas le nom d'une base de données mais le nom d'un utilisateur. Celui-ci doit exister.
Nous sommes amenés alors à construire la base de données [RDVMEDECINS-EF] à la main avec l'outil [EMS Manager for
Oracle]. Nous ne décrivons pas toutes les étapes mais simplement les plus importantes.
Les tables
[Link]
135/171
Table RVS
Table CRENEAUX
Les différentes tables ont les clés primaires et étrangères qu'avaient ces mêmes tables dans les deux exemples précédents. Les clés
étrangères ont notamment l'attribut ON DELETE CASCADE.
Les séquences
On a créé ici des séquences Oracle. Ce sont des générateurs de nombres consécutifs. Il y en a 5 [1].
• en [2], nous voyons les propriétés de la séquence [SEQUENCE_CLIENTS]. Elle génère des nombres consécutifs de 1 en
1, à partir de 1 jusqu'à une valeur très grande.
Les triggers
Un trigger est une procédure exécutée par le SGBD avant ou après un événement (Insertion, Modification, Suppression) dans une
table. Nous en avons 8 [1] :
[Link]
136/171
Regardons le code DDL du trigger [TRIGGER_PK_CLIENTS] qui alimente la clé primaire de la table [CLIENTS] :
Regardons le code DDL du trigger [TRIGGER_VERSIONS_CLIENTS] qui alimente la colonne [VERSIONING] de la table
[CLIENTS] :
• lignes 1-2 : avant chaque opération INSERT ou UPDATE sur la table [CLIENTS] ;
• ligne 8 : la colonne [VERSIONING] prendra la valeur suivante de la séquence [SEQUENCE_VERSIONS]. La colonne
[VERSIONING] aura ainsi des valeurs consécutives fournies par la séquence.
Le script de génération des tables de la base Oracle [RDVMEDECINS-EF] a été placé dans le dossier [RdvMedecins / databases /
oracle]. Le lecteur pourra le charger et l'exécuter pour créer ses tables.
Ceci fait, les différents programmes du projet peuvent être exécutés. Ils donnent les mêmes résultats qu'avec SQL Server sauf pour
le programme [ModifyDetachedEntities] qui plante pour la même raison qu'il avait planté avec MySQL. On résoud le problème de
la même façon. Il suffit de copier le programme [ModifyDetachedEntities] du projet [RdvMedecins-MySQL-01] dans le projet
[RdvMedecins-Oracle-01]. On a alors un nouveau problème :
1. 1-----------------------------
2. Client [206,x,x,x,616]
3. 2-----------------------------
4. Client [206,x,x,y,617]
5.
6. Exception non gérée : [Link]: Une
instruction de mise à jour, d'insertion ou de suppression dans le magasin a affecté un nombre
inattendu de lignes (0). Des entités ont peut-être été modifiées ou supprimées depuis leur
chargement. Actualisez les entrées ObjectStateManager. --->
[Link]: Une instruction de mise à jour, d'insertion ou de
suppression dans le magasin a affecté un nombre inattendu de lignes (0). Des entités ont peut-être
été modifiées ou supprimées depuis leur chargement. Actualisez les entrées ObjectStateManager.
7. ...
[Link]
137/171
8. à RdvMedecins_01.[Link](String[] args) dans d:\data\istia-
1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\ModifyDetachedE
9. [Link]:ligne 56
EF 5 a refusé de supprimer client1 en base, car client1 (ligne 6) n'avait pas la même version. On n'avait pas rencontré ce problème
avec MySQL. On s'aperçoit peu à peu que les connecteurs [Link] de différents SGBD présentent de légères différences. On
corrige comme suit :
et ça marche.
SPRING
Nous allons commencer par construire la couche [DAO] d'accès aux données. Pour ce faire, nous dupliquons le projet console VS
2012 [RdvMedecins-SqlServer-02] dans [RdvMedecins-Oracle-02] [1] :
[Link]
138/171
1
• en [3], on ajoute un projet existant à la solution. On le prend dans le dossier [RdvMedecins-Oracle-02] qui vient d'être
créé ;
• en [4], le nouveau projet porte le nom de celui qui a été supprimé. On va changer son nom ;
5 6
[Link]
139/171
• en [7], le dossier [Models] est supprimé pour être remplacé par le dossier [Models] du projet [RdvMedecins-Oracle-01]. En
effet, les deux projets partagent les mêmes modèles.
9
8
Dans le fichier [[Link]], on remplace les informations de la base SQL Server par celles de la base Oracle. On les trouve dans le
fichier [[Link]] du projet [RdvMedecins-Oracle-01] :
Ceci fait, nous sommes prêts à exécuter le test de la couche [DAO]. Il faut auparavant prendre soin de remplir la base (programme
[Fill] du projet [RdvMedecins-Oracle-01]. Le programme de test réussit.
Nous créons la DLL du projet comme il a été fait pour le projet [ RdvMedecins-SqlServer-02] et nous rassemblons l'ensemble
des DLL du projet dans un dossier [lib] créée dans [ RdvMedecins-Oracle-02]. Ce seront les références du projet web
[RdvMedecins-Oracle-03] qui va suivre.
[Link]
140/171
Nous sommes désormais prêts pour construire la couche [[Link]] de notre application :
SPRING
Nous allons partir du projet [RdvMedecins-SqlServer-03]. Nous dupliquons le dossier de ce projet dans [RdvMedecins- Oracle-03]
[1] :
1
3
• en [2], avec VS 2012 Express pour le web, nous ouvrons la solution du dossier [RdvMedecins-Oracle-03] ;
• en [3], nous changeons et le nom de la solution et le nom du projet ;
4 5 6
[Link]
141/171
Il ne nous reste plus qu'à modifier le fichier [[Link]] On remplace son contenu actuel par le contenu du fichier [[Link]] du
projet [RdvMedecins-Oracle-02]. Ceci fait, on exécute le projet web. Il marche. On n'oubliera pas de remplir la base avant
d'exécuter l'application web.
[Link]
142/171
6 Etude de cas avec PostgreSQL 9.2.1
• le SGBD : [[Link] ;
• un outil d'administration : EMS SQL Manager for PostgreSQL Freeware
[[Link]
Dans les exemples qui suivent, l'utilisateur postgres a le mot de passe postgres.
Lançons PostgreSQL puis l'outil [SQL Manager Lite for PostgreSQL] avec lequel on va administrer le SGBD.
Nous lançons maintenant l'outil [SQL Manager Lite for MySQL] avec lequel on va administrer le SGBD [3].
[Link]
143/171
5
11
10
8
• en [8], la base a été créée. Elle doit maintenant être enregistrée dans [EMS Manager]. Les informations sont bonnes. On
fait [OK] ;
• en [9], on s'y connecte ;
• en [10], [EMS Manager] affiche la base, pour l'instant vide. On notera que les tables appartiendront à un schéma appelé
public [11].
[Link]
144/171
1
[Link]
145/171
8
• en [7], on rajoute un autre projet à la solution. Celui-ci est pris dans le dossier [RdvMedecins-SqlServer-01] du projet que
nous avons supprimé de la solution précédemment ;
• en [8], le projet [RdvMedecins-SqlServer-01] a réintégré la solution.
Le projet [RdvMedecins-PostgreSQL-01] est identique au projet [RdvMedecins-SqlServer-01]. Il nous faut faire quelques
modifications. Dans [[Link]], nous allons modifier la chaîne de connexion et le [DbProviderFactory] qu'on doit adapter à
chaque SGBD.
[Link]
146/171
2
Dans le fichier [[Link]], il faut adapter le schéma des tables qui vont être générées :
Nous avons vu précédemment lors de la création d'une base PostgreSQL, que les tables appartenaient à un schéma appelé public.
[Link]
147/171
1
2
A ce stade, il n'y a pas d'erreurs de compilation. Exécutons le programme [CreateDB_01]. On obtient l'exception suivante :
On se rappelle avoir eu la même erreur avec MySQL et Oracle. C'est lié au type du champ Timestamp des entités. Nous faisons la
même modification qu'avec Oracle. Dans les entités, nous remplaçons les trois lignes
[Column("TIMESTAMP")]
[Timestamp]
public virtual byte[] Timestamp { get; set; }
[ConcurrencyCheck]
[Column("VERSIONING")]
public int? Versioning { get; set; }
On change le type de la colonne qui passe de byte[] à int?. Dans le SGBD, nous utiliserons des procédures stockées pour
incrémenter cet entier d'une unité à chaque fois qu'une ligne sera insérée ou modifiée.
Nous faisons la modification précédente pour les quatre entités puis nous réexécutons l'application. Nous obtenons alors l'erreur
suivante :
La ligne 1 indique que le connecteur [Link] de PostgreSQL n'est pas capable de supprimer la base existante. Exactement
comme avec Oracle. Nous sommes amenés alors à construire la base de données [RDVMEDECINS-EF] à la main avec l'outil
[EMS Manager for PostgreSQL]. Nous ne décrivons pas toutes les étapes mais simplement les plus importantes.
Les tables
[Link]
148/171
1
Table MEDECINS
• en [1], ID est clé primaire de type serial. Ce type PostgreSQL est un entier généré automatiquement par le SGBD.
Table CLIENTS
Table CRENEAUX
Table RVS
Les différentes tables ont les clés primaires et étrangères qu'avaient ces mêmes tables dans les exemples précédents. Les clés
étrangères ont l'attribut ON DELETE CASCADE.
[Link]
149/171
Les séquences
Comme avec Oracle, nous avons créé ici des séquences. Ce sont des générateurs de nombres consécutifs. Il y en a 5 [1].
• en [2], nous voyons les propriétés de la séquence [CLIENTS_ID_SEQ]. Elle génère des nombres consécutifs de 1 en 1, à
partir de 1 jusqu'à une valeur très grande.
Les triggers
Un trigger est une procédure exécutée par le SGBD avant ou après un événement (Insertion, Modification, Suppression) dans une
table. Nous en avons 4 [1] :
1 2
Regardons le code DDL du trigger [CLIENTS_tr] qui alimente la colonne [VERSIONING] de la table [CLIENTS] :
• lignes 1-3 : avant chaque opération INSERT ou UPDATE sur la table [CLIENTS] ;
• ligne 4 : la procédure [public.trigger_versions()] est exécutée.
1. BEGIN
2. NEW."VERSIONING":=nextval('sequence_versions');
3. return NEW;
4. END
• ligne 2 : NEW représente la ligne qui va être insérée ou modifiée. NEW. "VERSIONING " est la colonne
[VERSIONING] de cette ligne. On lui affecte la valeur suivante du générateur de nombres " sequence_versions ". Ainsi la
colonne [VERSIONING] change à chaque INSERT / UPDATE fait sur la table [CLIENTS].
[Link]
150/171
Les triggers [MEDECINS_tr, CRENEAUX_tr, RVS_tr] sont analogues. Les quatre colonnes [VERSIONING] tirent leur valeurs
de la même séquence.
Le script de génération des tables de la base PostgreSQL [RDVMEDECINS-EF] a été placé dans le dossier [RdvMedecins /
databases / postgreSQL]. Le lecteur pourra le charger et l'exécuter pour créer ses tables.
Ceci fait, les différents programmes du projet peuvent être exécutés. Ils donnent les mêmes résultats qu'avec SQL Server sauf pour
le programme [ModifyDetachedEntities] qui plante pour la même raison qu'il avait planté avec Oracle. On résoud le problème de la
même façon. Il suffit de copier le programme [ModifyDetachedEntities] du projet [RdvMedecins-Oracle-01] dans le projet
[RdvMedecins-PostgreSQL-01].
Ligne n° 1 de l'exception, l'erreur signalée fait penser à une jointure car LEFT est un mot clé de la jointure. Parce que la ligne 4 du
code ci-dessus, demande le chargement immédiat de la dépendance [Medecin] d'une entité [Creneau], EF a fait une jointure entre
les tables [CRENEAUX] et [MEDECINS]. Mais il semble que le connecteur [Link] a généré un ordre SQL incorrect. Nous
réécrivons le code de la façon suivante :
Ca marche mais de nouveau on constate que le changement de SGBD a un impact sur le code. En fait ce n'est pas le SGBD qui est
en cause mais son connecteur [Link].
[Link]
151/171
Application Couche d'accès Framework Connecteur
[Link] aux données [EF5] [[Link]] MySQL BD
[DAO] 1 2
SPRING
Nous allons commencer par construire la couche [DAO] d'accès aux données. Pour ce faire, nous dupliquons le projet console VS
2012 [RdvMedecins-SqlServer-02] dans [RdvMedecins-PostgreSQL-02] [1] :
• en [3], on ajoute un projet existant à la solution. On le prend dans le dossier [RdvMedecins-PostgreSQL-02] qui vient
d'être créé ;
• en [4], le nouveau projet porte le nom de celui qui a été supprimé. On va changer son nom ;
[Link]
152/171
7
5 6
9
8
Dans le fichier [[Link]], on remplace les informations de la base SQL Server par celles de la base PostgreSQL. On les trouve
dans le fichier [[Link]] du projet [RdvMedecins-PostgreSQL-01] :
[Link]
153/171
La ligne 7 référence l'assembly du projet [ RdvMedecins-SqlServer-02]. L'assembly est désormais [ RdvMedecins-
PostgreSQL-02].
Ceci fait, nous sommes prêts à exécuter le test de la couche [DAO]. Il faut auparavant prendre soin de remplir la base (programme
[Fill] du projet [RdvMedecins-PostgreSQL-01]. Le programme de test plante avec l'exception suivante :
Ligne 13, le message indique que l'erreur s'est produite dans la méthode [GetCreneauxMedecin] de la couche [DAO]. Celle-ci est la
suivante :
Ligne 11, on reconnaît le mot clé Include qui a déjà fait planter un précédent programme. Le code précédent peut être remplacé par
le suivant :
[Link]
154/171
17. }
18. }
Le nouveau code paraît même plus cohérent que l'ancien. Toujours est-il que cette fois-ci le programme de test passe.
Nous créons la DLL du projet comme il a été fait pour le projet [ RdvMedecins-SqlServer-02] et nous rassemblons l'ensemble
des DLL du projet dans un dossier [lib] créée dans [ RdvMedecins-PostgreSQL-02]. Ce seront les références du projet web
[RdvMedecins-PostgreSQL-03] qui va suivre.
Nous sommes désormais prêts pour construire la couche [[Link]] de notre aplication :
SPRING
Nous allons partir du projet [RdvMedecins-SqlServer-03]. Nous dupliquons le dossier de ce projet dans [RdvMedecins-
PostgreSQL-03] [1] :
• en [2], avec VS 2012 Express pour le web, nous ouvrons la solution du dossier [RdvMedecins-PostgreSQL-03] ;
• en [3], nous changeons et le nom de la solution et le nom du projet ;
[Link]
155/171
4 5 6
Il ne nous reste plus qu'à modifier le fichier [[Link]] On remplace son contenu actuel par le contenu du fichier [[Link]] du
projet [RdvMedecins-PostgreSQL-02]. Ceci fait, on exécute le projet web. Il marche. On n'oubliera pas de remplir la base avant
d'exécuter l'application web.
[Link]
156/171
7 Etude de cas avec Firebird 2.1
• le SGBD : [[Link] ;
• un outil d'administration : EMS SQL Manager for InterBase/Firebird Freeware
[[Link]
Dans les exemples qui suivent, l'utilisateur est sysdba avec le mot de passe masterkey.
Lançons Firebird puis l'outil [SQL Manager Lite for Firebird] avec lequel on va administrer le SGBD.
• en [1], nous lançons le SGBD Firebird à partir du Menu Démarrer. Ici, le SGBD n'a pas été installé comme un service
Windows ;
• en [2], le service est démarré. Une icône s'est installée en bas à droite de l'écran. Avec un clic droit sur celle-ci, on peut
arrêter le SGBD.
Nous lançons maintenant l'outil [SQL Manager Lite for Firebird] avec lequel on va administrer le SGBD [3].
[Link]
157/171
6
10
8
• en [8], la base a été créée. Elle doit maintenant être enregistrée dans [EMS Manager]. Les informations sont bonnes. On
fait [OK] ;
• en [9], on s'y connecte ;
• en [10], [EMS Manager] affiche la base, pour l'instant vide.
[Link]
158/171
1
[Link]
159/171
• en [5], le projet chargé s'appelle [RdvMedecins-SqlServer-01] ;
• en [6], on change son nom en [RdvMedecins-Firebird-01]
• en [7], on rajoute un autre projet à la solution. Celui-ci est pris dans le dossier [RdvMedecins-SqlServer-01] du projet que
nous avons supprimé de la solution précédemment ;
• en [8], le projet [RdvMedecins-SqlServer-01] a réintégré la solution.
Le projet [RdvMedecins-Firebird-01] est identique au projet [RdvMedecins-SqlServer-01]. Il nous faut faire quelques modifications.
Dans [[Link]], nous allons modifier la chaîne de connexion et le [DbProviderFactory] qu'on doit adapter à chaque SGBD.
• ligne 3 : l'utilisateur et son mot de passe, ainsi que le chemin complet de la base Firebird ;
• lignes8-10 : le DbProviderFactory. La ligne 9 référence une DLL [[Link]] que nous
n'avons pas. On se la procure avec NuGet [1] :
2
3
[Link]
160/171
• en [2], dans la zone de recherche on tape le mot clé firebird ;
• en [3], on choisit le paquetage [Firebird [Link] Data Provider]. C'est un connecteur [Link] pour Firebird ;
Dans le fichier [[Link]], il faut adapter le schéma des tables qui vont être générées :
1
2
A ce stade, il n'y a pas d'erreurs de compilation. Exécutons le programme [CreateDB_01]. On obtient l'exception suivante :
[Link]
161/171
4. (33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias.
Seuls les types primitifs peuvent être utilisés sans qualification.
5. (43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias.
Seuls les types primitifs peuvent être utilisés sans qualification.
6. à [Link]
7. ()
8. ...
9. à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
10. vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15
On se rappelle avoir eu la même erreur avec MySQL, Oracle et PostgreSQL. C'est lié au type du champ Timestamp des entités. Nous
faisons la même modification qu'avec les deux SGBD précédents. Dans les entités, nous remplaçons les trois lignes
[Column("TIMESTAMP")]
[Timestamp]
public virtual byte[] Timestamp { get; set; }
[ConcurrencyCheck]
[Column("VERSIONING")]
public int? Versioning { get; set; }
On change donc le type de la colonne qui passe de byte[] à int?. Dans le SGBD, nous utiliserons des procédures stockées pour
incrémenter cet entier d'une unité à chaque fois qu'une ligne sera insérée ou modifiée.
Nous faisons la modification précédente pour les quatre entités puis nous réexécutons l'application. Nous obtenons alors l'erreur
suivante :
La ligne 1 indique que la base est utilisée. Ce n'était pas le cas me semble-t-il et je n'ai pas réussi à régler ce problème.
Peu importe. Nous allons construire la base de données [RDVMEDECINS-EF] à la main avec l'outil [EMS Manager for Firebird].
Nous ne décrivons pas toutes les étapes mais simplement les plus importantes.
Les tables
Table MEDECINS
• en [1], ID est une clé primaire avec l'attribut Autoincrement. Elle sera générée automatiquement par le SGBD ;
[Link]
162/171
Table CLIENTS
Table CLIENTS
Table CRENEAUX
Table RVS
Les différentes tables ont les clés primaires et étrangères qu'avaient ces mêmes tables dans les exemples précédents. Les clés
étrangères ont l'attribut ON DELETE CASCADE.
Les générateurs
Comme avec Oracle et PostgreSQL, nous avons créé des générateurs de nombres consécutifs. Il y en a 5 [1].
[Link]
163/171
• [CLIENTS_ID_GEN] sera utilisé pour générer la clé primaire de la table [CLIENTS] ;
• [MEDECINS_ID_GEN] sera utilisé pour générer la clé primaire de la table [MEDECINS] ;
• [CRENEAUX_ID_GEN] sera utilisé pour générer la clé primaire de la table [CRENEAUX] ;
• [RVS_ID_GEN] sera utilisé pour générer la clé primaire de la table [RVS] ;
• [VERSIONS_GEN] sera utilisé pour générer les valeurs des colonnes [VERSIONING] de toutes les tables.
Les triggers
Un trigger est une procédure exécutée par le SGBD avant ou après un événement (Insertion, Modification, Suppression) dans une
table. Nous en avons 8 [1] :
Regardons le code DDL du trigger [BI_CLIENTS_ID] qui alimente la colonne [ID] de la table [CLIENTS] :
Les triggers [ BI_CLIENTS_ID, BI_MEDECINS_ID, BI_CRENEAUX_ID, BI_RVS_ID] sont tous construits de la même
façon.
Regardons maintenant le code DDL du trigger [CLIENTS_VERSION_TRIGGER] qui alimente la colonne [VERSIONING] de la
table [CLIENTS] :
• lignes 1-3 : avant chaque opération INSERT ou UPDATE sur la table [CLIENTS] ;
• ligne 6 : la colonne [" VERSIONING "] reçoit la valeur suivante du générateur de nombres [VERSIONS_GEN]. Ce
générateur alimente les colonnes [" VERSIONING "] des quatre tables.
Le script de génération des tables de la base Firebird [RDVMEDECINS-EF] a été placé dans le dossier [RdvMedecins /
databases / Firebird]. Le lecteur pourra le charger et l'exécuter pour créer ses tables.
Ceci fait, les différents programmes du projet peuvent être exécutés. Ils donnent les mêmes résultats qu'avec SQL Server sauf pour
le programme [ModifyDetachedEntities] qui plante pour la même raison qu'il avait planté avec Oracle et MySQL. On résoud le
problème de la même façon. Il suffit de copier le programme [ModifyDetachedEntities] du projet [RdvMedecins-Oracle-01] dans le
projet [RdvMedecins-Firebird-01].
[Link]
164/171
7.3 Architecture multi-couche s'appuyant sur EF 5
Nous revenons à notre étude de cas décrite au paragraphe 2, page 9.
SPRING
Nous allons commencer par construire la couche [DAO] d'accès aux données. Pour ce faire, nous dupliquons le projet console VS
2012 [RdvMedecins-SqlServer-02] dans [RdvMedecins-Firebird-02] [1] :
[Link]
165/171
4
• en [3], on ajoute un projet existant à la solution. On le prend dans le dossier [RdvMedecins-Firebird-02] qui vient d'être
créé ;
• en [4], le nouveau projet porte le nom de celui qui a été supprimé. On va changer son nom ;
5 6
Dans le fichier [[Link]], on remplace les informations de la base SQL Server par celles de la base Firebird. On les trouve dans
le fichier [[Link]] du projet [RdvMedecins-Firebird-01] :
[Link]
166/171
6. <!-- le factory provider -->
7. <[Link]>
8. <DbProviderFactories>
9. <add name="Firebird Client Data Provider"
invariant="[Link]" description=".Net Framework Data Provider for
Firebird" type="[Link],
[Link], Version=[Link], Culture=neutral,
PublicKeyToken=3750abcc3150b00c" />
10. </DbProviderFactories>
11. </[Link]>
Ceci fait, nous sommes prêts à exécuter le test de la couche [DAO]. Il faut auparavant prendre soin de remplir la base (programme
[Fill] du projet [RdvMedecins-Firebird-01]. Le programme de test passe.
Nous créons la DLL du projet comme il a été fait pour le projet [ RdvMedecins-SqlServer-02] et nous rassemblons l'ensemble
des DLL du projet dans un dossier [lib] créé dans [ RdvMedecins-Firebird-02]. Ce seront les références du projet web
[RdvMedecins-Firebird-03] qui va suivre.
Nous sommes désormais prêts pour construire la couche [[Link]] de notre aplication :
SPRING
Nous allons partir du projet [RdvMedecins-SqlServer-03]. Nous dupliquons le dossier de ce projet dans [RdvMedecins-Firebird-03]
[1] :
[Link]
167/171
3
1
2
• en [2], avec VS 2012 Express pour le web, nous ouvrons la solution du dossier [RdvMedecins-Firebird-03] ;
• en [3], nous changeons et le nom de la solution et le nom du projet ;
5
4 6
Il ne nous reste plus qu'à modifier le fichier [[Link]] On remplace son contenu actuel par le contenu du fichier [[Link]] du
projet [RdvMedecins-Firebird-02]. Ceci fait, on exécute le projet web. Il marche. On n'oubliera pas de remplir la base avant
d'exécuter l'application web.
[Link]
168/171
8 Conclusion
Dans ce document, nous avons tout d'abord découvert Entity Framework 5 Code First (EF 5). Puis nous avons porté l'application
suivante qui utilisait l'ORM NHibernate :
SPRING
SPRING
Nous avons construit cette dernière architecture avec cinq SGBD. Si la portabilité entre SGBD n'a pas été toujours de 100 %, elle a
été toutefois extrêmement satisfaisante.
[Link]
169/171
Table des matières
1 INTRODUCTION......................................................................................................................................................................2
1.1 OBJECTIF....................................................................................................................................................................................2
1.2 LES OUTILS UTILISÉS....................................................................................................................................................................3
1.3 LES CODES SOURCE......................................................................................................................................................................3
1.4 LA MÉTHODE...............................................................................................................................................................................4
1.5 PUBLIC VISÉ.................................................................................................................................................................................5
1.6 ARTICLES CONNEXES SUR DEVELOPPEZ .COM...................................................................................................................................6
2 L'ÉTUDE DE CAS.....................................................................................................................................................................7
2.1 LE PROBLÈME..............................................................................................................................................................................7
2.2 LA BASE DE DONNÉES.................................................................................................................................................................11
2.2.1 LA TABLE [MEDECINS]........................................................................................................................................................11
2.2.2 LA TABLE [CLIENTS]............................................................................................................................................................11
2.2.3 LA TABLE [CRENEAUX]......................................................................................................................................................12
2.2.4 LA TABLE [RV].......................................................................................................................................................................12
3 ETUDE DE CAS AVEC SQL SERVER EXPRESS 2012....................................................................................................14
3.1 INTRODUCTION...........................................................................................................................................................................14
3.2 INSTALLATION DES OUTILS..........................................................................................................................................................14
3.3 LE SERVEUR EMBARQUÉ (LOCALDB)\V11.0...................................................................................................................................20
3.4 CRÉATION DE LA BASE À PARTIR DES ENTITÉS...............................................................................................................................21
3.4.1 L'ENTITÉ [MEDECIN]................................................................................................................................................................23
3.4.2 L'ENTITÉ [CRENEAU]................................................................................................................................................................29
3.4.3 LES ENTITÉS [CLIENT] ET [RV]..................................................................................................................................................33
3.4.4 FIXER LE NOM DE LA BASE.........................................................................................................................................................36
3.4.5 REMPLISSAGE DE LA BASE..........................................................................................................................................................37
3.4.6 MODIFICATION DES ENTITÉS........................................................................................................................................................40
3.4.7 AJOUTER DES CONTRAINTES À LA BASE........................................................................................................................................41
3.4.8 LA BASE DÉFINITIVE..................................................................................................................................................................45
3.5 EXPLOITATION DE LA BASE AVEC ENTITY FRAMEWORK.................................................................................................................48
3.5.1 SUPPRESSION D'ÉLÉMENTS DU CONTEXTE DE PERSISTANCE...............................................................................................................48
3.5.2 AJOUT D'ÉLÉMENTS AU CONTEXTE DE PERSISTANCE........................................................................................................................51
3.5.3 AFFICHAGE DU CONTENU DE LA BASE...........................................................................................................................................53
3.5.4 APPRENTISSAGE DE LINQ AVEC LINQPAD................................................................................................................................59
3.5.5 MODIFICATION D'UNE ENTITÉ ATTACHÉE AU CONTEXTE DE PERSISTANCE............................................................................................71
3.5.6 GESTION DES ENTITÉS DÉTACHÉES...............................................................................................................................................73
3.5.7 LAZY ET EAGER LOADING.........................................................................................................................................................77
3.5.8 CONCURRENCE D'ACCÈS AUX ENTITÉS..........................................................................................................................................80
3.5.9 SYNCHRONISATION DANS UNE TRANSACTION..................................................................................................................................85
3.6 ETUDE D'UNE ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5.............................................................................................88
3.6.1 LE NOUVEAU PROJET.................................................................................................................................................................88
3.6.2 LA CLASSE EXCEPTION..............................................................................................................................................................89
3.6.3 LA COUCHE [DAO].................................................................................................................................................................90
3.6.4 TEST DE LA COUCHE [DAO].....................................................................................................................................................98
3.6.5 CONFIGURATION DE [Link]...............................................................................................................................................101
3.6.6 GÉNÉRATION DE LA DLL DE LA COUCHE [DAO].......................................................................................................................106
3.6.7 LA COUCHE [[Link]].......................................................................................................................................................107
3.7 CONCLUSION............................................................................................................................................................................111
4 ETUDE DE CAS AVEC MYSQL 5.5.28.............................................................................................................................112
4.1 INSTALLATION DES OUTILS........................................................................................................................................................112
4.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................113
4.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................120
4.4 CONCLUSION............................................................................................................................................................................123
5 ETUDE DE CAS AVEC ORACLE DATABASE EXPRESS EDITION 11G RELEASE 2.......................................... 125
5.1 INSTALLATION DES OUTILS........................................................................................................................................................125
5.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................128
5.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................136
6 ETUDE DE CAS AVEC POSTGRESQL 9.2.1..................................................................................................................141
6.1 INSTALLATION DES OUTILS........................................................................................................................................................141
6.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................142
6.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................149
7 ETUDE DE CAS AVEC FIREBIRD 2.1............................................................................................................................ 155
7.1 INSTALLATION DES OUTILS........................................................................................................................................................155
[Link]
170/171
7.2 CRÉATION DE LA BASE À PARTIR DES ENTITÉS.............................................................................................................................156
7.3 ARCHITECTURE MULTI-COUCHE S'APPUYANT SUR EF 5...............................................................................................................163
8 CONCLUSION...................................................................................................................................................................... 167
[Link]
171/171