Autoecole
Une auto-école propose à ses élèves des Leçons de conduite. L’application gère ses différents cours.
BLOC 2
2.1.2 Modéliser une solution applicative
2.1.3 Utiliser des composants d’accès aux données
2.1.5 Réaliser des tests nécessaires à la validation ou à la mise en production
A - Fonctionnalités attendues

B - La base de données
La base de données est sous MySql. Le schéma relationnel est le suivant :
Dans la table eleve, le crédit horaire correspond au nombre d’heures restant à effectuer.
Dans la table Leçon, le champ heure correspond à l’heure de la leçon dans la journée, la durée est la durée de la leçon (une ou deux heures) ; le champ effectueeOui_Non indique si la leçon a effectivement eu lieu.
C - Architecture
L’application présente 3 couches :
- La base de données
- Une couche de mapping utilisant le Framework Entity Framework (version 4.0) qui va permettre d’utiliser un modèle objet directement relié aux tables.
- La couche de présentation : une application WinForm
D - Mise en place du server
a) Configuration de la base de données
- Récupérez le fichier
w10SqlExpress.ova
et lancez le - Lancez le fichier SSMS
- Récupérez le fichier sql autoecole
- Créez la base de données en double cliquant sur
Databases
- Chargez le fichier sql avec
File>Open>File
ouCTRL + O
et Exécutez le
b) configuration de l’accès distant
- Lancer
cmd
dans la recherche - Saisir
ipconfig /all
et notez l’adresse IP
- Lancer le
Gestionnaire de Configuration SQL Server 2022
E - Développement de l’application
Cette première version ne mettra pas en œuvre de couches logicielles, ainsi tout le code sera dans le
même projet ; pour cette partie, nous nous attachons à découvrir les fonctionnalités de l’accès aux
données avec le Framework Entity
.
a) Création du projet.
- Créer un projet de type
WinForm
, choisissez la version.NET framework
, nommez-leautoEcoleEF
. - Ajouter au projet un nouvel élément ADO

Un assistant va générer ensuite des classes permettant cet accès aux données.

Configurer la connexion :
- Sélectionner la base de données autoEcole dans la liste.
- Tester.
- Faire suivant et accepter le nom de la connexion proposée.

L’assistant va ensuite vous demander de sélectionner les tables visées :
- Cocher bien les deux cases.
- Modifier le nom du namespace proposé.
- Faire terminer.


Remarquons également que la classe lecon contient des champs correspondant aux clés étrangères :
idEleve
et numImmaVehicule
; nous pouvions ne pas générer cette présence des clés étrangère en
décochant plus haut la case Inclure les colonnes clés étrangères…
Nous allons modifier ce modèle en supprimant la propriété de navigation lecons dans la classe vehicule ; en effet, nous ne demanderons jamais à un véhicule de nous donner les leçons associées. Faire un clic droit/supprimer sur ce champ de navigation.
Regardons le fichier XML App.Config
. C’est dans ce fichier que se trouvent les paramètres de
connexion.
b) Le formulaire menu
- Modifier son nom,
FrmMenu
, - Ajouter un menu (Boite à outils/MenuStrip)
- Dans les propriétés du formulaire mettre
IsMdiContainer
àTrue

- Ajoutez des sous-menus
- Fichier/Quitter
- Elève/Gérer
- Véhicule/Gérer et Véhicule/Liste
- Leçon/Ajouter et Leçon/Valider
- Déclarer un champ privé de type
autoecoleEntities
qui est notre classe de contexte


Pour que les formulaires s’intègrent dans le MDI, il faut copier le code de cette façon :

c) Le formulaire Elève/Gérer
- Ajouter au projet un nouveau formulaire,
FrmGererEleve
.

- Réaliser en conception ce formulaire, attention au nommage des composants :
txtNum, txtNom, …, dtInscription, cmbCredit - Le comboBox proposant le crédit horaire doit être chargé par une boucle :

- Nous allons ensuite lier l’ouverture de ce formulaire au formulaire de menu :

- Dans le formulaire FrmMenu :

Notion de binding
Le binding est un mécanisme qui permet de lier un composant graphique de présentation à une source de données. Cette liaison est bi-directionnelle, si l’on modifie la source, l’information sera transmise au composant et si on modifie l’information présente dans le composant, ceci sera répercuté dans la source de données. Une source de donnée peut être de nature très variée, ce peut être une table d’une base, une classe en mémoire, une liste typée. Dans notre cas, il s’agit d’une classe en mémoire.
On peut représenter les différentes responsabilités ainsi :
Le binding est mis en œuvre grâce à un composant qu’il faut installer dans le formulaire.
Il se trouve dans la partie Données de la boite à outils :
- Il faut maintenant le renommer (
bdgSourceEleve
) et lui indiquer sa source de données qui est la classeeleve
(et pas, bien sûr la table eleve). - Dans les propriétés du composant, cliquer sur DataSource, ajouter une nouvelle source de données.
- Indiquer que la source est un objet.


- Si la source n’apparait pas, compilez le projet.

il n’y a pas encore de chargement des données venant de la classe de contexte de donnée.

C’est ce que nous ferons par la propriété DataBinding/text
du premier textBox (celui qui contient l’id de
l’élève) que nous lierons à l’id de bdgSourceEleve
pour son champ id
:
Si nous lançons l’application, on constate l’accès aux données
Naviguer dans la table
Nous allons utiliser un composant dédié à la navigation, le BindingNavigator
.
Le formulaire fait apparaître une barre de navigation.
- Modifiez son nom en
bdgNav
- Paramétrons ce composant dans sa propriété
BindingSource

Si nous testons l’application, nous remarquons la possibilité de modifier, supprimer un élève.
Mais si nous relançons l’application, la base de données n’a pas été modifiée ; pour cela, il faut
demander explicitement à la classe qui communique avec la base de la faire, la classe
autoEcoleEntities
. On le fera un peu plus tard.
Intéressons-nous au bouton d’ajout du navigateur.
Se pose le problème de l’identifiant, il n’est pas
auto-incrémenté dans la base, il doit prendre une valeur ici (d’autres solutions pouvaient être
envisagées –procédure stockée-). Nous allons générer un numéro qui incrémente le numéro
maximum existant.
- Écrire la méthode
getNumEleve
dans la classeFrmGererEleve
.

On voit ici ce qu’on appelle une requête linq
, langage d’interrogation des données pour les classes de
mapping. L’avantage est de bénéficier de l’IntelliSense lorsqu’on requête, le prix à payer c’est le
nouveau langage de requêtage !!
txtNum
à readOnly = true
(l’utilisateur ne peut ainsi pas modifier l’identifiant) on va faire un appel
à cette méthode lorsque l’on désire ajouter un nouvel élève.

Intéressons-nous au bouton enregistrer
Enregistrer
à sa propriété Text, il est judicieux d’ajouter une image de disquette, par exemple, et dans le code de
l’événement clic associé :

endEdit()
permet de valider dans le modèle de classe la création. Cet appel doit se faire
explicitement parfois lorsque nous voulons faire prendre en compte immédiatement une
modification. Cet appel est le plus souvent fait automatiquement.

Add
permet d’ajouter l’objet dans la classe eleve
.
L’appel à SaveChanges()
se justifie ici, car la méthode getNumEleve
est une requête linq qui va
chercher en base un nouveau numéro ; si nous n’enregistrons pas en base ce numéro, la requête
retournera toujours la même valeur ;
Intéressons-nous au bouton supprimer
DeleteItem
à aucun
, sinon la suppression se fera dans le BindingSource
mais pas dans la base de donnée.


bdgSourceEleve.RemoveCurrent()
permet de supprimer l’élève à l’affichage.
Pour pouvoir supprimer l’élève dans la base de donnée il faut passer par un contexte temporaire context
. Ensuite il faut
rechercher avec une requête linq
la données dans la base, stockée dans la variable entiteASupprimer
.
L’appel à Remove
permet de supprimer l’objet dans la classe eleve
.
L’appel à SaveChanges()
se justifie ici, comme précédemment.
d) Le formulaire Véhicule/Gérer
FrmGererVehicule
et le code associé.
Contrainte : les champs doivent être en readOnly
, sauf si on ajoute un véhicule.

Un petit bug
Lorsque l’on ajoute un nouveau véhicule (il en était de même pour l’ajout d’un nouvel élève) et que
l’on ne saisit pas les informations, l’application actuellement ne signale pas d’erreur. Nous allons
modifier ce comportement. L’idée, c’est que, si l’utilisateur « sort » de l’ajout (en cliquant sur l’un des
3 boutons disponibles, movePrevious
, moveFirst
, addNew
) sans avoir rempli les champs nécessaires,
l’application le signale et annule la demande en cours.
- Il faut dans un premier temps positionner la propriété
CheckOnClicK
àtrue
pour les trois boutons énumérés plus haut, ce qui n’est pas fait par défaut. Ainsi, par défaut, si on clic sur un bouton, il n’est pas checké ! On peut regretter ce comportement par défaut. - Il faut « intercepter » l’événement lorsque le bouton est « dé-chécké », événement qui a lieu
avant l’événement click. C’est l’événement
checkedChanged
.moveFirst
,addNew
. La méthodeCancelEdit
annule la dernière modification.L’idéal est de créer une méthodevalidationVehicule
.
e) Le formulaire Véhicule/Liste
Le formulaire, FrmListeVehicule
, permet de visualiser tous les véhicules et éventuellement modifier le champ en Etat.
Voici la maquette attendue :
- Créez un nouveau formulaire
FrmListeVehicule
, - liez-le à l’ouverture de l’option Véhicule/Liste du menu principal,
- modifiez son constructeur,
- passez le champ privé dans l’appel de la construction,
- ajouter un composant de binding :
bdgVehicules
, - créez une nouvelle source de données pour valoriser son champ DataSource.

- Déposez dans le formulaire un DataGridView,
- renommez-le
dgvVehicule
, - valorisons sa propriété DataSource :

- Sur un clic droit, sur le DataGridView, modifiez les colonnes pour rendre les 3 premières en readOnly :

- Ajoutez le chargement des données dans la construction :

- Enfin, ajoutez un bouton dans le formulaire pour enregistrer les modifications :

f) Le formulaire d’ajout d’une leçon
Voici la maquette attendue :
effectuee_oui_non
.
- Créez ce formulaire,
FrmAjoutLecon
, et liez-le au menu, comme déjà fait plus haut, - liez le comBox des élèves à la source de données des élèves,
- déposez un composant de binding
bdgEleve
lié à la source de données déjà créée

- Liez le comboBox
cmbEleve
à cette source :

- Remplissez la source de données et chargeons
cmbHeure
avec des données correspondant à des horaires

- Mettez en œuvre une requête plus performante :

- Générez un objet de type
lecon

SelectedValue
.
lecon
est le constructeur par défaut (sans argument) ; c’est
pourquoi nous employons des seter
pour valoriser les champs. Nous aurions pu aussi surcharger ce
constructeur.
- Enregistrez la nouvelle leçon par l’appel du clic sur le bouton enregistrer

g) Le formulaire Leçon/Valider
Il s’agit ici de fixer le numéro d’immatriculation du véhicule et indiquer que la leçon a eu lieu ou pas.
Voici la maquette attendue :
- Ajoutez un formulaire
FrmValiderLecon
, - liaison au menu,
- déclaration du champ de contexte de données,
- modification du constructeur.
- Ajoutez un composant de binding
bdgSlecon
- liez ce composant à la classe
lecon
, - Ajoutez un DataGridView
- liez-le par sa propriété DataSource au composant de binding
bdgSlecon
Modification des colonnes du DataGridView
- ajoutons un second composant de binding
bdgSvehicule
, - liez-le à la source vehicule (déjà créée),
- sur un clic droit sur le DataGridView, supprimez des colonnes,
- transformez
effectueeOui_Non
en CheckBox, dans Données spécifiez la valeurFalse
etTrue
- transformez
numImmaVehicule
en ComboBox, que nous lions à la sourcebdgSvehicule
. - Le bouton enregistrer met à jour la base
- chargez les données

F - Pour aller plus loin
a) Le formulaire Leçon/Valider : version 2 (pour aller un peu plus loin)
Il est dommage que nous identifiions l’élève par son id, qui n’est pas très parlant, et par ses nom et prénom. Par ailleurs, nous allons un peu modifier le formulaire pour ne proposer dans le DataGridView que les leçons non encore validées.
Nous sommes confrontée à une difficulté liée à la conception des DataGridView, ceux-ci ne savent/peuvent pas se lier/binder (dans les deux sens lecture/écriture) à une « jointure », ce qui serait le cas dans l’hypothèse de faire apparaître le nom de l’élève dans le DataGridView. Une solution serait de modifier le comportement initial d’un DataGridView en le spécialisant. Solution que nous écartons.
Nous aurions pu aussi intervenir au niveau du modèle de classe et « migrer » des propriétés de la
classe eleve
vers la classe lecon
, le nom et le prénom de l’élève aurait été ainsi directement
accessibles.
Nous procèderons autrement en faisant apparaître dans un autre composant les noms et prénoms des élèves.
Concernant la seconde modification (ne faire apparaître que certaines leçons, celles qui ne sont pas
validées), il existe une propriété filter dans une source de données qui aurait pu faire l’affaire
(effectuee_OuiNon = false
) mais la documentation MSDN est très incomplète sur cette question.
Donc, nous voulons obtenir un formulaire qui ressemble à ceci :


b) Un formulaire visualisant les leçons d’un élève : deux DataGridView liés
Nous voulons obtenir les leçons des élèves à partir d’un DataGridView d’élèves :

eleve
, lecon
et vehicule
.
Supprimez les colonnes inutiles :

modifiez ses colonnes
remplaçez la colonne texte par un ComboBoxColumn pour le numéro d’immatriculation (cf plus haut) :





La ligne :
this.bdgSeleve.DataSource = this.mesDonneesEF.eleves.Include("lecons");
utilise une méthode Include
qui permet de charger pour chaque élève ses leçons. Ce qui n’est pas fait
par défaut. EF met en œuvre ce que l’on appelle le lazy loading
(chargement paresseux) ; par
exemple si on charge en mémoire un objet, ses objets associés ne sont pas disponibles ; il faut
utiliser explicitement un Include(<nom de la propriété de liaisons>)
pour obtenir ses objets liés.
c) Les procédures stockées
a) Côté MySql
Nous allons tout d’abord créer une procédure stockée dans MySql ; cette procédure créera un nouveau véhicule dont on passera les valeurs de tous les champs :

|
après le END
;
) en |
:

b) Côté EF
Cliquez n’importe où,
Demandez à mettre à jour le modèle à partir de la base de données :

Vérifiez que vous avez bien la procédure stockée en relançant l’assistant de mise à jour du modèle :



this.mesDonnesEF.newVehicule("123ZA79", "renault", "bleue", 1);