[Plan du site]
Vous êtes ici ---
> Le Site du Zéro
> Les tutoriels
> Non-Officiels
> Programmation
> C# / .NET
> Utilisez les bases de données dans vos programmes C#
> Lecture du tutoriel
Utilisez les bases de données dans vos programmes C#
Vous vous apprêtez à lire un tutoriel rédigé par un membre de ce site. Malgré tout le soin que ce membre a pu apporter au tutoriel, nous ne pouvons pas garantir que les informations contenues sur cette page sont exactes à 100%. Merci de garder cela en tête lorsque vous lirez cette page ;o)
Salut !
Les bases de données sont des moyens très puissants de gérer des flots importants de données.
Nous allons donc nous y intéresser. Comment les différents SGBD sont-ils implémentés à l'aide du .NET Framework ? Comment s'en sert-on ?
Pour expliquer tout ça avec un exemple pratique, nous utiliserons l'API SQLite, qui vous permettra d'exploiter simplement et facilement une base de données en moins de 10 minutes.
ADO.NET regroupe l'ensemble des classes permettant d'accéder aux bases de données, ainsi que celles permettant de rapatrier et de traiter les données.
Il est inclus dans le .NET Framework, son fonctionnement est donc similaire pour tous les langages .NET (C# et VB.NET principalement).
On peut utiliser les bases de données de deux façons avec ADO.NET. Le mode connecté et le mode déconnecté.
Mode connecté
Dans ce mode, l'application reçoit les données progressivement, en fonction de ses besoins. Elle est connectée en permanence à la base et enregistre les modifications directement. Cette façon de procéder a l'avantage d'être facile à implémenter. Malheureusement, elle génère beaucoup d'accès à la base de données, et génère donc plus de trafic réseau. Concrètement, c'est un goulet d'étranglement pour les performances. ADO.NET était conçu à l'origine pour travailler en mode connecté.
Mode déconnecté
Ce mode a pour spécificité de ne pas rester connecté en permanence avec la base de données. L'application rapatrie une partie de la base de données puis la stocke en mémoire. Elle peut donc travailler sur ces données, éventuellement les modifier puis appliquer les changements en envoyant les nouvelles données vers la base. Cette méthode a aussi ses avantages et ses inconvénients : elle permet de minimiser les connexions à la base, et donc d'optimiser les performances ; en revanche cette méthode est beaucoup plus complexe à implémenter, et cela nécessite beaucoup de mémoire (l'application doit stocker une partie de la base en mémoire). De plus, si la base de données est modifiée une fois que le programme en a récupéré une partie, cela risque de poser des problèmes car l'application travaille alors sur des données obsolètes.
Les providers
Les providers, ou fournisseurs, sont une couche (concrètement, un groupe de classes) permettant d'accéder à un certain type de bases de données. Par exemple, il y a un provider pour les bases SQL Server, un provider Oracle, un provider ODBC, ... La bibliothèque SQLite que nous utiliserons par la suite n'est rien d'autre qu'un autre provider.
Chaque provider propose des classes différentes permettant d'accéder aux données. Pour éviter de devoir réécrire tout le code en cas de changement de base de données (donc de provider), il existe des classes génériques DbConnection
, DbCommand
fournies par le service DbProviderFactory
. Leur utilisation ne sera pas détaillée ici, cependant vous pouvez vous renseigner par vous-même.
Passons maintenant à la pratique. Nous allons installer le provider SQLite. Il en existe plusieurs, mais nous allons choisir celui-ci : System.Data.SQLite. Rendez vous sur
le site officiel, et installez la dernière version. Prenez bien soin de cocher la case correspondant à Visual Studio (ou VSExpress) lors de l'installation.
Ensuite, créez un nouveau projet (j'ai choisi un projet console par souci de simplicité). Il vous faut ajouter la référence à la bibliothèque. Dans l'explorateur de solution, faites un clic droit "References", puis choisissez "Ajouter une référence".
Une fenêtre s'ouvre et affiche les bibliothèques disponibles. Choisissez donc System.Data.SQLite.
Enfin, il faut indiquer à Visual Studio de copier la bibliothèque dans le répertoire de destination. C'est un fichier DLL d'environ 800 Kio. Cela permettra d'exécuter le programme sans forcément installer la bibliothèque. Déroulez les références, séléctionnez System.Data.SQLite, puis dans les propriétés, choisissez "True" pour "Copie locale".
Vous êtes donc prêts à utiliser ce provider. Un dernier détail tout de même, vous devez ajouter
using System.Data.SQLite
au début de votre fichier.
Voici donc notre code qui nous servira à apprendre à exploiter une base de données avec SQLite.
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
namespace SimpleSQLite
{
class Program
{
static void Main(string[] args)
{
}
}
}
|
Maintenant, passons au vif du sujet !
Établir une connexion
Avant de s'amuser avec les requêtes, nous devons nous connecter à une base. Avec SQLite, les bases de données sont stockées dans un seul fichier. Une connexion peut donc se résumer à définir un fichier seul. Il est possible de protéger l'accès avec un mot de passe.
Toute connexion s'écrit sous la forme d'une
ConnectionString
, c'est une chaîne de caractères qui a la syntaxe suivante :
Code : Autre1
| Param1 = Valeur1 ; Param2 = Valeur2 ; |
Il est possible de s'amuser à faire cette chaîne à la main, il est cependant plus pratique de demander à une classe de le faire pour nous. L'objet
SQLiteConnectionStringBuilder
est conçu spécialement pour ça. Il va générer une chaîne qui sera nécessaire pour l'objet
SQLiteConnection
. On initialise l'objet
SQLiteConnectionStringBuilder
, puis on remplit les attributs qu'on a besoin (au minimum le fichier de la BDD à utiliser). On appelle ensuite la méthode
ToString()
, qui va générer la
ConnectionString
à partir des paramètres qu'on lui a donnés. Il suffit ensuite de créer un objet
SQLiteConnection
avec le constructeur (surchargé) qui prend en paramètre la chaîne de connection. Ensuite, la connection s'ouvre et se ferme avec les méthodes
Open()
et
Close()
de cet objet
SQLiteConnection
. Voici un exemple de code, qui explique mieux que des mots :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 | using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;
namespace SimpleSQLite
{
class Program
{
static void Main(string[] args)
{
// Encapsulons tout cela dans un bloc try, au cas où une erreur se produirait.
try
{
// On crée un nouvel objet SQLiteConnectionStringBuilder.
SQLiteConnectionStringBuilder SQLCSB = new SQLiteConnectionStringBuilder();
// On définit nos paramètres.
SQLCSB.DataSource = "Base.db3"; // La base de données à utiliser
SQLCSB.FailIfMissing = false; // Si la base n'existe pas, alors on la crée automatiquement
SQLCSB.Password = "kze3sgDJ"; // Permet de définir le mot de passe à utiliser pour l'accès à la BDD
// Obtenons cette ConnectionString !
string ConnectionString = SQLCSB.ToString();
// On crée une connexion, le constructeur prend en paramètre la ConnectionString.
SQLiteConnection SQLC = new SQLiteConnection(ConnectionString);
// On ouvre la connexion.
SQLC.Open();
//
// C'est ici qu'il faut faire des requêtes.
//
// On ferme la connexion.
SQLC.Close();
}
catch (Exception Ex)
{
// On affiche l'erreur.
Console.WriteLine(string.Format("*** Une exception a été lancée : {0} ***", Ex.Message));
}
}
}
}
|
Tout ceci n'est pas compliqué à comprendre. On a ouvert une connexion à l'aide d'une
ConnectionString
générée par le
SQLiteConnectionStringBuilder
. Ca fait des noms de classe un peu longs, mais c'est comme ça
Vous aurez remarqué qu'on se connecte à une base de données qui n'existe pas : nous ne l'avons pas préalablement créée. Cela n'est pas un problème : la classe SQLiteConnection
va se charger de le faire pour nous. Si la base de données n'existe pas et que l'attribut FailIfMissing
est à false
, alors la base sera créée automatiquement, avec le mot de passe spécifié dans l'attribut Password
s'il est spécifié.
Vous pouvez essayer de compiler et de lancer l'exemple ci-dessus. Si tout fonctionne, vous verrez une fenêtre de console apparaître, puis se fermer. C'est normal, on a initialisé une connexion, on la referme, puis le programme se termine. Nous n'avons pas mis de routine d'affichage.
ExecuteNonQuery()
: pour les requêtes du type INSERT INTO
, UPDATE
, ALTER TABLE
, DELETE FROM
, ...
Pour l'instant, notre base est vide. Nous allons corriger cela en ajoutant des tables et des données. Comment ? En exécutant une requête SQL !
L'objet permettant d'exécuter des requêtes SQL sur des bases est
SQLiteCommand
. Il ne s'instancie pas directement, car il ne saurait pas dans quelle base effectuer les requêtes qu'on lui spécifie. Il faut donc demander à notre objet
SQLiteConnection
de nous fournir un objet
SQLiteCommand
grâce à la méthode
CreateCommand()
.
Une fois que nous avons notre objet
SQLiteCommand
, nous devons spécifier l'attribut
CommandText
qui n'est autre que la requête SQL à effectuer.
Toutes vos requêtes doivent elles aussi se terminer avec un point virgule (";").
Une fois que nous avons spécifié notre requête, on peut l'exécuter en appelant
ExecuteNonQuery()
. Cette méthode exécute la requête, puis renvoie un entier correspondant au nombre de rows (entrées) affectées par la requête. Elle permet donc d'exécuter des requêtes ne demandant pas de retour, par exemple les
INSERT INTO
,
UPDATE
,
ALTER TABLE
,
DELETE FROM
, etc. Voici un exemple de code, qui crée une table fictive "Employes" et qui insère des données à l'intérieur :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 | // Ce code est à placer entre SQLC.Open() et SQLC.Close().
// On demande à notre connection de nous créer un objet SQLiteCommand.
SQLiteCommand SQLCmd = SQLC.CreateCommand();
// Créons une table fictive.
SQLCmd.CommandText = "CREATE TABLE Employes (Nom VARCHAR(255), Prenom VARCHAR(255), Salaire INTEGER);";
SQLCmd.ExecuteNonQuery();
// Insérons des données fantaisistes...
SQLCmd.CommandText = "INSERT INTO Employes (Nom, Prenom, Salaire) VALUES ('John', 'Doe', 2100);";
Console.WriteLine(SQLCmd.ExecuteNonQuery()); // Cela nous permettra de voir combien d'entrées auront été affectées
SQLCmd.CommandText = "INSERT INTO Employes (Nom, Prenom, Salaire) VALUES ('Sam', 'Linston', 3600);";
Console.WriteLine(SQLCmd.ExecuteNonQuery()); // Idem.
// Pour éviter qu'elle se ferme instantanément.
Console.ReadKey();
|
Voici le résultat produit :
Ouf, nos requêtes ont bien affecté une entrée. Ce qui est parfaitement logique puisque ce sont des requêtes du type
INSERT INTO
!
La méthode
ExecuteNonQuery()
est pratique, mais elle n'est pas adaptée aux requêtes de type
SELECT
. En effet, on se fiche bien du nombre d'entrées affectées, on veut les données de la table ! Heureusement, il existe les méthodes
ExecuteReader()
et
ExecuteScalar()
qui vont nous faciliter la vie.
ExecuteScalar()
: pour les requêtes SELECT
ne renvoyant qu'une valeur
ExecuteScalar()
est une méthode de l'objet
SQLiteCommand
. Lorsqu'on a rempli le champ
CommandText
et qu'on fait appel à cette méthode, l'objet va exécuter la requête et la première valeur de la première colonne que renvoie la requête sera renvoyée. Cela est donc parfaitement adapté aux requêtes du type
SELECT COUNT()
,
SELECT SUM()
,
SELECT AVG()
, ... qui ne renvoient qu'une seule valeur.
Voici un petit exemple pour illustrer l'utilisation de cette méthode :
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12 | // Ce code est à placer entre SQLC.Open() et SQLC.Close().
// On crée une SQLiteCommand
SQLiteCommand SQLCmd = SQLC.CreateCommand();
// On spécifie notre requête
SQLCmd.CommandText = "SELECT COUNT(*) FROM Employes;";
Console.WriteLine(string.Format("Nombre d'entrées dans la table Employes : {0}", SQLCmd.ExecuteScalar().ToString()));
// Pour pouvoir lire les résultats...
Console.ReadKey();
|
Et voici le résultat :
Cette méthode est donc pratique à utiliser car elle évite d'utiliser le mastodon SQLiteDataReader, que nous allons voir tout de suite, pour une seule valeur.
ExecuteReader()
: pour les requêtes SELECT
classiques
Lorsqu'on effectue des requêtes du type
SELECT
plus classiques, on a besoin d'accéder à plusieurs cellules. Or, ni
ExecuteNonQuery()
ni
ExecuteScalar()
ne permettent de faire ça. C'est là qu'on va utiliser la méthode
ExecuteReader()
de notre objet
SQLiteCommand
.
Cette méthode renvoie un objet
SQLiteDataReader
. Comme son nom l'indique, il sert à lire les données renvoyées par la requête. Comment s'en servir ? Tout simplement en utilisant sa méthode
Read()
. A chaque appel de cette méthode, l'objet lit une entrée renvoyée par la requête, puis renvoie
true
s'il reste encore des données à lire,
false
sinon. On accède ensuite aux données avec l'accesseur
[]
sur cet objet. Concrètement, si on a un objet
SQLiteDataReader
nommé
SQLDR
et qu'on a spécifié une requête de type
SELECT
, alors on peut accéder aux données en utilisant la notation
SQLDR["NomDeLaColonne"]
ou encore
SQLDR[0]
si on souhaite accéder à la première colonne (tout ceci après lui avoir demandé de lire une entrée avec
Read()
bien sûr).
Le fait que la méthode
Read()
renvoie
true
ou
false
est très pratique, ça permet de s'en servir dans une simple boucle
while()
. Dans l'exemple ci-dessous, nous affichons le contenu de notre table Employes. Remarquez comment on accède aux données :
SQLDR["Nom"]
,
SQLDR["Prenom"]
, ...
Code : C# 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24 | // Ce code est à placer entre SQLC.Open() et SQLC.Close().
// On demande à notre connection de nous créer un objet SQLiteCommand.
SQLiteCommand SQLCmd = SQLC.CreateCommand();
SQLCmd.CommandText = "SELECT Nom, Prenom, Salaire, ROWID FROM Employes";
// On affiche les en-têtes (facultatif, mais ça permet d'y voir plus clair)
Console.WriteLine("Nom\t\tPrénom\t\tSalaire\t\tROWID");
Console.WriteLine("-------------------------------------------------------");
// On crée un objet SQLiteDataReader
SQLiteDataReader SQLDReader = SQLCmd.ExecuteReader();
// La méthode Read() lit l'entrée actuelle puis renvoie true tant qu'il y a des entrées à lire.
while (SQLDReader.Read())
{
// On affiche les données...
Console.WriteLine(string.Format("{0}\t\t{1}\t\t{2}\t\t{3}",
SQLDReader["Nom"], SQLDReader["Prenom"], SQLDReader["Salaire"], SQLDReader["ROWID"]));
}
// Pour pouvoir lire les résultats...
Console.ReadKey();
|
Cet exemple fonctionne très bien, et produit le résultat suivant :
On voit bien ici qu'on travaille en mode connecté : la méthode Read()
lit bien les données au fur et à mesure qu'on en a besoin, directement dans la base de données.
Voici un petit récapitulatif, pour vous aider à retenir l'essentiel.
Avant de commencer à programmer
- Ajoutez la référence à System.Data.SQLite.
- N'oubliez pas de mettre "True" à "Copie locale" dans les propriétés de la référence.
- Ajoutez une directive using
pour System.Data.SQLite.
Se connecter / déconnecter à la BDD
- Utilisez l'objet SQLiteConnectionStringBuilder
pour faciliter l'entrée des paramètres de connexion, notamment DataSource
, Password
et FailIfMissing
.
- Créez un objet SQLiteConnection
et utilisez ses méthodes Open()
et Close()
pour vous connecter / déconnecter.
SQLiteCommand en mode connecté
- Utilisez la méthode ExecuteNonQuery()
pour les requêtes ne renvoyant pas de données.
- Utilisez la méthode ExecuteScalar()
pour celles renvoyant une valeur unique.
- Utilisez la méthode ExecuteReader()
pour les requêtes renvoyant des données.
Amusez-vous bien avec SQLite dans vos programmes ! Cela vous simplifiera grandement la vie.
Une partie sur le travail en mode déconnecté est en cours de rédaction.
Ce tutoriel touche à sa fin, j'espère que vous saurez faire bon usage de cette API qui est à la fois très simple et très puissante.