Chapitre 931 minIntermédiaire
Connexion C# à MySQL avec MySqlConnector
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- choisir entre MySqlConnector et MySql.Data ;
- écrire une chaîne de connexion et ouvrir une connexion proprement ;
- exécuter des requêtes paramétrées pour lire, ajouter, modifier et supprimer ;
- enregistrer une ordonnance et ses lignes dans une transaction ;
- traduire une erreur MySQL en message compréhensible.
4 exercices corrigés
Pour connecter une application C# à une base MySQL ou MariaDB, on installe un pilote ADO.NET (le paquet NuGet MySqlConnector), on ouvre une connexion avec une chaîne de connexion, puis on exécute des commandes SQL paramétrées et on lit leurs résultats. Ce chapitre construit la couche données de GSB Ordonnances : le script de la base, la classe de connexion, le DAO complet des patients et l'enregistrement d'une ordonnance dans une transaction.
Le script de la base gsb_ordonnances
Exécute ce script dans ton client MySQL (ligne de commande, phpMyAdmin, DBeaver) sur MySQL 8.4 ou MariaDB. Les noms de colonnes suivent la convention des sujets de BTS (numPatient, dateNaissance). La création et les contraintes sont détaillées dans le cours SQL.
CREATE DATABASE gsb_ordonnances CHARACTER SET utf8mb4;
USE gsb_ordonnances;
CREATE TABLE medecin (
numMedecin INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50),
numeroRpps CHAR(11) UNIQUE,
specialite VARCHAR(50)
);
CREATE TABLE patient (
numPatient INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(50) NOT NULL,
prenom VARCHAR(50),
dateNaissance DATE,
numeroSecu CHAR(13) UNIQUE
);
CREATE TABLE medicament (
codeMedicament VARCHAR(10) PRIMARY KEY,
nom VARCHAR(80) NOT NULL,
dosage VARCHAR(30)
);
CREATE TABLE ordonnance (
numOrdonnance INT AUTO_INCREMENT PRIMARY KEY,
dateEmission DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
numMedecin INT NOT NULL,
numPatient INT NOT NULL,
FOREIGN KEY (numMedecin) REFERENCES medecin (numMedecin),
FOREIGN KEY (numPatient) REFERENCES patient (numPatient)
);
CREATE TABLE contenir (
numOrdonnance INT NOT NULL,
codeMedicament VARCHAR(10) NOT NULL,
posologie VARCHAR(100) NOT NULL,
dureeJours SMALLINT NOT NULL CHECK (dureeJours BETWEEN 1 AND 90),
PRIMARY KEY (numOrdonnance, codeMedicament),
FOREIGN KEY (numOrdonnance) REFERENCES ordonnance (numOrdonnance),
FOREIGN KEY (codeMedicament) REFERENCES medicament (codeMedicament)
);
CREATE TABLE utilisateur (
numUtilisateur INT AUTO_INCREMENT PRIMARY KEY,
login VARCHAR(30) NOT NULL UNIQUE,
hashMotDePasse VARCHAR(100) NOT NULL,
role VARCHAR(20) NOT NULL DEFAULT 'secretariat'
);
INSERT INTO medecin (nom, prenom, numeroRpps, specialite) VALUES
('Martin', 'Paul', '10001234567', 'Médecine générale'),
('Nguyen', 'Claire', '10007654321', 'Pédiatrie');
INSERT INTO patient (nom, prenom, dateNaissance, numeroSecu) VALUES
('Dupont', 'Marie', '1990-05-12', '2900575123456'),
('Petit', 'Léa', '2012-09-30', NULL),
('Bernard', 'Hugo', '2008-11-03', '1081175123456');
INSERT INTO medicament (codeMedicament, nom, dosage) VALUES
('PARA1000', 'Paracétamol', '1000 mg'),
('AMOX500', 'Amoxicilline', '500 mg'),
('IBUP400', 'Ibuprofène', '400 mg');
INSERT INTO ordonnance (dateEmission, numMedecin, numPatient) VALUES
('2026-10-15 09:30:00', 1, 1),
('2026-10-20 17:10:00', 2, 2);
INSERT INTO contenir (numOrdonnance, codeMedicament, posologie, dureeJours) VALUES
(1, 'PARA1000', '1 comprimé 3 fois par jour', 5),
(1, 'AMOX500', '1 gélule matin et soir', 7),
(2, 'PARA1000', '1 comprimé si fièvre, 3 par jour au plus', 3);
-- mot de passe de démonstration : Ordonnance-2026! (haché avec BCrypt, chapitre 12)
INSERT INTO utilisateur (login, hashMotDePasse, role) VALUES
('secretariat', '$2a$12$KaivI.GwRD8Nh62xfaOgIuRJf5gEFyf74DbwX3HvtRMiJXNE1UTvS', 'secretariat');
MySqlConnector ou MySql.Data ?
Deux pilotes ADO.NET existent pour MySQL. Leurs classes portent les mêmes noms (MySqlConnection, MySqlCommand, MySqlDataReader) ; seul l'espace de noms change.
| MySqlConnector | MySql.Data (Connector/NET) | |
|---|---|---|
| Éditeur | Projet libre, communautaire | Oracle |
| Licence | MIT | GPL 2.0 avec exception FOSS |
using | using MySqlConnector; | using MySql.Data.MySqlClient; |
| Version (novembre 2026) | 2.6 | 26.10 |
| Bases visées | MySQL, MariaDB, Aurora, Azure Database for MySQL… | MySQL |
| Asynchrone | Conçu pour : OpenAsync, ExecuteReaderAsync… réellement asynchrones | Méthodes …Async présentes |
Ce cours utilise MySqlConnector : il fonctionne aussi bien avec MariaDB qu'avec MySQL, sa licence MIT ne pose aucune question quand on distribue l'application, et son asynchronisme est complet. MySql.Data reste très répandu : beaucoup de tutoriels, de projets existants et de sujets d'examen l'utilisent. Le code est presque identique ; il suffit de changer le using et le paquet.
Installe le paquet dans le projet Gsb.Donnees : clic droit sur le projet > Gérer les packages NuGet > onglet Parcourir > MySqlConnector > Installer. En ligne de commande :
dotnet add Gsb.Donnees package MySqlConnector
La chaîne de connexion
Une chaîne de connexion rassemble, sous forme clé=valeur;, tout ce qu'il faut pour joindre le serveur :
Server=srv-bdd.cabinet.local;Port=3306;Database=gsb_ordonnances;User ID=gsb_app;Password=…;SslMode=Required
| Clé | Rôle |
|---|---|
Server | Nom ou adresse IP du serveur (localhost en développement) |
Port | 3306 par défaut |
Database | La base utilisée |
User ID, Password | Le compte MySQL de l'application, jamais root (chapitre 12) |
SslMode | Required chiffre la connexion ; VerifyCA ou VerifyFull vérifient en plus le certificat du serveur |
Connection Timeout | Délai maximal pour se connecter, en secondes (15 par défaut) |
MySqlConnector utilise SslMode=Preferred par défaut : il chiffre si le serveur le permet, sinon il continue en clair. Sur un réseau d'entreprise, exige le chiffrement avec Required.
Ouvrir une connexion proprement
Une petite classe centralise la création des connexions. Chaque DAO la reçoit par son constructeur :
using MySqlConnector;
namespace Gsb.Donnees;
public class Connexion(string chaineConnexion)
{
public async Task<MySqlConnection> OuvrirAsync()
{
var conn = new MySqlConnection(chaineConnexion);
await conn.OpenAsync();
return conn;
}
}
Chaque méthode du DAO ouvre sa connexion au début et la ferme à la fin, grâce à await using, qui appelle DisposeAsync à la sortie du bloc, même en cas d'exception.
await using MySqlConnection conn = await connexion.OuvrirAsync();
// … utilisation
// à la fin de la méthode, la connexion est fermée automatiquement
Ouvrir et fermer une connexion à chaque requête n'est pas coûteux : le pilote garde un pool de connexions ouvertes et réutilise une connexion « fermée » au lieu d'en créer une nouvelle. À l'inverse, garder une connexion ouverte pendant toute la vie de l'application est une erreur : si le serveur la coupe (redémarrage, délai d'inactivité), toutes les requêtes suivantes échouent.
Les trois façons d'exécuter une commande
Une MySqlCommand associe un texte SQL à une connexion. On l'exécute de trois façons, selon ce que la requête renvoie.
| Méthode | Pour | Renvoie |
|---|---|---|
ExecuteReaderAsync() | SELECT qui renvoie des lignes | un MySqlDataReader à parcourir |
ExecuteScalarAsync() | SELECT qui renvoie une seule valeur (COUNT(*), MAX(…)) | un object? |
ExecuteNonQueryAsync() | INSERT, UPDATE, DELETE | le nombre de lignes touchées (int) |
Les requêtes paramétrées
Une valeur saisie par l'utilisateur ne doit jamais être collée dans le texte de la requête. Si l'on écrit "… WHERE nom = '" + txtNom.Text + "'", une saisie qui contient une apostrophe casse la requête, et une saisie construite exprès peut modifier sa logique : c'est l'injection SQL, une des failles les plus exploitées (principe et parades).
La parade est la requête paramétrée : le texte SQL contient des marqueurs @nom, et les valeurs sont transmises à part. Elles ne sont jamais interprétées comme du SQL, quelle que soit leur forme.
const string sql = "SELECT numPatient, nom FROM patient WHERE nom LIKE @debut ORDER BY nom";
await using var cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@debut", txtRecherche.Text + "%");
AddWithValue déduit le type MySQL du type C# de la valeur. Pour imposer le type, on peut écrire cmd.Parameters.Add("@debut", MySqlDbType.VarChar).Value = ….
Les valeurs null. Une colonne facultative (le prénom) reçoit NULL en SQL. En ADO.NET, la valeur à transmettre est DBNull.Value ; l'expression (object?)p.Prenom ?? DBNull.Value la choisit quand le prénom est null.
Le DAO des patients
Voici PatientDao complet. Il implémente l'interface IPatientRepository déclarée par la couche métier (chapitre 8).
using Gsb.Metier;
using MySqlConnector;
namespace Gsb.Donnees;
public class PatientDao(Connexion connexion) : IPatientRepository
{
public async Task<List<Patient>> ListerAsync()
{
const string sql = """
SELECT numPatient, nom, prenom, dateNaissance, numeroSecu
FROM patient
ORDER BY nom, prenom
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
await using MySqlDataReader lecteur = await cmd.ExecuteReaderAsync();
var patients = new List<Patient>();
while (await lecteur.ReadAsync())
{
patients.Add(Lire(lecteur));
}
return patients;
}
public async Task<Patient?> TrouverAsync(int numPatient)
{
const string sql = """
SELECT numPatient, nom, prenom, dateNaissance, numeroSecu
FROM patient
WHERE numPatient = @num
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@num", numPatient);
await using MySqlDataReader lecteur = await cmd.ExecuteReaderAsync();
return await lecteur.ReadAsync() ? Lire(lecteur) : null;
}
public async Task<bool> NumeroSecuExisteAsync(string numeroSecu, int saufNumPatient)
{
const string sql = """
SELECT COUNT(*) FROM patient
WHERE numeroSecu = @secu AND numPatient <> @sauf
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("@secu", numeroSecu);
cmd.Parameters.AddWithValue("@sauf", saufNumPatient);
long nombre = (long)(await cmd.ExecuteScalarAsync())!;
return nombre > 0;
}
public async Task<int> AjouterAsync(Patient patient)
{
const string sql = """
INSERT INTO patient (nom, prenom, dateNaissance, numeroSecu)
VALUES (@nom, @prenom, @naissance, @secu)
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
AjouterParametres(cmd, patient);
await cmd.ExecuteNonQueryAsync();
return (int)cmd.LastInsertedId; // la valeur AUTO_INCREMENT attribuée
}
public async Task ModifierAsync(Patient patient)
{
const string sql = """
UPDATE patient
SET nom = @nom, prenom = @prenom, dateNaissance = @naissance, numeroSecu = @secu
WHERE numPatient = @num
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
AjouterParametres(cmd, patient);
cmd.Parameters.AddWithValue("@num", patient.NumPatient);
int lignes = await cmd.ExecuteNonQueryAsync();
if (lignes == 0) throw new RegleMetierException("Ce patient n'existe plus.");
}
public async Task SupprimerAsync(int numPatient)
{
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand("DELETE FROM patient WHERE numPatient = @num", conn);
cmd.Parameters.AddWithValue("@num", numPatient);
try
{
await cmd.ExecuteNonQueryAsync();
}
catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.RowIsReferenced2)
{
throw new RegleMetierException("Ce patient a des ordonnances : impossible de le supprimer.");
}
}
private static void AjouterParametres(MySqlCommand cmd, Patient p)
{
cmd.Parameters.AddWithValue("@nom", p.Nom);
cmd.Parameters.AddWithValue("@prenom", (object?)p.Prenom ?? DBNull.Value);
cmd.Parameters.AddWithValue("@naissance", (object?)p.DateNaissance ?? DBNull.Value);
cmd.Parameters.AddWithValue("@secu", (object?)p.NumeroSecu ?? DBNull.Value);
}
private static Patient Lire(MySqlDataReader l) => new()
{
NumPatient = l.GetInt32("numPatient"),
Nom = l.GetString("nom"),
Prenom = l.IsDBNull(l.GetOrdinal("prenom")) ? null : l.GetString("prenom"),
DateNaissance = l.IsDBNull(l.GetOrdinal("dateNaissance")) ? null : l.GetDateTime("dateNaissance"),
NumeroSecu = l.IsDBNull(l.GetOrdinal("numeroSecu")) ? null : l.GetString("numeroSecu"),
};
}
Les points à retenir :
- Lire un résultat.
ReadAsync()avance d'une ligne et renvoiefalsequand il n'y en a plus.GetInt32("numPatient"),GetString("nom"),GetDateTime(…)lisent une colonne par son nom. Lire une colonneNULLavecGetStringlève une exception : on teste d'abordIsDBNull, qui prend la position de la colonne, donnée parGetOrdinal. - Le type de
COUNT(*). MySQL renvoie un entier 64 bits :ExecuteScalarAsyncrenvoie donc unlongemballé dans unobject. Le convertir directement enint((int)resultat) lève uneInvalidCastException. - L'identifiant créé.
cmd.LastInsertedIddonne la valeurAUTO_INCREMENTattribuée par l'INSERT(l'équivalent deSELECT LAST_INSERT_ID()). ExecuteNonQueryAsyncrenvoie le nombre de lignes touchées. UnUPDATEqui touche 0 ligne signale un identifiant inexistant : un autre poste a peut-être supprimé le patient entre-temps.- Les chaînes brutes
"""…"""gardent le SQL lisible, sur plusieurs lignes.
Une requête avec jointure
La liste des ordonnances affiche le nom du patient et du médecin : une jointure sur trois tables (jointures SQL), dont le résultat remplit des OrdonnanceLisible.
public async Task<List<OrdonnanceLisible>> ListerLisiblesAsync()
{
const string sql = """
SELECT o.numOrdonnance, o.dateEmission,
CONCAT(p.nom, ' ', COALESCE(p.prenom, '')) AS patient,
CONCAT('Dr ', m.nom) AS medecin
FROM ordonnance o
JOIN patient p ON p.numPatient = o.numPatient
JOIN medecin m ON m.numMedecin = o.numMedecin
ORDER BY o.dateEmission DESC
""";
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(sql, conn);
await using MySqlDataReader l = await cmd.ExecuteReaderAsync();
var liste = new List<OrdonnanceLisible>();
while (await l.ReadAsync())
{
liste.Add(new OrdonnanceLisible(
l.GetInt32("numOrdonnance"),
l.GetDateTime("dateEmission"),
l.GetString("patient").Trim(),
l.GetString("medecin")));
}
return liste;
}
Avec le jeu d'essai, la grille affiche deux lignes : l'ordonnance 2 du 20/10/2026 à 17:10 pour Petit Léa (Dr Nguyen), puis l'ordonnance 1 du 15/10/2026 à 09:30 pour Dupont Marie (Dr Martin). COALESCE remplace un prénom NULL par une chaîne vide : sans lui, CONCAT renverrait NULL pour un patient sans prénom, et GetString lèverait une exception.
Enregistrer une ordonnance dans une transaction
Une ordonnance s'enregistre en plusieurs requêtes : un INSERT dans ordonnance, puis un INSERT par ligne dans contenir. Si la troisième ligne échoue (un code médicament inconnu, par exemple), il ne doit rien rester des deux premières : une ordonnance à moitié enregistrée serait dangereuse. On regroupe donc les requêtes dans une transaction : soit tout est validé (COMMIT), soit tout est annulé (ROLLBACK) (transactions SQL).
public async Task<int> EnregistrerAsync(Ordonnance ordonnance)
{
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using MySqlTransaction tx = await conn.BeginTransactionAsync();
try
{
await using var cmd = new MySqlCommand("""
INSERT INTO ordonnance (dateEmission, numMedecin, numPatient)
VALUES (@date, @medecin, @patient)
""", conn, tx);
cmd.Parameters.AddWithValue("@date", ordonnance.DateEmission);
cmd.Parameters.AddWithValue("@medecin", ordonnance.NumMedecin);
cmd.Parameters.AddWithValue("@patient", ordonnance.NumPatient);
await cmd.ExecuteNonQueryAsync();
int numOrdonnance = (int)cmd.LastInsertedId;
foreach (LigneOrdonnance ligne in ordonnance.Lignes)
{
await using var cmdLigne = new MySqlCommand("""
INSERT INTO contenir (numOrdonnance, codeMedicament, posologie, dureeJours)
VALUES (@num, @code, @posologie, @duree)
""", conn, tx);
cmdLigne.Parameters.AddWithValue("@num", numOrdonnance);
cmdLigne.Parameters.AddWithValue("@code", ligne.CodeMedicament);
cmdLigne.Parameters.AddWithValue("@posologie", ligne.Posologie);
cmdLigne.Parameters.AddWithValue("@duree", ligne.DureeJours);
await cmdLigne.ExecuteNonQueryAsync();
}
await tx.CommitAsync();
ordonnance.NumOrdonnance = numOrdonnance;
return numOrdonnance;
}
catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.NoReferencedRow2)
{
await tx.RollbackAsync();
throw new RegleMetierException("Un médicament de l'ordonnance n'existe pas dans le référentiel.");
}
catch
{
await tx.RollbackAsync();
throw; // on annule, puis on laisse l'appelant traiter l'erreur
}
}
Chaque commande reçoit la transaction en troisième argument (new MySqlCommand(sql, conn, tx)). Avec MySqlConnector, l'oublier provoque une erreur explicite : une commande ne peut pas s'exécuter hors de la transaction ouverte sur sa connexion.
Les deux blocs catch annulent la transaction. Le premier ne vise que l'erreur 1452 (un code médicament absent de la table medicament) et la traduit en message métier, que le formulaire affiche ; le second laisse passer toute autre erreur telle quelle (la section suivante détaille ces codes). Le DAO ne vérifie aucune règle : OrdonnanceService l'a fait avant de l'appeler.
Traduire les erreurs MySQL
Quand le serveur refuse une requête, le pilote lève une MySqlException. Sa propriété ErrorCode (une valeur de l'énumération MySqlErrorCode) dit pourquoi ; c'est elle qu'il faut tester, pas le texte du message, qui change selon la version et la langue du serveur.
| Code | MySqlErrorCode | Cause | Message pour l'utilisateur |
|---|---|---|---|
| 1062 | DuplicateKeyEntry | Valeur déjà présente dans une colonne UNIQUE | « Ce numéro de sécurité sociale est déjà utilisé. » |
| 1451 | RowIsReferenced2 | Suppression d'une ligne référencée par une clé étrangère | « Ce patient a des ordonnances : impossible de le supprimer. » |
| 1452 | NoReferencedRow2 | Clé étrangère vers une ligne qui n'existe pas | « Un médicament de l'ordonnance n'existe pas dans le référentiel. » |
| 1045 | AccessDenied | Identifiant ou mot de passe MySQL refusé | « Accès à la base refusé : contacte l'administrateur. » |
| 1042 | UnableToConnectToHost | Serveur injoignable | « Serveur de données injoignable. » |
Le filtre when d'un catch n'intercepte que l'erreur visée ; les autres continuent leur chemin. Le DAO traduit l'erreur technique en RegleMetierException, que le formulaire sait afficher, sans que l'interface connaisse MySQL.
catch (MySqlException ex) when (ex.ErrorCode == MySqlErrorCode.DuplicateKeyEntry)
{
throw new RegleMetierException("Ce numéro de sécurité sociale est déjà utilisé.");
}
Le code synchrone des sujets d'examen
Les sujets et beaucoup de projets existants utilisent MySql.Data en version synchrone, avec des blocs using classiques. Tu dois savoir le lire et l'écrire sur papier :
using MySql.Data.MySqlClient;
public List<Patient> ObtenirTous()
{
var liste = new List<Patient>();
using (var conn = new MySqlConnection(chaineConnexion))
{
conn.Open();
var cmd = new MySqlCommand("SELECT numPatient, nom, prenom FROM patient ORDER BY nom", conn);
using (MySqlDataReader r = cmd.ExecuteReader())
{
while (r.Read())
{
liste.Add(new Patient
{
NumPatient = r.GetInt32("numPatient"),
Nom = r.GetString("nom"),
Prenom = r.IsDBNull(r.GetOrdinal("prenom")) ? null : r.GetString("prenom"),
});
}
}
}
return liste;
}
La logique est exactement la même : Open au lieu de OpenAsync, ExecuteReader au lieu de ExecuteReaderAsync, Read au lieu de ReadAsync, using (…) { } au lieu de await using. Dans ton application, garde la version asynchrone ; sur une copie d'examen, les deux sont acceptées si les requêtes sont paramétrées.
Exercices
Exercice 1 sur 4 Compter les ordonnances d'un patient Réussi À revoir
Écris la méthode Task<int> CompterOrdonnancesAsync(int numPatient) d'OrdonnanceDao. Avec le jeu d'essai, que renvoie-t-elle pour les patients 1, 2 et 3 ?
Voir la réponseMasquer la réponse de l'exercice 1
public async Task<int> CompterOrdonnancesAsync(int numPatient)
{
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(
"SELECT COUNT(*) FROM ordonnance WHERE numPatient = @num", conn);
cmd.Parameters.AddWithValue("@num", numPatient);
long nombre = (long)(await cmd.ExecuteScalarAsync())!;
return (int)nombre;
}
Patient 1 (Dupont) : 1. Patient 2 (Petit) : 1. Patient 3 (Bernard) : 0. COUNT(*) renvoie toujours une ligne, avec 0 quand rien ne correspond : ExecuteScalarAsync ne renvoie jamais null ici. On convertit d'abord en long, le type renvoyé par MySQL, puis en int.
Tu avais trouvé ?
Exercice 2 sur 4 Réparer une requête dangereuse Réussi À revoir
Réécris cette méthode avec une requête paramétrée, et explique ce qui se passe avec la saisie D'Alembert dans la version d'origine.
public List<Medecin> Rechercher(string nom)
{
using var conn = new MySqlConnection(_chaine);
conn.Open();
var cmd = new MySqlCommand("SELECT numMedecin, nom FROM medecin WHERE nom = '" + nom + "'", conn);
// … lecture
}
Voir la réponseMasquer la réponse de l'exercice 2
Avec D'Alembert, la requête devient … WHERE nom = 'D'Alembert' : l'apostrophe ferme la chaîne SQL trop tôt et le serveur renvoie une erreur de syntaxe. Une saisie construite exprès pourrait de la même façon changer la condition : c'est une injection SQL.
public async Task<List<Medecin>> RechercherAsync(string nom)
{
await using MySqlConnection conn = await connexion.OuvrirAsync();
await using var cmd = new MySqlCommand(
"SELECT numMedecin, nom FROM medecin WHERE nom = @nom", conn);
cmd.Parameters.AddWithValue("@nom", nom);
await using MySqlDataReader l = await cmd.ExecuteReaderAsync();
var liste = new List<Medecin>();
while (await l.ReadAsync())
{
liste.Add(new Medecin { NumMedecin = l.GetInt32("numMedecin"), Nom = l.GetString("nom") });
}
return liste;
}
La valeur D'Alembert est transmise à part, comme une donnée : l'apostrophe n'a plus aucun effet sur la requête.
Tu avais trouvé ?
Exercice 3 sur 4 Pourquoi une transaction ? Réussi À revoir
L'ordonnance d'un patient contient trois médicaments : PARA1000, AMOX500 et XXX999, un code qui n'existe pas dans la table medicament. Que contient la base après l'appel à EnregistrerAsync, avec la transaction, puis si on avait écrit le même code sans transaction ?
Voir la réponseMasquer la réponse de l'exercice 3
Le troisième INSERT dans contenir échoue avec l'erreur 1452 (clé étrangère vers un médicament inexistant).
- Avec la transaction, le
catchqui vise l'erreur 1452 appelleRollbackAsync: l'ordonnance et ses deux premières lignes sont annulées, et la base est exactement comme avant l'appel. Le DAO lève ensuite uneRegleMetierException(« Un médicament de l'ordonnance n'existe pas dans le référentiel. »), que le formulaire affiche dans sa boîte d'avertissement. - Sans transaction, chaque
INSERTest validé dès son exécution : la base contient une ordonnance avec seulement deux médicaments sur trois, que le médecin croit complète. C'est une incohérence grave.
Tu avais trouvé ?
Exercice 4 sur 4 Les erreurs de lecture Réussi À revoir
Le patient Petit Léa n'a pas de numéro de sécurité sociale (NULL). Ce code de lecture plante à son tour dans la boucle. Quelle exception, et pourquoi ? Corrige la ligne.
NumeroSecu = l.GetString("numeroSecu"),
Voir la réponseMasquer la réponse de l'exercice 4
GetString sur une colonne NULL lève une exception : la valeur NULL de la base (DBNull.Value en C#) ne peut pas devenir une chaîne. Il faut tester la valeur avant de la lire :
NumeroSecu = l.IsDBNull(l.GetOrdinal("numeroSecu")) ? null : l.GetString("numeroSecu"),
Pour une colonne déclarée NOT NULL (comme nom), ce test est inutile.
Tu avais trouvé ?