Ce post va présenter comment effectuer l’une des opérations de base en C# : la connexion à une base de données et l’exécution de requêtes SQL. Il est nécessaire d’avoir au préalable les connaissances de base de C# et du SQL

Connexion

La première étape est donc la connexion. Celle-ci nécessite deux choses :

  • Le driver pour la base de données
  • La chaine de connexion (connection string)

Vous pourrez trouver l’un comme l’autre sur ce site : www.connectionstrings.com. Cependant, si par exemple vous utilisez une base de données SQL Server, les drivers sont inclus. Voici également la chaine de connexion de base pour une telle base de données :

"Data Source=AdresseDuServeur;Initial Catalog=NomDeLaBase;User Id=NomDUtilisateur;Password=MotDePasse;"
// Il faut évidemment replacer AdresseDuServeur, NomDeLaBase, etc par la valeur correspondante

Pour créer la connexion, on utilise la classe SqlConnection (dont le namespace (et peut-être le préfixe) changera selon votre driver). Pour SQL Server, le namespace est System.Data.SqlClient.

SqlConnection maConnexion = new SqlConnection("Data Source=127.0.0.1,1433;Initial Catalog=MaBase;User Id=Krimog;Password=monMotDePasse;");

Une fois la connexion créée, on peut l’ouvrir :

maConnexion.Open();

Cette dernière ligne peut évidemment générer une exception si, par exemple, la chaine de connexion est mauvaise. Il faudra également fermer la connexion grâce à l’instruction :

maConnexion.Close();

Création de la requête

Pour cela, on va utiliser la classe SqlCommand, indiquer la connexion et la requête :

SqlCommand maCommande = new SqlCommand();
maCommande.Connection = maConnexion;
maCommande.CommandText = "INSERT INTO maTable (monChamp1, monChamp2) VALUES ("
                       + monInt + ", '" + monstring + "');";

Voici une manière simple de créer et d’initialiser un objet SqlCommand.

Exécuter une requête (hors SELECT)

La première question à se poser est la suivante : ma requête va-t-elle renvoyer une valeur ? C’est bien simple, tout dépend de la requête : une requête SELECT renvoie des données, mais pas les autres requêtes (INSERT, UPDATE, DELETE).
Commençons par quelque chose de simple : DELETE. Nous allons donc utiliser la méthode ExecuteNonQuery(). Celle-ci nous renvoie le nombre d’enregistrement affectés par la requête :

maCommande.CommandText = "DELETE FROM maTable WHERE id < " + monInt;
int nbEnregistrementsAffectes = maCommande.ExecuteNonQuery();
if (nbEnregistrementsAffectes > 0)
{
    Console.WriteLine(nbEnregistrementsAffectes + " enregistrements supprimés");
}
else
{
    Console.WriteLine("Aucun enregistrement supprimé");
}

Là encore, la méthode ExecuteNonQuery() peut générer une exception, par exemple si la requête n’est pas valide ou que la connexion n’est pas ouverte.

Exécuter une requête SELECT

Là, les choses se compliquent un tout petit peu. Tout simplement parce qu’il faut gérer ce que renvoie la requête. Il y a cependant une méthode simple pour récupérer 1 résultat (donc 1 colonne d’1 ligne) : ExecuteScalar().

maCommande.CommandText = "SELECT NomClient FROM Clients WHERE IdClient = " + monInt;
string nomDuClient = maCommande.ExecuteScalar().ToString();
Console.WriteLine("Mon client s'appelle " + nomDuClient);

Pour être précis, la méthode ExecuteScalar() renvoie un object, d’où l’intérêt du ToString() derrière.

Imaginons maintenant que vous ayez pleins de champs et pleins d’enregistrements. Il faudra alors utiliser un SqlDataReader :

maCommande.CommandText = "SELECT NomClient, PrenomClient FROM Clients";
SqlDataReader monReader = maCommande.ExecuteReader();
while (monReader.Read()) // Tant qu'il y a des résultats, je lis le prochain
{
    Console.WriteLine(monReader["PrenomClient"].ToString() + " " + monReader["NomClient"].ToString());
    // ou
    Console.WriteLine(monReader[1].ToString() + " " + monReader[0].ToString());
    // ou
    Console.WriteLine(monReader.GetString(1) + " " + monReader.GetString(0));
}
monReader.Close(); // On n'oublie pas de fermer le reader

Il existe une dernière solution qui consiste à insérer le résultat de la requête dans un DataSet grâce à un SqlDataAdapter. Cependant, n’ayant pas beaucoup testé ce système, je ne m’étendrai pas longtemps dessus.

DataSet mesClients = new DataSet();
maCommande.CommandText = "SELECT NomClient, PrenomClient FROM Clients";
SqlDataAdapter monAdapter = new SqlDataAdapter();
monAdapter.SelectCommand = maCommande;
monAdapter.Fill(mesClients);
// Le dataset mesClients est alors rempli

Les requêtes paramétrées

Voici donc quelque chose de très simple, mais pas assez utilisé. De manière générale, une requête n’a d’intérêt que si certaines valeurs sont des variables. Mais dans l’exemple précédent du INSERT, on voit tout de suite que ce n’est pas simple. De plus, il est possible de faire de l’injection SQL dans cette requête. Voici donc 2 problèmes qui sont résolus simplement avec le fait d’utiliser des requêtes paramétrées : simplicité et sécurité.
La première étape consiste à définir où intégrer les variables dans la requête, et la deuxième donne les valeurs aux variables.

// On remplace les valeurs par @unNom, sans jamais mettre de ' autour
maCommande.CommandText = "INSERT INTO maTable (monChamp1, monChamp2) VALUES (@maValeur1, @maValeur2);";
// Puis on définit les valeurs
maCommande.Parameters.Add(new SqlParameter("@maValeur1", monInt));
maCommande.Parameters.Add(new SqlParameter("@maValeur2", monstring));

Ainsi, nous n’avons même pas à nous préoccuper de mettre ou non des quotes autour des string, d’utiliser un point ou une virgule pour les doubles… Tout est directement géré à partir du type des objets (en l’occurrence monInt et monstring).

Les procédures stockées

Voici bien un moyen de gagner en performances et en maintenabilité de votre application : utiliser des procédures stockées. Le principe est que votre requête est directement enregistrée et compilée dans la base de données. Donc aucun besoin de recompiler l’application pour modifier une requête. Il suffit juste de modifier la procédure stockée directement dans votre base.

maCommande.CommandText = "NomDeMaProcedure";
maCommande.CommandType = CommandType.StoredProcedure;
maCommande.Parameters.Add(new SqlParameter("@MonParametre", maValeur));

Quant à l’exécution elle-même, on utilise ExecuteNonQuery(), ExecuteScalar() ou ExecuteReader() selon les mêmes conditions qu’une requête manuelle.

Résumé

Effectuer une requête sur une base de données est très simple, mais il faut absolument prendre l’habitude d’utiliser des requêtes paramétrées, et si possible des procédures stockées.
Voici également un code pour résumer l’ensemble :

public void getListeClientsDansUneVille(string ville)
{
    SqlConnection maConnexion = null;
    try
    {
        maConnexion = new SqlConnection("Data Source=127.0.0.1,1433;Initial Catalog=MaBase;User Id=Krimog;Password=monMotDePasse;");
        SqlCommand maCommande = new SqlCommand();
        maCommande.Connection = maConnexion;
        maCommande.CommandText = "SELECT PrenomClient, NomClient FROM Clients WHERE VilleClient = @Ville;";
        maCommande.Parameters.Add(new SqlParameter("@Ville", ville));
        maConnexion.Open();
        SqlDataReader monReader = maCommande.ExecuteReader();
        if (!monReader.HasRows)
        {
            Console.WriteLine("Aucun client n'habite à " + ville);
        }
        else
        {
            while (monReader.Read())
            {
                Console.WriteLine(monReader["PrenomClient"].ToString() + " " + monReader["NomClient"].ToString() + " habite à " + ville);
            }
        }
    }
    catch
    {
        Console.WriteLine("Erreur lors de la connexion ou l'exécution de la requête");
    }
    finally
    {
        if (maConnexion != null && maConnexion.State == ConnectionState.Open)
        {
            maConnexion.Close();
        }
    }
}