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
usingpour libérer les ressources - Créer une classe
PersonnageDaoavecSauvegarderetChargerTous
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
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
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();
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
-
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'); -
Installez le package :
dotnet add package MySql.Data -
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
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'
);
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.
using pour chaque ressource IDisposableMySqlConnection, 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.
GetString / GetInt32 plutôt que le cast génériquePré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
| Notion | Résumé |
|---|---|
dotnet add package MySql.Data | Installe le connecteur MySQL via NuGet. |
MySqlConnection | Représente la connexion à la base. Toujours dans un using. |
cnx.Open() | Ouvre la connexion avant toute requête. |
MySqlCommand | Représente une requête SQL. Utiliser @param pour les paramètres. |
cmd.Parameters.AddWithValue | Ajoute 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 DAO | Classe dédiée à l'accès aux données. Sépare la logique métier de la persistance. |