Aller au contenu principal

6) Persistance en BD

Objectifs de la séance

  • Installer le package NuGet MySql.Data
  • Ouvrir une connexion MySQL avec MySqlConnection
  • Exécuter des requêtes paramétrées avec MySqlCommand
  • Lire des données avec MySqlDataReader
  • Utiliser le bloc using pour libérer les ressources
  • Créer une classe PersonnageDao avec Sauvegarder et ChargerTous

Notions théoriques

ADO.NET et NuGet

ADO.NET est la couche d'accès aux données standard de .NET. Pour MySQL, on utilise le package MySql.Data (fourni par Oracle).

dotnet add package MySql.Data
info

NuGet est le gestionnaire de packages .NET, équivalent de Maven ou Gradle en Java. La commande dotnet add package ajoute la dépendance dans votre fichier .csproj.

Connexion et using

using MySql.Data.MySqlClient;

string chaineConnexion = "Server=localhost;Database=jeu_rpg;Uid=root;Pwd=root;";

using MySqlConnection connexion = new MySqlConnection(chaineConnexion);
connexion.Open();
// Travail avec la connexion...
// La connexion est fermée automatiquement en fin de bloc using
info

Le bloc using garantit que la ressource (connexion, lecteur) est fermée et libérée même si une exception survient. C'est l'équivalent de try-finally avec .Close(), mais plus concis. À toujours utiliser avec les connexions et les readers.

Requête paramétrée

string sql = "INSERT INTO personnages (nom, points_de_vie, force) VALUES (@nom, @pv, @force)";

using MySqlCommand cmd = new MySqlCommand(sql, connexion);
cmd.Parameters.AddWithValue("@nom", personnage.Nom);
cmd.Parameters.AddWithValue("@pv", personnage.PointsDeVie);
cmd.Parameters.AddWithValue("@force", personnage.Force);
cmd.ExecuteNonQuery();
attention

N'utilisez jamais la concaténation de chaînes pour construire vos requêtes SQL. Une requête comme "INSERT INTO ... VALUES ('" + nom + "')" est vulnérable à l'injection SQL. Utilisez toujours des paramètres @nomParam.

Lire des données avec MySqlDataReader

string sql = "SELECT nom, points_de_vie, force FROM personnages";

using MySqlCommand cmd = new MySqlCommand(sql, connexion);
using MySqlDataReader reader = cmd.ExecuteReader();

List<Personnage> liste = new();
while (reader.Read())
{
string nom = reader.GetString("nom");
int pv = reader.GetInt32("points_de_vie");
int frc = reader.GetInt32("force");
liste.Add(new Guerrier(nom, pv, frc, 0)); // exemple simplifié
}

Pattern DAO (Data Access Object)

Le DAO isole la logique d'accès aux données dans une classe dédiée. Cela respecte le principe S de SOLID (Single Responsibility) : la classe Personnage ne connaît pas la base de données.

class PersonnageDao
{
private readonly string _chaineConnexion;

public PersonnageDao(string chaineConnexion)
=> _chaineConnexion = chaineConnexion;

public void Sauvegarder(Personnage p) { /* INSERT */ }
public List<Personnage> ChargerTous() { /* SELECT */ return new(); }
}

Mise en place de l'environnement

  1. Créez la base de données MySQL :

    CREATE DATABASE jeu_rpg;
    USE jeu_rpg;

    CREATE TABLE personnages (
    id INT AUTO_INCREMENT PRIMARY KEY,
    nom VARCHAR(100) NOT NULL,
    points_de_vie INT NOT NULL DEFAULT 100,
    force INT NOT NULL DEFAULT 10,
    type VARCHAR(50) NOT NULL DEFAULT 'Guerrier'
    );
  2. Installez le package :

    dotnet add package MySql.Data
  3. Ajoutez l'import en tête de fichier :

    using MySql.Data.MySqlClient;

Exemple pratique

using System;
using System.Collections.Generic;
using MySql.Data.MySqlClient;

// --- Classes Personnage, Guerrier (reprises des séances précédentes) ---
abstract class Personnage
{
public string Nom { get; protected set; }
public int PointsDeVie { get; protected set; }
public int Force { get; protected set; }

protected Personnage(string nom, int pv, int force)
{ Nom = nom; PointsDeVie = pv; Force = force; }

public abstract void Attaquer(Personnage cible);
}

class Guerrier : Personnage
{
public Guerrier(string nom, int pv, int force) : base(nom, pv, force) { }
public override void Attaquer(Personnage cible)
=> Console.WriteLine($"{Nom} frappe {cible.Nom} pour {Force} dégâts !");
}

// --- DAO ---
class PersonnageDao
{
private readonly string _connexionString;

public PersonnageDao(string connexionString)
=> _connexionString = connexionString;

public void Sauvegarder(Personnage p)
{
using MySqlConnection cnx = new(_connexionString);
cnx.Open();

string sql = @"INSERT INTO personnages (nom, points_de_vie, force, type)
VALUES (@nom, @pv, @force, @type)";

using MySqlCommand cmd = new(sql, cnx);
cmd.Parameters.AddWithValue("@nom", p.Nom);
cmd.Parameters.AddWithValue("@pv", p.PointsDeVie);
cmd.Parameters.AddWithValue("@force", p.Force);
cmd.Parameters.AddWithValue("@type", p.GetType().Name);
cmd.ExecuteNonQuery();

Console.WriteLine($" ✓ {p.Nom} sauvegardé.");
}

public List<Personnage> ChargerTous()
{
using MySqlConnection cnx = new(_connexionString);
cnx.Open();

string sql = "SELECT nom, points_de_vie, force FROM personnages";

using MySqlCommand cmd = new(sql, cnx);
using MySqlDataReader reader = cmd.ExecuteReader();

List<Personnage> liste = new();
while (reader.Read())
{
string nom = reader.GetString("nom");
int pv = reader.GetInt32("points_de_vie");
int frc = reader.GetInt32("force");
liste.Add(new Guerrier(nom, pv, frc));
}
return liste;
}
}

// --- Programme principal ---
string cnxStr = "Server=localhost;Database=jeu_rpg;Uid=root;Pwd=root;";
PersonnageDao dao = new(cnxStr);

List<Personnage> equipe = new()
{
new Guerrier("Kael", 100, 15),
new Guerrier("Theron", 90, 12),
};

Console.WriteLine("=== Sauvegarde ===");
foreach (Personnage p in equipe)
dao.Sauvegarder(p);

Console.WriteLine("\n=== Chargement ===");
List<Personnage> charges = dao.ChargerTous();
foreach (Personnage p in charges)
Console.WriteLine($" {p.Nom} — PV : {p.PointsDeVie} Force : {p.Force}");

Test de mémorisation/compréhension


Quel package NuGet faut-il installer pour accéder à MySQL depuis C# ?


Pourquoi utilise-t-on un bloc `using` avec `MySqlConnection` ?


Pourquoi utilise-t-on des paramètres `@nom` dans les requêtes SQL ?


Quelle méthode exécute une requête INSERT ou UPDATE sans retourner de données ?


Quel est l'avantage du pattern DAO pour la classe `Personnage` ?


TP pour réfléchir et résoudre des problèmes

Étape 1 — Créer la table MySQL

Exécutez le script SQL suivant dans votre client MySQL (MySQL Workbench, phpMyAdmin, ou en ligne de commande).

CREATE DATABASE IF NOT EXISTS jeu_rpg;
USE jeu_rpg;

CREATE TABLE IF NOT EXISTS personnages (
id INT AUTO_INCREMENT PRIMARY KEY,
nom VARCHAR(100) NOT NULL,
points_de_vie INT NOT NULL DEFAULT 100,
force INT NOT NULL DEFAULT 10,
type VARCHAR(50) NOT NULL DEFAULT 'Guerrier'
);

Bonne pratique - Ne jamais coder la chaîne de connexion en dur

Dans un vrai projet, la chaîne de connexion est stockée dans un fichier de configuration (appsettings.json) ou dans une variable d'environnement, jamais en clair dans le code source. Vous évitez ainsi d'exposer vos identifiants dans git. Pour ce TP, c'est acceptable, mais notez cette règle pour vos projets réels.

Étape 2 — Implémenter Sauvegarder dans PersonnageDao

Complétez la méthode Sauvegarder qui insère un personnage en base de données.


Bonne pratique - using pour chaque ressource IDisposable

MySqlConnection, MySqlCommand et MySqlDataReader implémentent IDisposable. Enveloppez-les systématiquement dans un using. Si une exception se produit à mi-chemin, les ressources seront quand même libérées, évitant les fuites de connexions et les blocages de base de données.

Étape 3 — Implémenter ChargerTous

Complétez la méthode ChargerTous qui lit tous les personnages depuis la base.


Bonne pratique - GetString / GetInt32 plutôt que le cast générique

Préférez reader.GetString("colonne") à (string)reader["colonne"]. Les méthodes typées sont plus lisibles, lèvent des exceptions plus précises en cas d'erreur, et évitent les conversions implicites problématiques (par exemple DBNull vers string).

📌 Une solution

Ce qu'il faut retenir

NotionRésumé
dotnet add package MySql.DataInstalle le connecteur MySQL via NuGet.
MySqlConnectionReprésente la connexion à la base. Toujours dans un using.
cnx.Open()Ouvre la connexion avant toute requête.
MySqlCommandReprésente une requête SQL. Utiliser @param pour les paramètres.
cmd.Parameters.AddWithValueAjoute un paramètre de façon sécurisée (anti-injection SQL).
ExecuteNonQuery()Exécute un INSERT, UPDATE ou DELETE.
ExecuteReader()Exécute un SELECT et retourne un lecteur ligne par ligne.
reader.Read()Avance d'une ligne. Retourne false en fin de résultats.
Pattern DAOClasse dédiée à l'accès aux données. Sépare la logique métier de la persistance.