Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

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.

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.

MySqlConnectorMySql.Data (Connector/NET)
ÉditeurProjet libre, communautaireOracle
LicenceMITGPL 2.0 avec exception FOSS
usingusing MySqlConnector;using MySql.Data.MySqlClient;
Version (novembre 2026)2.626.10
Bases viséesMySQL, MariaDB, Aurora, Azure Database for MySQL…MySQL
AsynchroneConçu pour : OpenAsync, ExecuteReaderAsync… réellement asynchronesMé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 :

bash
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 :

text
Server=srv-bdd.cabinet.local;Port=3306;Database=gsb_ordonnances;User ID=gsb_app;Password=…;SslMode=Required
CléRôle
ServerNom ou adresse IP du serveur (localhost en développement)
Port3306 par défaut
DatabaseLa base utilisée
User ID, PasswordLe compte MySQL de l'application, jamais root (chapitre 12)
SslModeRequired chiffre la connexion ; VerifyCA ou VerifyFull vérifient en plus le certificat du serveur
Connection TimeoutDé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 :

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

csharp
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éthodePourRenvoie
ExecuteReaderAsync()SELECT qui renvoie des lignesun MySqlDataReader à parcourir
ExecuteScalarAsync()SELECT qui renvoie une seule valeur (COUNT(*), MAX(…))un object?
ExecuteNonQueryAsync()INSERT, UPDATE, DELETEle 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.

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

csharp
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 renvoie false quand il n'y en a plus. GetInt32("numPatient"), GetString("nom"), GetDateTime(…) lisent une colonne par son nom. Lire une colonne NULL avec GetString lève une exception : on teste d'abord IsDBNull, qui prend la position de la colonne, donnée par GetOrdinal.
  • Le type de COUNT(*). MySQL renvoie un entier 64 bits : ExecuteScalarAsync renvoie donc un long emballé dans un object. Le convertir directement en int ((int)resultat) lève une InvalidCastException.
  • L'identifiant créé. cmd.LastInsertedId donne la valeur AUTO_INCREMENT attribuée par l'INSERT (l'équivalent de SELECT LAST_INSERT_ID()).
  • ExecuteNonQueryAsync renvoie le nombre de lignes touchées. Un UPDATE qui 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.

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

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

CodeMySqlErrorCodeCauseMessage pour l'utilisateur
1062DuplicateKeyEntryValeur déjà présente dans une colonne UNIQUE« Ce numéro de sécurité sociale est déjà utilisé. »
1451RowIsReferenced2Suppression d'une ligne référencée par une clé étrangère« Ce patient a des ordonnances : impossible de le supprimer. »
1452NoReferencedRow2Clé étrangère vers une ligne qui n'existe pas« Un médicament de l'ordonnance n'existe pas dans le référentiel. »
1045AccessDeniedIdentifiant ou mot de passe MySQL refusé« Accès à la base refusé : contacte l'administrateur. »
1042UnableToConnectToHostServeur 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.

csharp
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 :

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

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

csharp
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 catch qui vise l'erreur 1452 appelle RollbackAsync : 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 une RegleMetierException (« 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 INSERT est 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.

csharp
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 :

csharp
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é ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Entity Framework Core avec MySQLRevoir le sommaire du cours