Langage Orienté Objet - Notes de cours

Transcription

Langage Orienté Objet - Notes de cours
ENSEIGNEMENT DE PROMOTION SOCIALE
——————————————————————
Cours de
LANGAGE ORIENTE OBJET
- Accès C++ aux bases de données ——————————————————————
H. Schyns
v 1.0
Avril 2013
Accès C++ aux bases de données
Sommaire
Sommaire
1.
INTRODUCTION
2.
PRINCIPES DE BASE ET VOCABULAIRE
2.1. Base de données
2.2. Associer application et base de données
2.3. Les outils
2.3.1.
2.3.2.
2.3.3.
2.3.4.
2.3.5.
3.
LES PREMIERS PAS EN DAO
3.1.
3.2.
3.3.
3.4.
3.5.
3.6.
4.
DSN
Chaîne de connexion
ODBC
DAO
MFC
Créer la base de données
Se connecter à la base de données
Lire les enregistrements de la table T_Villes
Ajouter des enregistrements dans la table T_Villes
Modifier des enregistrements de la table T_Villes
Supprimer des enregistrements de la table T_Villes
PREMIERS PAS EN ODBC
4.1.
4.2.
4.3.
4.4.
Créer la base de données
Se connecter à la base de données
Lire les enregistrements de la table T_Villes
Mapper la table sur un recordset
4.4.1.
4.4.2.
4.4.3.
4.4.4.
4.4.5.
4.4.6.
Position du problème
Créer une classe dérivée de CRecordset
Lire les enregistrements de la table T_Villes
Ajouter des enregistrements dans la table T_Villes
Modifier des enregistrements de la table T_Villes
Supprimer des enregistrements de la table T_Villes
4.5. Utiliser le mode SQL
4.5.1.
4.5.2.
4.5.3.
4.5.4.
Position du problème
Ajouter des enregistrements dans la table T_Villes
Modifier des enregistrements de la table T_Villes
Supprimer des enregistrements de la table T_Villes
5.
POUR ALLER PLUS LOIN…
6.
CONCLUSION
H. Schyns
S.1
Accès C++ aux bases de données
7.
ANNEXE
7.1.
7.2.
7.3.
7.4.
7.5.
7.6.
7.7.
7.8.
8.
Sommaire
CDaoDatabase
CDaoRecordset
CDatabase
CRecordset
Utilisation d'un objet COleVariant
CDBVariant
Fonctions de mappage RFX
Principales instructions SQL
SOURCES
8.1. Ouvrages
8.2. Sites web
H. Schyns
S.2
Accès C++ aux bases de données
1.
1 - Introduction
Introduction
Ce document présente les principes d'interfaçage d'une application C++ avec une
base de données quelconque.
Il répond à une demande des étudiants sur les modifications à apporter à un projet
C++ lorsque les données sont conservées dans une base de données de type
Access, MySQL ou SQLite et non plus dans les streams binaires utilisés dans la
version 5 du projet ABC v5 soumis à titre de projet d'examen en février 2012.
L'objectif de ce document est de présenter :
-
les principes de base, le vocabulaire spécifique et le différents modes d'accès
aux bases de données,
les objets de la bibliothèque MFC (Microsoft Fundation Class Library)
l'utilisation d'objets que nous n'avons pas créés afin de définir et d'accéder à une
base de données
l'implémentation des fonctionnalités de base CRUD (1) selon trois méthodes
différentes,
le développement progressif d'un projet en commençant par des choses simples
que l'on teste avant de passer à des choses plus complexes.
Afin de limiter le nombre de problèmes à résoudre - il y en aura déjà bien assez
comme ça - nous allons développer la solution dans un environnement 100%
Microsoft :
-
système d'exploitation Windows,
plate-forme de développement Visual Studio,
base de données MS Access
bibliothèque MFC
Comme pour les résolutions précédentes, la solution proposée ici ne prétend pas
être la meilleure ni la plus astucieuse. Elle se veut simplement claire d'un point de
vue pédagogique, correcte d’un point de vue conceptuel, et fonctionnelle.
Nous attirons cependant l'attention du lecteur sur le fait que ce document n'est ni un
cours sur la conception des bases de données, ni un cours de SQL, ni un exposé
sur l'art d'effectuer des transactions sécurisées dans un environnement
multiutilisateurs.
Il expose simplement les principes qui sont à la base du dialogue entre une
application rédigée dans un langage de programmation et une base de données
conçue dans un autre environnement.
Le lecteur est vivement encouragé à modifier les exemples de code et à tester les
différents comportements qui en résultent. Il est aussi encouragé à consulter les
nombreuses références présentées en annexe et dans la bibliographie.
1 Create-Read-Update-Delete
H. Schyns
1.1
Accès C++ aux bases de données
2.
2 - Principes de base et vocabulaire
Principes de base et vocabulaire
2.1.
Base de données
Dans les versions précédentes du projet, nous avons conservé les informations dans
un ensemble de fichiers binaires indépendants entre eux.
C'était à notre application qu'il incombait de vérifier la validité des données, leur
cohérence et leur intégrité référentielle.
Habituellement, ces tâches sont confiées à un système de gestion de base de
données (SGBD).
La base de données est la pièce centrale de toute application logicielle qui collecte
des données, les stocke, les traite et les restitue aux utilisateurs. D'un point de vue
conceptuel, la base de données n'est rien d'autre qu'un conteneur tout comme notre
ensemble de fichiers était un conteneur.
L'ensemble des fonctions membres de ce container constitue le système de gestion
de base de données (SGBD). Il permet non seulement d'accéder aux données qui y
sont stockées grâce à un langage standard nommé SQL, mais aussi de manipuler la
structure de la base de données. Le SGBD sert donc d'intermédiaire entre la base
de données et ses utilisateurs.
L'utilisation d'une base de données en lieu et place de fichiers permet :
-
de partager des informations entre plusieurs utilisateurs simultanés
de contrôler automatique de la cohérence et de la redondance des informations
de limiter l'accès aux informations à certaines catégories d'utilisateurs
de produire plus facilement des informations synthétiques à partir des
renseignements bruts.
L'administrateur de bases de données est la personne chargée d'organiser le
contenu de la base de données d'une manière qui soit bénéfique à l'ensemble des
utilisateurs. Il en gère les accès et en assure la sécurité (backups).
2.2.
Associer application et base de données
Avant de plonger dans la réalisation pratique, voyons quelques principes de base
sur la manière de travailler avec une base de données.
Dans une architecture 3-tier ou MVC, l'application et le modèle de données sont
généralement "déconnectés". On entend par là le fait que l'application et la base de
données sont totalement séparées : une application Java utilise les données
fournies par un serveur ORACLE; un module C++ gère des données stockées dans
un environnement MS Access, un module PHP traite les données conservées dans
un environnement MySQL, etc.
Dès lors, l'application, qui ne possède pas les données, doit aller les chercher avant
d'exécuter les traitements qui lui sont demandés par l'utilisateur.
H. Schyns
2.1
Accès C++ aux bases de données
2 - Principes de base et vocabulaire
fig. 2.1 Séquence de travail avec une base de données
Le processus est illustré à la fig. 2.1 :
-
-
-
-
-
-
-
l'application se connecte à la base de données.
Cette étape correspond à l'ouverture du fichier de données (stream) en mode
lecture dans les versions antérieures de notre projet
l'application extrait les données de la base de données. Pour cela, elle lui
envoie une requête de lecture ainsi qu'un container pour récupérer les
informations.
Ceci correspond à la lecture du fichier, à la découpe des enregistrements et à
leur stockage dans le container.
l'application se déconnecte de la base de données afin, par exemple, d'en
laisser l'accès à un autre utilisateur.
Ceci correspond à la fermeture du fichier ou du stream.
l'application est maintenant en mesure de manipuler les données hors
connexion et d'effectuer tous les traitements désirés par l'utilisateur.
Dans les versions précédentes de notre projet, ceci correspond aux traitements
effectués sur les données conservées par le container.
lorsque les traitements sont terminés, l'application se reconnecte à la base de
données
Cette étape correspond à l'ouverture du fichier de données en mode écriture.
l'application écrit les données de la base de données. Pour cela, elle lui envoie
une requête de mise à jour ainsi que le container qui contient les informations
traitées.
Ceci revient à parcourir le container, à restructurer les données sous la forme
requise pour l'écriture dans le fichier.
l'application se déconnecte de la base de données.
Ceci correspond à la fermeture du fichier ou du stream.
Le processus peut être parcouru une ou plusieurs fois selon que l'application traite
les enregistrements en bloc ou un par un.
2.3.
Les outils
2.3.1.
DSN
En toute généralité, les fichiers, les tableurs, les bases de données portent le nom
générique de sources de données (ang.: Data Source).
H. Schyns
2.2
Accès C++ aux bases de données
2 - Principes de base et vocabulaire
Ainsi que nous l'avons vu au paragraphe précédent, pour y accéder, il faut établir
une connexion. Les paramètres de cette connexion sont décrits dans une structure
appelée nom de source de données (ang.: Data Source Name) ou, plus
communément DSN (1).
Le DSN ne doit pas être confondu avec le nom du fichier qui constitue la base de
données. Ainsi, la base de données MS Access définie dans le fichier URBAIN.MDB
peut être associée à un DSN nommé GESTIONCHANTIERS.
Les paramètres (data membres) du DSN comprennent généralement :
-
le nom de la source de données (ang.: filename)
le répertoire (ang.: path) de la source de données
le nom du pilote (ang.: driver) qui permet d'accéder à la source de données
l'identifiant de l'utilisateur qui souhaite accéder aux données (éventuellement)
le mot de passe utilisateur qui souhaite accéder aux données (éventuellement)
Les DSN font l'objet d'une norme, ce qui permet d'assurer assez simplement la
communication entre une application et des sources de données conçues et
hébergées dans des environnements très variés (MS Access, Oracle, MySQL, etc.).
Le plus souvent, pour des raisons de sécurité d'accès, c'est l'administrateur du
système qui crée les DSN pour chacune des sources de données installées sur la
machine ou le serveur.
Il existe trois types de DSN :
-
DSN utilisateur
Définit une source de données spécifique à l'utilisateur et disponible uniquement
pour l'utilisateur qui le crée. Les DSN utilisateur sont stockés dans le Registre
Windows sous la clé :
HKEY_CURRENT_USER\Software\Odbc\Odbc.ini\Odbc
-
DSN système
Définit une source de données qui est stockée localement mais dont l'usage
n'est pas limité à un seul utilisateur particulier. Tout utilisateur qui ouvre une
session un ordinateur peut y accéder. Les DSN système sont stockés dans le
Registre Windows sous la clé :
HKEY_LOCAL_MACHINE\Software\Odbc\Odbc.ini\Odbc
-
DSN fichier
Fichier texte local portant l'extension .DSN, qui contient toutes les informations
nécessaires pour accéder à une source de données partagée entre plusieurs
utilisateurs. Les DSN fichier sont stockés par défaut dans le dossier
C:\Program Files\Fichiers communs\ODBC\Data Sources
2.3.2.
Chaîne de connexion
La chaîne de connexion (ang.: connection string) est une méthode alternative au
DSN. Cette chaîne de caractères définit la source de données ainsi que le moyen
de s'y connecter (driver). Elle est définie dans le code source et passée au pilote de
la source de données afin d'initier la connexion.
Les paramètres qui constituent la chaîne de connexion sont les mêmes que ceux qui
définissent le DSN.
1 Ne pas confondre le DSN (Data Source Name) avec le DNS (Domain Name System) qui est le service web
qui associe un nom de domaine (p.ex. www.google.com) à l'adresse IP du serveur qui l'héberge.
H. Schyns
2.3
Accès C++ aux bases de données
2.3.3.
2 - Principes de base et vocabulaire
ODBC
Pour faciliter la communication entre les applications (clients) fonctionnant sous
Windows et les divers systèmes de gestion de bases de données (SGBD)
disponibles sur le marché, Microsoft a développé en 1992 le concept d'ODBC (ang.:
Open DataBase Connectivity) (1).
Dans son principe, ODBC ressemble au système de drivers qui permet à une
application Windows donnée (p.ex. un traitement de texte) d'utiliser un périphérique
(p.ex. une imprimante) conçu par n'importe quel fabricant (fig. 2.2).
fig. 2.2 Principe de fonctionnement des drivers de périphériques et de l'ODBC
L'ODBC est un intergiciel (ang.: middleware) qui permet à une application d'utiliser
les mêmes instructions pour interagir avec une source de données, quelles que
soient sa structure, son support ou le langage qu'elle supporte.
Les instructions que l'application peut employer sont définies dans l'API ODBC.
Comme toutes les API (2), il s'agit d'une bibliothèque fournie avec le système
d'exploitation qui prend habituellement la forme d'une bibliothèque à liaison
dynamique (ang.: Dynamically Linked Library).
De même que chaque fabricant d'imprimante fournit un pilote (ang.: driver) pour
permettre l'utilisation de son matériel par un système d'exploitation donné, chaque
concepteur de SGBD fournit un pilote qui permet de manipuler ses bases de
données à partir des instructions standard de l'API. Ainsi, on trouve facilement sur le
web des pilotes ODBC pour MySQL, SQLite, ORACLE, postgreSQL, etc.
L'ODBC apparaît dès lors comme la couche de gestion de l'ensemble des pilotes :
lorsqu'une demande de manipulation de données est faite par l'application, l'API
ODBC la transmet au pilote SGBD correspondant.
Avant toute transaction, l'application doit évidemment spécifier quel SGBD et quelle
source de données elle désire utiliser.
Comme dans le cas des DSN, c'est l'administrateur du système qui crée la liste des
sources de données et qui les lie au driver correspondant. Ceci se fait au moyen du
module OUTILS D'ADMINISTRATION du PANNEAU DE CONFIGURATION de Windows (fig.
2.3).
1 ODBC peut a peu près se traduire par "Connectivité ouverte aux bases de données"
2 Application Programming Interface ou Interface de Programmation d'Application.
H. Schyns
2.4
Accès C++ aux bases de données
2 - Principes de base et vocabulaire
fig. 2.3 Interface de définition des source de données ODBC sous Windows XP
Le nom des pilotes installés ainsi que les sources de données disponibles se
trouvent dans la base de registre Windows sous les clés
HKEY_LOCAL_MACHINE\Software\Odbc\Odbcinst.ini
HKEY_LOCAL_MACHINE\Software\Odbc\Odbc.ini
Le principe de l'OBDC a été repris par la plupart des OS et des plateformes de
développement. On trouve ainsi
-
-
JDBC (ang.: Java DataBase Connectivity)
Logiciel édité par Sun Microsystems, qui permet à des applications écrites pour
la machine virtuelle Java de manipuler des bases de données.
unixODBC et iODBC
Logiciels conformes à la norme ISO/IEC 9075, pour les systèmes d'exploitation
Unix et Linux.
2.3.4.
DAO
DAO (ang.: Data Access Objects) comme ODBC est une API qui permettent d'écrire
des applications indépendantes de la source de données.
Cependant, alors que l'API ODBC est très générique, l'API DAO est optimisée pour
fonctionner avec le moteur de bases de données Microsoft Jet qui est à la base
gestionnaire de base de données MS Access (1).
Utiliser DAO est particulièrement aisé pour les développeurs qui ont une expérience
dans la création de bases de données Access interfacées en VBA (ang.: Visual
Basic Application)(2). On y retrouve en effet les mêmes objets qui permettent
d'accéder aux données et de les manipuler : database, tabledef, querydef,
recordset, etc. (fig. 2.4)
Toutefois, l'usage de DAO tend à devenir obsolète depuis l'apparition de
l'environnement .NET et la généralisation de ODBC.
1 Il est cependant possible d'accéder aussi aux sources de données ODBC et à d'autres sources de
données externes à l'aide de cette API
2 Ce qui est précisément le cas de l'auteur de ce document !
H. Schyns
2.5
Accès C++ aux bases de données
2 - Principes de base et vocabulaire
fig. 2.4 Hiérarchie (simplifiée) des objets DAO
Le modèle objet DAO possède une structure hiérarchisée dont la racine est un objet
DBEngine. Chaque objet parent possède une collection de chacun des types
d'objet enfant. Par exemple, l'objet DBEngine possède une collection d'objet
Workspace. Cette collection est nommée Workspaces (nom de l'enfant mis au
pluriel).
2.3.5.
MFC
La bibliothèque MFC (ang.: Microsoft Fundation Classes) est un ensemble de plus
de 200 classes C++ qui permettent de gérer les différents aspects de la
programmation sous Windows tels que fenêtres, documents, fichiers, boutons,
combo boxes, etc (fig. 2.5).
Parmi toutes les ressources disponibles - parmi lesquelles il est parfois difficile de s'y
retrouver - on trouve un ensemble de classes qui donnent accès et permettent de
manipuler n'importe quel type de base de données pour lesquelles un pilote ODBC
est disponible.
En fonction des objets qu'il choisit, le développeur peut travailler de manière quasi
transparente dans le mode DAO ou ODBC.
Ainsi, pour travailler en mode DAO, il choisira les objets CDaoDatabase,
CDaoRecordset, CDaoQuerydef, etc définis dans AFXDAO.H ; tandis qu'en
mode ODBC il choisira les objets CDatabase, CRecordset, etc définis dans
AFXDB.H
L'inconvénient majeur de MFC est qu'il s'agit d'une bibliothèque 100% Microsoft
Windows et qu'elle n'est utilisable que dans les environnement de développement de
type Visual Studio. Heureusement, on trouve sur le web des bibliothèques
similaires pour la plupart des environnements de développement.
H. Schyns
2.6
fig. 2.5 Ensemble des classes de la bibliothèque MFC
Accès C++ aux bases de données
3.
3 - Les premiers pas en DAO
Les premiers pas en DAO
3.1.
Créer la base de données
Simplifions le problème au maximum.
Avant de nous lancer dans une application client-serveur multi-utilisateurs,
commençons par créer une simple base de données MS Access que nous appelons
URBAIN.MDB et que nous sauvons sur le même poste de travail que l'environnement
de développement (fig. 3.1).
fig. 3.1 Création d'une nouvelle base de données MS Access
Ainsi, nous éliminons déjà une série de problèmes de connexion et de compatibilité
avec Visual Studio.
Nous développons la base de données en créant une table T_Villes qui
contiendra les informations relatives aux villes de notre projet (fig. 3.2).
fig. 3.2 Création d'une table T_Villes
Commençons par des choses simples : la table ne contient que deux champs (fig.
3.3)
-
H. Schyns
idVille
Nom
: identifiant de la ville, de type numérique entier long,
: nom de la ville, de type texte, pouvant contenir 50 caractères.
3.1
Accès C++ aux bases de données
3 - Les premiers pas en DAO
fig. 3.3 Définition des champs de la table T_Villes
Notons que idVille, bien que défini comme la clé primaire, n'est pas de type
"numérotation automatique" car les identifiants sont gérés par la classe Ville du
projet C++ et non par la base de données.
De même, la longueur du champ Nom est identique à celle du data membre Nom
défini dans la classe Ville.
Ceci fait, nous ouvrons la table en mode "Encodage" et nous y introduisons
quelques données (1) (fig. 3.4) :
fig. 3.4 Insertion de quelques données dans la table T_Villes
Il ne nous reste plus qu'à tout sauver et à quitter MS Access.
3.2.
Se connecter à la base de données
Dans un premier temps, nous utiliserons le modèle DAO pour lequel il n'est pas
nécessaire de définir une référence ODBC.
Comme de coutume, pour nos premiers pas, nous créons un nouveau projet dans
Visual Studio. Comme d'habitude, il s'agit d'une application console commençant
avec un projet vide sans en-têtes précompilés, bien que notre application utilise la
bibliothèque MFC (fig. 3.5).
1 Nous évitons soigneusement les caractères accentués afin de réduire les problèmes potentiels.
H. Schyns
3.2
Accès C++ aux bases de données
3 - Les premiers pas en DAO
fig. 3.5 Création d'une application console Win32
Par contre, nous devrons modifier les propriétés de notre projet pour indiquer que
nous utilisons une .DLL contenant les objets MFC (1)(fig. 3.6).
fig. 3.6 Utilisation de la bibliothèque MFC sous la forme de .DLL
Ceci fait, nous pouvons reprendre la procédure habituelle en créant un fichier source
que nous nommons TESTDATABASE.CPP.
#define WINVER 0x0501
#include <afxdao.h>
#include <iostream>
// TestDataBase.cpp
// version pour Windows XP
// bibliothèque des objets DAO
using namespace std;
1 Si ça ne marche pas, nous prendrons l'option "Utiliser les MFC dans une bibliothèque statique"
H. Schyns
3.3
Accès C++ aux bases de données
3 - Les premiers pas en DAO
void main(void)
{
CDaoDatabase dbUrbain;
CString sPathDb("E:\\Lang_Cpp\\urbain.mdb");
// objet DataBase
// string Unicode
//--- trouver (connecter) la base de donnees --dbUrbain.Open(sPathDb);
// ouvrir la base de données
if (dbUrbain.IsOpen())
// est-elle ouverte ?
cout << "-- DB trouvee et connectee! --" << endl;
else
{
cout << "-- DB pas trouvee ou echec de connexion! --" << endl;
return;
}
// ---- fermer (déconnecter) la base de données ---dbUrbain.Close();
if (dbUrbain.IsOpen())
cout << "-- DB echec de fermeture! --" << endl;
else
cout << "-- DB deconnectee et fermee! --" << endl;
AfxDaoTerm();
}
// Termine la .dll DAO (facultatif)
winver est une constante qu'il est nécessaire de définir. Elle précise avec quelle
version de Windows la bibliothèque MFC doit travailler. Les valeurs hexadécimales
admises sont :
0x0501
0x0502
0x0600
0x0601
=
=
=
=
Windows
Windows
Windows
Windows
XP
Server 2003
Vista
7.
afxdao.h est le header de la bibliothèque MFC qui contient toutes les définitions
des classes propres à la méthode DAO. Pour la méthode ODBC, nous utiliserons le
header afxdb.h.
La classe CDaoDataBase est aux bases de données ce que la classe fstream
est aux fichiers : elle contient toutes les caractéristiques et toutes les fonctions qui
permettent à l'utilisateur d'accéder à une base de donnée en mode DAO sans qu'il
ait à en connaître le fonctionnement ou l'organisation interne.
Les fonctions membres de la classe CDaoDataBase qui nous seront les plus utiles
sont reprises ci-dessous. Une liste plus complète et plus explicite est fournie en
annexe. Pour plus d'informations, le lecteur est invité à se rendre sur le site
msdn.microsoft.com.
Fonctions les plus utiles de CDaoDataBase
Close
Execute
Create
IsOpen
CreateRelation
Open
Parmi les fonctions membres, Create() permet de créer une nouvelle base de
données. Execute() permet d'envoyer des commandes SQL qui créent les
tables et les champs tandis que CreateRelation() permet de créer les
relations entre les tables.
H. Schyns
3.4
Accès C++ aux bases de données
3 - Les premiers pas en DAO
La classe CString définit un type de chaîne de caractères propre à la bibliothèque
MFC (1).
Bien que ce type possède un constructeur qui accepte les chaînes C classiques, il
ne conserve pas le zéro terminal mais bien la taille de la chaîne de caractères. Il
accepte et traite correctement aussi bien les textes ASCII (1 byte par caractère) que
les textes Unicode (2 bytes par caractère).
Utiliser une base de données n'est pas beaucoup plus compliqué qu'utiliser un
fichier :
dbUrbain.Open(sPathDb);
permet d'ouvrir la base de données en passant en paramètre le chemin et le nom du
fichier, chose aisée ici puisqu'il s'agit d'une base de données MS Access contenue
dans un fichier .MDB.
Malheureusement, Open() est une fonction (void) qui ne renvoie aucune valeur
en retour. Pour savoir si tout s'est bien passé, nous devons utiliser
dbUrbain.IsOpen()
afin de vérifier l'ouverture effective de la base de données.
Comme dans le cas d'un fichier, nous refermons (déconnectons) la base de données
par
dbUrbain.Close();
lorsque nous avons fini de l'utiliser.
Eventuellement, nous pouvons vérifier que la fermeture s'est bien passée grâce à un
nouvel appel à IsOpen().
La dernière ligne est facultative.
AfxDaoTerm();
Elle force la fermeture de l'environnement DAO (dll) dans le cas où celle-ci ne se fait
pas automatiquement ou proprement (auquel cas un impressionnant et
incompréhensible panneau d'erreur apparaît).
Si nous avons défini correctement l'accès à notre base de données, voici ce que
nous devons avoir à l'écran (2) :
-- DB trouvee et connectee! --- DB deconnectee et fermee! -Appuyez sur une touche pour continuer...
// Affichage à l'écran
Génial ! Le plus dur est fait. Nous pouvons à présent essayer de lire les données
dans la table T_Villes.
1 Pour des exemples d'utilisation de CString, voir la référence mentionnée dans les sources.
2 Si l'exécution fait apparaîte "mfc80ud.dll introuvable", il est recommandé de bien nettoyer la solution et de
la régénérer. Si le problème persiste, changer les propriétés du projet et définir "Utiliser les MFC dans une
DLL partagée" ou, au contraire, "Utiliser les MFC dans une bibliothèque statique ".
H. Schyns
3.5
Accès C++ aux bases de données
3.3.
3 - Les premiers pas en DAO
Lire les enregistrements de la table T_Villes
Pour accéder aux données proprement dites, nous devons utiliser un objet
CDaoRecordset (1) Le Recordset est l'objet central de toute gestion de base
de données. On peut le voir comme un container qui héberge une collection
d'enregistrements (ang.: records) mais il faut plutôt le voir comme une sorte de
fenêtre qui défile devant les enregistrements contenus dans la table ou sélectionnés
par la requête SQL.
La classe CDaoRecordset dispose d'un nombre impressionnant de fonctions
membres dont une liste assez complète est fournie en annexe. Comme plusieurs
d'entre-elles existent en plusieurs versions surchargées, il est indispensable de
consulter la documentation du site msdn.microsoft.com (et disposer de
beaucoup de patience, de calme, et de transpiration) pour savoir comment les
utiliser.
void main(void)
{
CDaoDatabase dbUrbain;
CString sPathDb("E:\\Lang_Cpp\\urbain.mdb");
// string Unicode
dbUrbain.Open(sPathDb);
// ouvrir la base de données
x
// etc
// ---- définir les objets nécessaires ---CDaoRecordset rsVilles(&dbUrbain);
COleVariant vIdVille, vNom;
CString sTable("T_Villes");
// ---- associer le recordset à la table T_Villes ---rsVilles.Open(dbOpenTable, sTable, dbReadOnly);
if (!rsVilles.IsOpen())
// ouverture OK ?
{
cout << "-- recordset non connecte! --" << endl;
return;
}
// ---- lister tous les records de la table ---rsVilles.MoveFirst();
// aller au premier record de la table
while(!rsVilles.IsEOF())
// boucler sur les records
{
// récuperer les valeurs des champs
rsVilles.GetFieldValue(0,vIdVille);
rsVilles.GetFieldValue(1,vNom);
// afficher les valeurs des champs
wcout
<< vIdVille.lVal
<< " - "
<< (wchar_t*)vNom.pbstrVal << endl;
// passer au record suivant
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
Lorsque nous déclarons un CDaoRecordset, nous devons lui fournir l'adresse de
la base de données avec laquelle il va travailler.
1 En mode ODBC, on utilisera un objet CRecordset
H. Schyns
3.6
Accès C++ aux bases de données
3 - Les premiers pas en DAO
CDaoRecordset rsVilles(&dbUrbain);
Nous pouvons ensuite lui demander d'ouvrir une vue sur la table choisie :
rsVilles.Open(dbOpenTable, sTable, dbReadOnly);
Il existe plusieurs méthodes Open() pour ouvrir un recordset. Celle qui est utilisée
ci-dessus propose plusieurs mécanismes que nous sélectionnons au moyen d'une
constante symbolique (dbXxx). Le mécanisme choisi définit ce qu'il sera possible
de faire par la suite :
-
dbOpenDynaset
Jeu de données dynamique, qui peut être mis à jour et dans lequel on peut
naviguer dans les deux sens. C'est l'option par défaut, notamment lorsqu'on
utilise une requête SQL pour sélectionner les enregistrements (requête simple
ou avec jointures).
-
dbOpenTable
Jeu de données qui correspond à une table donnée. Il peut être mis à jour et
permet la navigation dans les deux sens.
-
dbOpenSnapshot
Jeu de données statiques qui est une image figée de la table ou de la requête au
moment où le recordset a été ouvert. Il ne peut être mis à jour mais permet la
navigation dans les deux sens. Ce mode d'ouverture convient bien pour
alimenter des listes déroulantes.
Nous fournissons en deuxième paramètre soit le nom d'une table, soit une
instruction SQL "SELECT…FROM…".
Le troisième paramètre est une autre constante symbolique qui précise le
fonctionnement de la méthode choisie : lecture seule (dbReadOnly), uniquement
ajout en fin de table (dbAppendOnly), etc.
Comme dans le cas de la base de données, il est prudent de vérifier si nous avons
bien réussi à ouvrir le recordset :
if (!rsVilles.IsOpen())
Si tout à bien marché, nous positionnons la fenêtre du recordset sur le premier
enregistrement de la table T_Villes et nous entrons dans une boucle de lecture
classique :
rsVilles.MoveFirst();
while(!rsVilles.IsEOF())
{
x
rsVilles.MoveNext();
}
// aller au premier record de la table
// boucler sur les records
// etc
Il s'agit maintenant de récupérer les données des différents champs (data membres)
de l'enregistrement sur lequel le recordset est positionné.
C'est ici que les choses se compliquent.
L'objet CDaoRecordset dispose d'une fonction GetFieldValue() qui permet
de récupérer la valeur conservée dans chacun des champs du record. Cette
fonction existe en deux variantes : on lui transmet soit l'index, soit le nom du champ
à extraire. Chacune de ces deux variantes existe elle-même en deux sous-variantes
H. Schyns
3.7
Accès C++ aux bases de données
3 - Les premiers pas en DAO
selon que l'on récupère la valeur du champ en "valeur en retour" ou par passage
d'une référence.
Le problème est que la variable qui récupère l'information n'est ni un float, ni un
string mais un type COleVariant qui est l'équivalent du type variant défini
en Visual Basic. Autrement dit, nous récupérons l'information des deux champs de
la table dans deux variables non typées.
COleVariant vIdVille, vNom;
x
rsVilles.GetFieldValue(0,vIdVille);
rsVilles.GetFieldValue(1,vNom);
x
// variables non typées
// etc
// premier champ
// deuxieme champ
// etc
La forme alternative, plus souple mais moins efficace est
rsVilles.GetFieldValue(CString("IdVille"),vIdVille);
rsVilles.GetFieldValue(CString("Nom"),vNom);
ou encore, de manière encore moins efficace (1), par valeur en retour :
vIdVille = rsVilles.GetFieldValue(0);
vNom = rsVilles.GetFieldValue(1);
et
vIdVille=rsVilles.GetFieldValue(CString("IdVille"));
vNom=rsVilles.GetFieldValue(CString("Nom"));
L'idée de travailler avec des variables non typées peut être séduisante à première
vue mais, en pratique, nous devrons quand même définir le type au moment
d'afficher les valeurs :
wcout
<< vIdVille.ulVal
<< " - "
<< (wchar_t*)vNom.pbstrVal << endl;
Le premier point à noter est l'utilisation du stream de sortie wcout au lieu du cout
auquel nous sommes habitués.
Le préfixe w (pour wide) indique que nous sommes dans un environnement wide
char (ou caractères larges) dans lequel chaque caractère Unicode occupe deux
bytes au lieu d'un.
Pour récupérer l'identifiant de la ville, qui est de type unsigned long, nous
devons utiliser le data membre ulVal du variant (2). Pour récupérer le nom, qui
est une chaîne de caractères Unicode, nous devons non seulement utiliser le data
membre pbstrVal (pointeur sur un basic string Unicode) mais encore le "caster"
dans un type équivalent reconnu par C++ soit wchar_t* , soit LPCTSTR.
Ceci fait, nous refermons soigneusement le recordset
rsVilles.Close();
Si nous avons défini correctement nos types et nos corrections, voici ce que nous
devons avoir à l'écran :
1 Copier un objet dans la stack pour le renvoyer à la fonction appelante n'est jamais très efficace.
2 Un variant est en fait une union des différents types définis
H. Schyns
3.8
Accès C++ aux bases de données
-- DB trouvee et connectee! -1 - Liege
2 - Bruxelles
3 - Antwerpen
4 - Namur
5 - Charleroi
-- DB deconnectee et fermee! -Appuyez sur une touche pour continuer...
3 - Les premiers pas en DAO
// Affichage à l'écran
Yes !
3.4.
Ajouter des enregistrements dans la table T_Villes
L'objet CDaoRecordset dispose d'une fonction AddNew() permet d'ajouter un
enregistrement "vide" au recordset. Les valeurs sont introduites dans les différents
champs de l'enregistrement grâce à la fonction SetFieldValue(). Ensuite,
l'enregistrement est copié dans la table sous-jacente grâce à la fonction Update().
Il n'est pas permis d'ajouter des enregistrements dans un recordset ouvert en mode
dbOpenSnapshot ni dans un recordset ouvert en mode dbOpenDynaset avec
une expression SQL qui fait intervenir des jointures.
void main(void)
{
CDaoDatabase dbUrbain;
CString sPathDb("E:\\Lang_Cpp\\urbain.mdb");
// string Unicode
dbUrbain.Open(sPathDb);
// ouvrir la base de données
x
// etc, comme précédemment
// ---- définir les objets nécessaires ---CDaoRecordset rsVilles(&dbUrbain);
// comme précédemment
COleVariant vIdVille, vNom;
CString sTable("T_Villes");
// ---- associer le recordset à la table T_Villes ---rsVilles.Open(dbOpenTable, sTable, 0);
// mode Read+Write
if (!(rsVilles.IsOpen() && rsVilles.CanUpdate()))
// OK ?
{
cout << "-- non connecte ou mode lecture seule! --" << endl;
return;
}
// ---- ajouter un nouveau record à T_Villes ---rsVilles.AddNew();
// préparer un nouveau record
vIdVille = 6L;
// mettre les valeurs
vNom = _T("Arlon");
// dans des "variants"
rsVilles.SetFieldValue(0,vIdVille);
// les envoyer dans le record
rsVilles.SetFieldValue(1,vNom);
rsVilles.Update();
// envoyer le record dans la table
// ---- lister tous les records de la table ---rsVilles.MoveFirst();
// aller au premier record de la table
while(!rsVilles.IsEOF())
// boucler sur les records
{
x
// etc
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
H. Schyns
3.9
Accès C++ aux bases de données
3 - Les premiers pas en DAO
Avant d'ajouter des enregistrements il faut attacher le recordset à la table désirée et
l'ouvrir en mode lecture et écriture (mode R/W). Ce mode est défini en omettant le
troisième paramètre ou en le mettant à zéro.
rsVilles.Open(dbOpenTable, sTable, 0);
if (!(rsVilles.IsOpen() && rsVilles.CanUpdate()))
// OK ?
Nous pouvons vérifier à tout moment si le recordset admet les ajouts et mises à jour
grâce à la fonction membre CanUpdate().
Nous sommes maintenant en mesure de créer un nouvel enregistrement dans l'objet
CDaoRecordset local (ce qui signifie qu'il n'est pas encore dans la table source) :
rsVilles.AddNew();
vIdVille = 6L;
vNom = _T("Arlon");
rsVilles.SetFieldValue(0,vIdVille);
rsVilles.SetFieldValue(1,vNom);
rsVilles.Update();
La fonction membre SetFieldValue() nous permet d'inscrire les valeurs
voulues dans le nouvel enregistrement local.
Comme la fonction
GetFieldValue() vue au point précédent, elle utilise un variant, ce qui nous
interdit de passer directement les valeurs et nous impose une petite gymnastique
intermédiaire :
-
si la valeur est numérique, nous pouvons l'affecter directement au variant,
si la valeur est une chaîne de caractères C (char*) ou un String C++, nous
devons d'abord la transformer en chaîne Unicode grâce au constructeur _T().
Ensuite, nous pouvons l'affecter au variant.
La fonction membre Update() copie l'enregistrement local dans la table source qui
fait partie de la base de données.
Nous pouvons ensuite lister tous les records de la table pour vérifier si l'insertion a
bien eu lieu.
-- DB trouvee et connectee! -1 - Liege
2 - Bruxelles
3 - Antwerpen
4 - Namur
5 - Charleroi
6 - Arlon
-- DB deconnectee et fermee! --
// Affichage à l'écran
// nouveau record !
Si nous ouvrons la base de données MS Access et que nous affichons la table en
mode "Feuille de données", nous y retrouvons bien l'enregistrement ajouté (fig. 3.7).
fig. 3.7 L'enregistrement a bien été ajouté dans la table T_Villes
H. Schyns
3.10
Accès C++ aux bases de données
3 - Les premiers pas en DAO
Attention 1 : avant de relancer le programme C++ il faut soit changer les données
dans le code source, soit supprimer le dernier enregistrement de la table MS
Access. N'oublions pas que idVille est une clé primaire qui n'admet pas de
doublons !
Attention 2 : le nouvel enregistrement n'est pas forcément ajouté à la fin de la table.
Tout dépend des règles définies dans le SGBD pour la gestion des index et des clés
primaires. De même, l'enregistrement qui vient d'être ajouté ne devient pas
forcément l'enregistrement courant. Pour que ce soit le cas, il faut utiliser les signets
(ang.: bookmarks) :
rsVilles.SetBookmark(rsVilles.GetLastModifiedBookmark( ) );
3.5.
Modifier des enregistrements de la table T_Villes
Pour modifier les données d'un enregistrement nous utilisons la fonction Edit().
La procédure est assez semblable à celle de l'ajout : les valeurs modifiées sont
introduites dans un variant puis transférées dans l'enregistrement local grâce à la
fonction SetFieldValue(). Cet enregistrement est ensuite copié dans la table
sous-jacente grâce à la fonction Update().
void main(void)
{
CDaoDatabase dbUrbain;
CString sPathDb("E:\\Lang_Cpp\\urbain.mdb");
// string Unicode
dbUrbain.Open(sPathDb);
// ouvrir la base de données
x
// etc, comme précédemment
// ---- définir les objets nécessaires ---CDaoRecordset rsVilles(&dbUrbain);
// comme précédemment
COleVariant vIdVille, vNom;
CString sTable("T_Villes");
// ---- associer le recordset à la table T_Villes ---rsVilles.Open(dbOpenTable, sTable, 0);
// mode Read+Write
x
// etc, comme précédemment
// ---- modifier un à un tous les records à T_Villes ---rsVilles.MoveFirst();
while( !rsVilles.IsEOF() )
{
// lire les valeurs des champs
rsVilles.GetFieldValue(0,vIdVille);
rsVilles.GetFieldValue(1,vNom);
wcout
<< "avant : " << vIdVille.ulVal << " - "
<< (wchar_t*)vNom.pbstrVal << endl;
// modifier les valeurs lues
vIdVille.ulVal += 1000l;
vNom = vNom + CString("xxx");
// afficher les modifications
wcout
<< "apres : " << vIdVille.ulVal << " - "
<< (wchar_t*)vNom.pbstrVal << endl;
// modifier le record
rsVilles.Edit();
rsVilles.SetFieldValue(0,vIdVille);
rsVilles.SetFieldValue(1,vNom);
rsVilles.Update();
// passer au suivant
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
H. Schyns
3.11
Accès C++ aux bases de données
3 - Les premiers pas en DAO
Dans l'exemple ci-dessus, nous ajoutons 1000 à tous les identifiants et nous collons
"xxx" à la suite de tous les noms de ville.
A nouveau, la difficulté réside plus dans la manipulation des variants vIdVille
et vNom que dans la transaction avec la base de données.
Comme operator+= n'est pas défini pour les variants, nous devons l'appliquer
au membre qui représente la valeur numérique (ulVal).
De même, la concaténation impose de passer d'abord par un objet CString
vIdVille.ulVal += 1000l;
vNom = vNom + CString("xxx");
Ceci fait, la mise à jour n'est plus qu'une formalité qui ressemble fortement à l'ajout
avec AddNew() :
rsVilles.Edit();
rsVilles.SetFieldValue(0,vIdVille);
rsVilles.SetFieldValue(1,vNom);
rsVilles.Update();
// préparer le record local
// y mettre les valeurs
// transférer vers la table
Voici ce que doit afficher notre programme de test lors de l'exécution :
-- DB
avant
apres
avant
apres
avant
apres
avant
apres
avant
apres
avant
apres
-- DB
trouvee et connectee! -: 1 - Liege
: 1001 - Liegexxx
: 2 - Bruxelles
: 1002 - Bruxellesxxx
: 3 - Antwerpen
: 1003 - Antwerpenxxx
: 4 - Namur
: 1004 - Namurxxx
: 5 - Charleroi
: 1005 - Charleroixxx
: 6 - Arlon
: 1006 - Arlonxxx
deconnectee et fermee! --
// Affichage à l'écran
Si nous ouvrons la base de données MS Access et que nous affichons la table en
mode "Feuille de données", nous retrouvons bien les modifications apportées aux
enregistrements ajoutés (fig. 3.7).
fig. 3.8 Les enregistrements sont modifiés dans la table T_Villes
3.6.
Supprimer des enregistrements de la table T_Villes
La dernière fonction CRUD à implémenter est la suppression des enregistrements.
Ceci se fait très simplement à l'aide de la fonction Delete() de l'objet
CDaoRecordset.
H. Schyns
3.12
Accès C++ aux bases de données
3 - Les premiers pas en DAO
Cette fonction détruit l'enregistrement courant et le rend inaccessible. Bien qu'il soit
impossible de le modifier ou de l'utiliser, l'enregistrement détruit reste
l'enregistrement courant jusqu'à ce qu'on déplace l'index vers un autre
enregistrement.
Contrairement à ce qui se passe avec les fonctions Edit() et AddNew(), l'effet
de Delete() est immédiat ; il n'est pas nécessaire d'appeler Update() pour
effectuer la destruction.
void main(void)
{
CDaoDatabase dbUrbain;
x
// etc, comme précédemment
// ---- associer le recordset à la table T_Villes ---rsVilles.Open(dbOpenTable, sTable, 0);
// mode Read+Write
x
// etc, comme précédemment
// ---- supprimer les records impairs de T_Villes ---rsVilles.MoveFirst();
while( !rsVilles.IsEOF() )
{
rsVilles.GetFieldValue(0,vIdVille);
rsVilles.GetFieldValue(1,vNom);
if (vIdVille.ulVal % 2)
// si IdVille est impair
{
wcout << "effacer : " ;
rsVilles.Delete();
// effacer le record
}
else
{
wcout << "conserver : " ;
}
wcout
<< vIdVille.ulVal << " - "
<< (wchar_t*)vNom.pbstrVal << endl;
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
Dans l'exemple ci-dessus, nous supprimons toutes les villes dont l'identifiant est
impair. Les autres sont maintenues
if (vIdVille.ulVal % 2)
{wcout << "effacer : " ;
rsVilles.Delete();
}
// si IdVille est impair
// effacer le record
Voici ce que doit afficher notre programme de test lors de l'exécution :
-- DB trouvee et connectee! -effacer : 1001 - Liegexxx
conserver : 1002 - Bruxellesxxx
effacer : 1003 - Antwerpenxxx
conserver : 1004 - Namurxxx
effacer : 1005 - Charleroixxx
conserver : 1006 - Arlonxxx
-- DB deconnectee et fermee! --
// Affichage à l'écran
Les suppressions ont bien été effectuées dans la base de données MS Access (fig.
3.9).
H. Schyns
3.13
Accès C++ aux bases de données
3 - Les premiers pas en DAO
fig. 3.9 Les enregistrements impairs sont supprimés dans la table T_Villes
H. Schyns
3.14
Accès C++ aux bases de données
4.
4 - Premiers pas en ODBC
Premiers pas en ODBC
4.1.
Créer la base de données
Nous reprenons la base de données MS Access définie au chapitre précédent (point
3.1) mais cette fois, nous créons une référence ODBC comme expliqué au point
2.3.3 (fig. 4.1) :
fig. 4.1 Création de la source de données ODBC sous Windows XP
La source pointe sur la base de données E:\\Lang_Cpp\\urbain.mdb et est
nommée GestionChantiers.
4.2.
Se connecter à la base de données
Nous allons voir que l'appel des fonctionnalités de base en mode ODBC diffère fort
peu de celui en mode DAO. La plus grosse différence porte sur le nom des objets :
CDatabase
remplace
CDAODatabase
et
CRecordset
remplace
CDAORecordset. Ces objets sont définis dans afxdb.h qui remplace ainsi
afxdao.h .
#define WINVER 0x0501
#include <afxdb.h>
#include <iostream>
// TestDataBase.cpp
// version pour Windows XP
// bibliothèque des objets ODBC
using namespace std;
void main(void)
{
CDatabase dbUrbain;
CString sDSN ("GestionChantiers");
H. Schyns
// objet database ODBC
// source ODBC
4.1
Accès C++ aux bases de données
4 - Premiers pas en ODBC
//--- trouver (connecter) la base de donnees --dbUrbain.Open(sDSN);
// ouvrir la base de données
if (dbUrbain.IsOpen())
// est-elle ouverte ?
cout << "-- DB trouvee et connectee! --" << endl;
else
{
cout << "-- DB pas trouvee ou echec de connexion! --" << endl;
return;
}
// ---- fermer (déconnecter) la base de données ---dbUrbain.Close();
if (dbUrbain.IsOpen())
cout << "-- DB echec de fermeture! --" << endl;
else
cout << "-- DB deconnectee et fermee! --" << endl;
}
Hormis le type des objets, la seule différence notable avec la mode DAO est
l'utilisation de la source OBDC qui remplace le chemin et nom de la base de
données :
CString sDSN ("GestionChantiers");
Pour le reste, l'usage des fonctions Open() et IsOpen() est identique à ce qui a
été fait au point 3.2.
Notons aussi qu'il n'est plus nécessaire d'appeler AfxDaoTerm() puisque nous ne
sommes plus en mode DAO.
4.3.
Lire les enregistrements de la table T_Villes
Comme en mode DAO, il est impératif d'utiliser un recordset pour récupérer les
informations. Ici, il s'agit d'un objet CRecordset.
A nouveau, le code ressemble furieusement à ce qui a été fait en mode DAO.
void main(void)
{
CDatabase dbUrbain;
CString sDSN ("GestionChantiers");
//--- trouver (connecter) la base de donnees --dbUrbain.Open(sDSN);
x
// ---- définir les objets nécessaires ---CRecordset rsVilles(&dbUrbain);
CString sSQL("SELECT * FROM T_Villes");
CDBVariant vIdVille;
CStringA sNom;
// etc
// ---- ouvrir et rechercher les enregistrements ---rsVilles.m_strSort = _T("Nom");
// tri sur les noms
rsVilles.Open (CRecordset::dynaset, sSQL, CRecordset::readOnly);
if (!rsVilles.IsOpen())
// ouverture OK ?
{
cout << "-- recordset non connecte! --" << endl;
return;
}
H. Schyns
4.2
Accès C++ aux bases de données
4 - Premiers pas en ODBC
// ---- lister tous les records de la table ---rsVilles.MoveFirst();
while (!rsVilles.IsEOF())
{
// récuperer les valeurs des champs
rsVilles.GetFieldValue((short)0, vIdVille);
rsVilles.GetFieldValue((short)1, sNom);
// afficher les valeurs des champs
cout << vIdVille.m_lVal << " - " << sNom << endl;
// passer au record suivant
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
Cette fois, nous ouvrons le recordset à l'aide d'une requête SQL encodée dans un
objet CString. Toutefois, avant d'ouvrir le recordset, nous pouvons définir une
clause "ORDER BY" ou une clause "WHERE" (sans ces mots-clés) dans les data
membres m_strSort et m_strFilter :
CRecordset rsVilles(&dbUrbain);
CString sSQL("SELECT * FROM T_Villes");
x
rsVilles.m_strSort = _T("Nom");
rsVilles.Open (CRecordset::snapshot, sSQL, CRecordset::readOnly);
Le mode snapshot renvoie une image figée de la requête au moment où le
CRecordset a été ouvert. Il ne peut être mis à jour, ce qui explique le mode
readOnly (qui, ici, est quelque peu redondant). Ce mode permet cependant la
navigation dans les deux sens à l'aide des fonctions MoveFirst(),
MoveNext(), etc.
Comme dans le mode DAO, nous récupérons les valeurs des champs à l'aide de la
fonction GetFieldValue() :
CDBVariant vIdVille;
CStringA sNom;
x
rsVilles.GetFieldValue((short)0, vIdVille);
rsVilles.GetFieldValue((short)1, sNom);
Cette fonction admet quatre versions surchargées (1) selon que l'on souhaite
accéder aux champs par leur nom ou par leur indice et récupérer les valeurs sous la
forme d'un variant ou d'une chaîne de caractère. Le cast short dans les
instructions ci-dessus indique au compilateur qu'il s'agit d'entiers et non d'adresses
(ambigüité des déclarations).
L'objet CDBVariant est "presque" identique à l'objet COleVariant… sauf que
les membres qui correspondent aux différents types ne portent pas les mêmes
noms ! Ainsi, une valeur de type long se retrouve dans le membre m_lVal (il n'y a
pas de membre m_ulVal).
1 Nous renvoyons le lecteur aux pages d'aide de Microsoft renseignées dans les sources.
H. Schyns
4.3
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Par contre, le type CStringA, qui est "presque" semblable à CString, permet de
récupérer une chaîne de caractère ASCII et de l'afficher sur cout sans devoir faire
toute une gymnastique de conversion :
cout << vIdVille.m_lVal << " - " << sNom << endl;
Lorsque nous en avons fini avec la boucle d'affichage, nous refermons proprement
le CRecordset et nous déconnectons la base de données CDatabase :
rsVilles.Close();
dbUrbain.Close();
Si nous avons bien travaillé, nous obtenons la liste des villes triées en ordre
alphabétique.
-- DB trouvee et connectee! -3 - Antwerpen
2 - Bruxelles
5 - Charleroi
1 - Liege
4 - Namur
-- DB deconnectee et fermee! --
4.4.
// Affichage à l'écran
Mapper la table sur un recordset
4.4.1.
Position du problème
Dans le paragraphe précédent, nous avons récupéré les valeurs des champs à l'aide
de la fonction GetFieldValue(). Cette technique relativement lourde nous
impose de passer par toute une gymnastique d'objets CDBVariant et CString.
CDBVariant vIdVille;
CStringA sNom;
x
rsVilles.GetFieldValue((short)0, vIdVille);
rsVilles.GetFieldValue((short)1, sNom);
Les choses se compliqueront bientôt, lorsque nous essayerons d'ajouter ou de
modifier les données car il n'existe pas de fonction SetFieldValue().
Heureusement, la bibliothèque MFC offre une méthode plus élégante : construire un
recordset qui a la même structure que la table (ou la requête SQL) que l'on désire
explorer. Il est même possible d'établir automatiquement une correspondance entre
les champs de la requête et les data membres de l'objet.
On parle alors de
mappage de la table (ou de la requête) sur un recordset.
4.4.2.
Créer une classe dérivée de CRecordset
La première étape consiste à créer une classe CRecordsetV (V pour Ville) dérivée
de CRecordset, qui fera tout ce que cette dernière peut faire… et quelques
choses de plus.
Evidemment, il nous faudra créer une classe dérivée pour chacune des tables ou
requêtes que nous devrons interfacer mais nous verrons que ce n'est pas un gros
travail.
#ifndef WINVER
#define WINVER 0x0501
#endif
#include <afxdb.h>
H. Schyns
// CRecordsetV.h
// pourrait avoir été défini ailleurs
// version pour Windows XP
4.4
Accès C++ aux bases de données
4 - Premiers pas en ODBC
class CRecordsetV : public CRecordset
{
public:
long
m_idVille;
CStringA m_Nom;
public:
virtual void
// héritage
// public et non protected!
// data membres correspondant
// aux champs de la table
// constructeur avec param
CRecordsetV (CDatabase* pDatabase = NULL);
// fonction de mappage
DoFieldExchange (CFieldExchange* pFX);
};
Notre classe déclare autant de data membre qu'il y a de champs à interfacer dans la
table ou la requête. Ces data membres doivent être public. L'usage veut qu'on
reprenne exactement le nom des champs en les préfixant par m_.
On notera que, dans notre cas, les chaînes de caractères sont de type CStringA
(A pour ASCII).
Nous devrons aussi redéfinir le constructeur avec paramètre (1) et implémenter la
fonction de mappage virtuelle DoFieldExchange() (2).
// CRecordsetV.cpp
CRecordsetV::CRecordsetV (CDatabase* pDatabase)
:CRecordset(pDatabase)
// constructeur
{
m_idVille = 0;
// valeurs par défaut
m_Nom = _T("");
m_nFields = 2;
// nombre de champs
m_nDefaultType = dynaset;
// mode d'ouverture par défaut
}
Le constructeur assure le chaînage avec le constructeur de la classe de base et
initialise nos data membre.
m_nFields et m_nDefaultType sont des data membres de la classe de base.
Le premier définit combien de champs doivent être mappés, le second définit le
mode d'ouverture par défaut du recordset.
La fonction de mappage peut être créée automatiquement par l'assistant de Visual
Studio lors de la création d'un nouveau projet MFC mais il est plus simple et plus
rapide de l'écrire soi-même.
// CRecordsetV.cpp
void CRecordsetV::DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
// champs à mapper
RFX_Long(pFX, _T("[idVille]"), m_idVille);
RFX_Text(pFX, _T("[Nom]")
, m_Nom);
}
Nous ne détaillerons pas l'en-tête ni la première ligne qui doivent être reproduites
telles quelles.
1 Rappelons que les constructeurs ne sont pas hérités mais qu'ils doivent être redéfinis et chaînés.
2 Le mode DAO implémente aussi une méthode de mappage via la fonction CDAOFieldExchange() et les
fonctions DFX_ffff().
H. Schyns
4.5
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Les deux dernières lignes réalisent le mappage. Chacune d'elle comprend :
-
une fonction de mappage de RFX_tttt qui indique le type de donnée du
champ en question (la liste des fonctions est donnée à l'annexe 0),
le pointeur de chaînage passé en paramètre (pFX),
le nom du champ (entre crochets et guillemets et transformé en type _T),
-
le nom du data membre correspondant.
4.4.3.
Lire les enregistrements de la table T_Villes
L'utilisation du mappage simplifie le programme de test développé au point 4.3 car
nous n'avons plus besoin des variables de transfert vIdVille et sNom.
#ifndef WINVER
#define WINVER 0x0501
#endif
// TestDataBase.cpp
// pourrait avoir été défini ailleurs
// selon la séquence de compilation
#include <afxdb.h>
#include <iostream>
#include "crecordsetv.h"
using namespace std;
// inclure notre recordset dérivé
void main(void)
{
CDatabase dbUrbain;
CString sDSN ("GestionChantiers");
//--- trouver (connecter) la base de donnees --dbUrbain.Open(sDSN);
x
// etc
// ---- définir les objets nécessaires ---CRecordsetV rsVilles(&dbUrbain);
// notre recordset mappé!
CString sSQL("[T_Villes]");
// select toute la table
// ---- connecter le recordset à la table ---rsVilles.m_strSort = _T("Nom");
// tri sur les noms
rsVilles.Open (CRecordset::snapshot, sSQL, CRecordset::readOnly);
if (!rsVilles.IsOpen())
// ouverture OK ?
{
cout << "-- recordset non connecte! --" << endl;
return;
}
// ---- lister tous les records de la table ---rsVilles.MoveFirst();
// transfert automatique
while (!rsVilles.IsEOF())
{
// affichage des data membres
cout << rsVilles.m_idVille << " - " << rsVilles.m_Nom << endl;
rsVilles.MoveNext();
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc comme précedemment
}
Nous utilisons notre classe "mappée" CRecordsetV en lieu et place de
CRecordset.
Le mode ODBC ne reconnaît pas le mode dbOpenTable que nous avons utilisé
en DAO mais nous pouvons le simuler en remplaçant la requête
H. Schyns
4.6
Accès C++ aux bases de données
CString
4 - Premiers pas en ODBC
sSQL("SELECT * FROM T_Villes");
par le nom de la table à explorer
CString sSQL("[T_Villes]");
// select toute la table
Lors de chaque mouvement MoveFirst(), MoveNext() du recordset, les
valeurs sont automatiquement lues dans la table et copiées dans les data membres
correspondants du recordset :
rsVilles.MoveFirst();
// transfert automatique
x
// etc
cout << rsVilles.m_idVille << " - " << rsVilles.m_Nom << endl;
rsVilles.MoveNext();
Comme il s'agit de data membres public, nous pouvons directement les afficher
ou, comme nous le verrons plus loin, les modifier.
4.4.4.
Ajouter des enregistrements dans la table T_Villes
Ainsi qu'il a été dit plus haut, l'objet CRecordset dispose bien d'une fonction
AddNew() comme l'objet CDAORecordset mais pas de la fonction
SetFieldValue(), ce qui nous impose de passer par le mappage.
Nous utilisons exactement les mêmes objets qu'au point précédent.
void main(void)
{
CDatabase dbUrbain;
x
// TestDataBase.cpp
// etc, comme ci-dessus
//--- trouver (connecter) la base de donnees --dbUrbain.Open(sDSN);
x
// etc, comme ci-dessus
// ---- définir les objets nécessaires ---CRecordsetV rsVilles(&dbUrbain);
// notre recordset mappé!
CString sSQL("[T_Villes]");
// select toute la table
// ---- connecter le recordset à la table ---rsVilles.m_strSort = _T("Nom");
// tri sur les noms
// mode Read+Write par défaut
rsVilles.Open(CRecordset::dynaset, sSQL);
// ouverture OK pour ajout ?
if (!(rsVilles.IsOpen() && rsVilles.CanAppend()))
{
cout << "-- recordset non connecte ou read only! --" << endl;
return;
}
// trois villes à ajouter
char* NewVille[3] = {"Verviers", "Arlon", "Mons"};
// boucle d'ajout
for (int i=0; i<3; i++)
{
rsVilles.AddNew();
// préparer un nouveau record
rsVilles.m_idVille = 6+i;
// id dans le recordset
rsVilles.m_Nom = NewVille[i];
// nom dans le recordset
rsVilles.Update();
// envoi du record dans la table
}
H. Schyns
4.7
Accès C++ aux bases de données
4 - Premiers pas en ODBC
// ---- lister tous les records de la table ---rsVilles.Requery();
// refraichir le recordset
rsVilles.MoveFirst();
while (!rsVilles.IsEOF())
{
x
// etc, comme ci-dessus
}
// ---- fermer tout proprement ---rsVilles.Close();
// fermer le recordset
dbUrbain.Close();
// fermer la base de donnees
x
// etc, comme ci-dessus
}
Pour ajouter des enregistrements, il est vivement recommandé d'ouvrir le recordset
en mode dynaset. Nous utilisons la valeur par défaut du troisième paramètre, ce
qui autorise les accès en lecture et écriture :
rsVilles.Open(CRecordset::dynaset, sSQL);
// pas de 3ème param
Les nouveaux enregistrements sont toujours ajoutés en fin de table, même si
l'ouverture s'est faite avec un critère de tri, comme ici. Nous vérifions si l'ajout est
autorisé grâce à la fonction CanAppend() :
if (!(rsVilles.IsOpen() && rsVilles.CanAppend()))
La séquence d'ajout commence obligatoirement par un appel à la fonction
AddNew() qui prépare un nouveau record ; et se termine obligatoirement par un
appel à Update(), qui envoie le record dans la table.
Entre les deux, nous copions les informations dans les data membres de notre
recordset.
rsVilles.AddNew();
rsVilles.m_idVille = 6+i;
rsVilles.m_Nom = NewVille[i];
rsVilles.Update();
// préparer un nouveau record
// id dans le recordset
// nom dans le recordset
// envoi du record dans la table
Bien que nous ayons mis un critère de tri alphabétique dans le recordset, les
nouveaux enregistrements sont toujours insérés en fin de table. Nous devons donc
rafraîchir le recordset avant de lister les villes :
rsVilles.Requery();
// refraichir le recordset
Si nous avons bien travaillé, nous obtenons la liste des villes triées en ordre
alphabétique.
-- DB trouvee et connectee! -3 - Antwerpen
7 - Arlon
2 - Bruxelles
5 - Charleroi
1 - Liege
8 - Mons
4 - Namur
6 - Verviers
-- DB deconnectee et fermee! --
4.4.5.
// Affichage à l'écran
Modifier des enregistrements de la table T_Villes
Modifier les enregistrements est un jeu d'enfant avec la fonction Edit() dont notre
objet CRecordsetV a hérité.
H. Schyns
4.8
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Dans l'exemple ci-dessous, nous ajoutons 1000 à tous les identifiants et nous
collons "xxx" à la suite de tous les noms de ville.
void main(void)
{
CDatabase dbUrbain;
x
// TestDataBase.cpp
// etc, comme ci-dessus
// ---- associer le recordset à la table T_Villes ---rsVilles.m_strSort = _T("Nom");
rsVilles.Open(CRecordset::dynaset, sSQL);
// mode Read+Edit
if (!(rsVilles.IsOpen() && rsVilles.CanUpdate())) // ouverture OK ?
{
cout << "-- recordset non connecte ou read only! --" << endl;
return;
}
// ---- modifier les records de la table T_Villes ---rsVilles.MoveFirst();
// boucle de modification
while (!rsVilles.IsEOF())
{
rsVilles.Edit();
// préparer le record courant
rsVilles.m_idVille += 1000;
// modifier l'id
rsVilles.m_Nom = rsVilles.m_Nom + _T("xxx"); // modifier le nom
rsVilles.Update();
// envoyer dans la table
rsVilles.MoveNext();
// suivant
}
// ---- lister tous les records de la table ---rsVilles.Requery();
// refraichir le recordset
x
// etc, comme ci-dessus
}
CanUpdate() nous permet de savoir si les modifications sont autorisées.
La fonction Edit() s'utilise comme AddNew(). Elle signale au recordset que
l'enregistrement va être modifié. Il nous suffit ensuite d'appliquer les modifications
voulues aux data membres du recordset puis de renvoyer le tout dans la table grâce
à Update().
Comme dans le cas de l'ajout, nous rafraîchissons le recordset par Requery()
avant de lister les enregistrements modifiés quoique, dans le cas présent, ce ne soit
pas strictement nécessaire puisque l'opération n'a pas modifié l'ordre alphabétique :
-- DB trouvee et connectee! -1003 - Antwerpenxxx
1007 - Arlonxxx
1002 - Bruxellesxxx
1005 - Charleroixxx
1001 - Liegexxx
1008 - Monsxxx
1004 - Namurxxx
1006 - Verviersxxx
-- DB deconnectee et fermee! --
4.4.6.
// Affichage à l'écran
Supprimer des enregistrements de la table T_Villes
La fonction Delete() permet supprimer l'enregistrement courant, celui sur lequel
l'objet CRecordsetV. est positionné.
Dans l'exemple ci-dessus, nous supprimons toutes les villes dont l'identifiant est
impair. Les autres sont maintenus :
H. Schyns
4.9
Accès C++ aux bases de données
void main(void)
{
CDatabase dbUrbain;
x
4 - Premiers pas en ODBC
// TestDataBase.cpp
// etc, comme ci-dessus
rsVilles.MoveFirst();
// boucle de modification
while (!rsVilles.IsEOF())
{
if (rsVilles.m_idVille % 2)
{
cout << "Effacer " << rsVilles.m_Nom << endl;
rsVilles.Delete();
// effacer le record
}
// pas d'appel à Update()
else
cout << "Conserver " << rsVilles.m_Nom << endl;
rsVilles.MoveNext();
}
cout << endl;
// ---- lister tous les records de la table ---rsVilles.Requery();
// refraichir le recordset
x
// etc, comme ci-dessus
}
Ici aussi, c'est CanUpdate() qui nous permet de savoir si les suppressions sont
autorisées. Pourtant, contrairement à ce qui se passe avec AddNew() et Edit(),
il ne faut pas appeler Update() après avoir utilisé Delete().
-- DB trouvee et connectee! -Effacer Antwerpenxxx
Effacer Arlonxxx
Conserver Bruxellesxxx
Effacer Charleroixxx
Effacer Liegexxx
Conserver Monsxxx
Conserver Namurxxx
Conserver Verviersxxx
// Affichage à l'écran
1002 - Bruxellesxxx
1008 - Monsxxx
1004 - Namurxxx
1006 - Verviersxxx
-- DB deconnectee et fermee! --
4.5.
Utiliser le mode SQL
4.5.1.
Position du problème
Nous avons vu que mapper la table sur un recordset dérivé simplifie
considérablement toutes les opérations.
Cette technique qui fonctionne très bien pour de petites bases de données locales
est cependant très déconseillée pour les grandes bases de données auxquelles on
accède via un serveur.
En effet, dans certains cas, l'application cliente peut être amenée à copier
localement l'entièreté de la base de données avant d'effectuer l'opération demandée,
ce qui peut prendre plusieurs minutes !
La solution recommandée consiste à effectuer toutes les opérations d'ajout, de
modification ou de suppression à l'aide de requêtes SQL, en appelant la fonction
ExecuteSQL() de l'objet CDatabase.
H. Schyns
4.10
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Autre avantage : les requêtes SQL ont une meilleure portabilité que les fonctions des
objets Recordset.
Une liste des principales instructions SQL est donnée à l'annexe 7.8
4.5.2.
Ajouter des enregistrements dans la table T_Villes
Pour ajouter un enregistrement dans une table on utilise l'instruction SQL
INSERT INTO nom_table ( champ1, champ2 ) VALUES ( valeur1, valeur2);
Dans notre cas, si nous voulons ajouter les enregistrements
idVille
6
7
8
Nom
Verviers
Arlon
Mons
Nous créerons pour chacun des enregistrements une requête qui aura la forme cidessous :
INSERT INTO T_Villes ( idVille, Nom ) VALUES ( 6, 'Verviers');
Nous pouvons évidemment générer cette expression en concaténant les différents
morceaux mais il est plus facile d'utiliser la fonction Format() de la classe
CString qui fonctionne presque comme sprintf() afin d'en faire une
instruction paramétrée.
void main(void)
{
CDatabase dbUrbain;
x
// TestDataBase.cpp
// etc, comme ci-dessus
// ---- définir la requête d'ajout ---CString strSQL =
"INSERT INTO T_Villes (idVille, Nom) VALUES (%d, '%s');";
CString strSQLval;
CString NewVille[3] = {"Verviers", "Arlon", "Mons"};
for (int i=0; i<3; i++)
{
// remplacer les paramètres
strSQLval.Format(strSQL, 6+i, NewVille[i]);
wcout << strSQLval.GetBuffer() << endl;
dbUrbain.ExecuteSQL(strSQLval);
// exécuter
}
cout << endl;
// ---- lister tous les records de la table ---CRecordsetV rsVilles(&dbUrbain);
// besoin d'un recordset
CString sTable("[T_Villes]");
// pas de tri alpha
// ouverture en lecture seule
rsVilles.Open(CRecordset::snapshot, sTable, CRecordset::readOnly);
rsVilles.MoveFirst();
x
// etc, comme ci-dessus
// ---- fermer tout proprement ---x
// etc, comme ci-dessus
}
H. Schyns
4.11
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Nous utilisons deux chaînes SQL :
-
strSQL contient la syntaxe et utilise les formats %d et %s à la place des
-
valeurs (notez les apostrophes (') qui entourent le format %s et le point-virgule (;)
final),
strSQLval est la copie qui contiendra les valeurs substituées au format.
La substitution est effectuée par la fonction Format() de l'objet de destination :
strSQLval.Format(strSQL, 6+i, NewVille[i]);
La chaîne mise en forme est alors affichée pour vérification et envoyée à l'objet
CDatabase pour exécution :
dbUrbain.ExecuteSQL(strSQLval);
Dans une application distribuée client/serveur, l'interprétation et l'exécution de
l'expression SQL sont laissées au SGBD, ce qui est généralement très rapide. En
cas d'erreur, la fonction renvoie une exception qui peut être capturée.
Jusqu'à ce stade, aucun recordset n'a été nécessaire car il s'agit d'une requête
d'action qui ne renvoie pas d'enregistrements.
Par contre, nous aurons besoin de notre recordset mappé pour lister le contenu de
la table :
-- DB trouvee et connectee! -INSERT INTO T_Villes (idVille, Nom) VALUES (6,
INSERT INTO T_Villes (idVille, Nom) VALUES (7,
INSERT INTO T_Villes (idVille, Nom) VALUES (8,
// Affichage à l'écran
'Verviers');
'Arlon');
'Mons');
1 - Liege
2 - Bruxelles
3 - Antwerpen
4 - Namur
5 - Charleroi
6 - Verviers
7 - Arlon
8 - Mons
-- DB deconnectee et fermee! --
Dans les expressions SQL, notez les apostrophes (') qui entourent les données qui
sont des chaînes de caractères.
4.5.3.
Modifier des enregistrements de la table T_Villes
Pour ajouter un enregistrement dans une table on utilise une instruction SQL du
type :
UPDATE nom_table SET champ1 = newdata1, champ2 = newdata2;
L'instruction peut éventuellement comprendre une clause WHERE.
Reprenons l'exemple du point 4.4.5 dans lequel nous avons ajouté 1000 à tous les
identifiants et coller "xxx" à la suite de tous les noms de ville.
L'instruction SQL s'écrit
UPDATE T_Villes SET idVille = idVille + 1000, Nom = Nom & 'xxx';
H. Schyns
4.12
Accès C++ aux bases de données
4 - Premiers pas en ODBC
Rappelons que cette instruction sera exécutée par l'interpréteur SQL du serveur
SGBD. Dès lors, les noms de variables sont ceux qui apparaissent dans la table et
les opérateurs sont ceux qui sont définis par SQL.
Pour garder un peu de souplesse et conserver la philosophie développée au point
précédent, nous paramétrons les modifications grâce aux formats %d et %s.
void main(void)
{
CDatabase dbUrbain;
x
// TestDataBase.cpp
// etc, comme ci-dessus
// ---- définir la requête de mise à jour ---CString strSQL =
"UPDATE T_Villes SET idVille=idVille+%d, Nom=Nom & '%s';";
CString strSQLval;
// remplacer les paramètres
strSQLval.Format(strSQL, 1000, _T("xxx"));
wcout << strSQLval.GetBuffer() << endl;
// afficher SQL
dbUrbain.ExecuteSQL(strSQLval);
// exécuter
cout << endl;
x
// ---- lister tous les records de la table ---// etc, comme ci-dessus
// ---- fermer tout proprement ----
x
// etc, comme ci-dessus
}
Le résultat est conforme aux attentes :
-- DB trouvee et connectee! -// Affichage à l'écran
UPDATE T_Villes SET idVille=idVille+1000, Nom=Nom & 'xxx';
1001 - Liegexxx
1002 - Bruxellesxxx
1003 - Antwerpenxxx
1004 - Namurxxx
1005 - Charleroixxx
1006 - Verviersxxx
1007 - Arlonxxx
1008 - Monsxxx
-- DB deconnectee et fermee! --
4.5.4.
Supprimer des enregistrements de la table T_Villes
Il nous reste à montrer comment utiliser l'instruction SQL Delete pour supprimer
certains enregistrements :
DELETE * FROM nom_table WHERE critere1;
Si, comme au point 4.4.6, nous voulons supprimer toutes les villes dont l'identifiant
est impair, l'expression SQL devient
DELETE * FROM T_Villes WHERE (idVille Mod 2 = 1)
où Mod le nom sous lequel l'opérateur modulo est connu en SQL.
Bien que l'expression puisse être utilisée telle quelle dans le code, nous utilisons, à
titre d'illustration, les formats %d, %d pour en faire une expression paramétrée.
H. Schyns
4.13
Accès C++ aux bases de données
void main(void)
{
CDatabase dbUrbain;
x
4 - Premiers pas en ODBC
// TestDataBase.cpp
// etc, comme ci-dessus
// ---- définir la requête de suppression ---CString strSQL
= "DELETE * FROM T_Villes WHERE (idVille mod %d = %d)";
CString strSQLval;
strSQLval.Format(strSQL, 2, 1);
// remplacer les paramètres
wcout << strSQLval.GetBuffer() << endl;
// afficher SQL
dbUrbain.ExecuteSQL(strSQLval);
// exécuter
cout << endl;
// ---- lister tous les records de la table ---x
// etc, comme ci-dessus
// ---- fermer tout proprement ---x
// etc, comme ci-dessus
}
Comme précédemment, l'instruction ExecuteSQL() confie l'interprétation et
l'exécution de l'expression SQL au SGBD ce qui est très rapide.
Le résultat est bien celui que nous attendons :
-- DB trouvee et connectee! -// Affichage à l'écran
DELETE * FROM T_Villes WHERE (idVille mod 2 = 1)
1002 - Bruxellesxxx
1004 - Namurxxx
1006 - Verviersxxx
1008 - Monsxxx
-- DB deconnectee et fermee! --
H. Schyns
4.14
Accès C++ aux bases de données
5.
5 - Pour aller plus loin…
Pour aller plus loin…
Bien que les exemples portent sur une base de données MS Access, il sera aisé de
transposer la solution à tout SGBD qui supporte le mode ODBC et le langage SQL
Le lecteur n'aura aucun mal adapter les exemples aux cas qu'il rencontrera en
pratique.
Dans le cas d'une application ou d'un projet, il reste évidemment à emballer tous ces
concepts dans une ou plusieurs classes qui remplacent les classes fstream et qui
proposent des fonctions membres telles que LoadContainer() ou
SaveContainer()etc. L'accès à la base de données doit devenir abstraction de
telle sorte que l'utilisateur (entendez, le développeur qui utilise ces classes) n'ait pas
besoin de connaître la syntaxe SQL ni la mécanique des objets MFC.
A partir de ce moment, la même application C++ peut gérer plusieurs bases de
données de même structure puisque la seule chose qui change, c'est le nom
symbolique de la référence ODBC.
H. Schyns
5.1
Accès C++ aux bases de données
6.
6 - Conclusion
Conclusion
Interfacer une base de données en C++ n'est pas excessivement compliqué quand
on sait par quel bout prendre le problème.
Par souci de cohérence avec le reste du cours, nous avons choisi d'utiliser les objets
de la bibliothèque MFC de Microsoft mais d'autres bibliothèques au fonctionnement
similaires existent sur le net.
Deux grandes philosophies sont applicables :
-
le mode DAO, propre à Microsoft et optimisé pour les bases de données MS
Access
le mode ODBC, plus générique, supporté par la plupart des SGBD
Dans ce document, nous avons privilégié le mode ODBC.
Ces philosophies proposent chacune trois méthodes d'accès aux données :
-
utiliser les fonctions d'un objet recordset générique,
mapper les tables sur un objet recordset spécifique,
faire exécuter directement les instructions SQL par le SGBD.
La troisième méthode est la plus efficace quand il s'agit d'exécuter des requêtes
d'action, c'est-à-dire des requêtes qui ne renvoient pas de données et qui
implémentent le C–UD du CRUD :
-
ajouter des enregistrements, mais aussi des tables ou des bases de données,
modifier des enregistrements, mais aussi des tables ou des bases de données,
supprimer des enregistrements, mais aussi des tables ou des bases de
données.
Par contre, pour implémenter le ––R– du CRUD, la deuxième méthode est la plus
simple, moyennant l'écriture d'une classe Recordset spécifique.
H. Schyns
6.1
Accès C++ aux bases de données
7.
7 - Annexe
Annexe
7.1.
CDaoDatabase
Construction
CDaoDatabase
Construit un objet CDaoDatabase. Ensuite appeler Open pour
connecter l'objet à une base de données.
Attributs
CanTransact
Détecte si la base de données prend en charge les transactions.
CanUpdate
Détecte si l'objet est modifiable (= pas en lecture seule).
GetConnect
Retourne la chaîne de connexion utilisée pour connecter l'objet
CDaoDatabase à une base de données.
GetName
Retourne le nom de la base de données en cours d'utilisation.
GetQueryTimeout
Retourne le nombre de secondes après lequel une requête sera
interrompue ou abandonnée.
GetRecordsAffected
Retourne le nombre d'enregistrements affectés par la dernière
action.
GetVersion
Renvoie la version du moteur de base de données associé à la base
de données.
IsOpen
Détecte si l'objet CDaoDatabase est réellement connecté à une base
de données.
SetQueryTimeout
Définit le nombre de secondes après lequel une requête sera
interrompue ou abandonnée.
Operations
Close
Ferme la connexion à la base de données.
Create
Crée une nouvelle base de données DAO et initialise l'objet
CDaoDatabase.
CreateRelation
Crée une nouvelle relation entre deux tables de la base de données.
DeleteQueryDef
Supprime un objet QueryDef enregistré dans la base de données.
DeleteRelation
Supprime une relation existante entre les tables de la base de
données.
DeleteTableDef
Supprime une table (définition et enregistrements) dans la base de
données.
Execute
Exécute une requête action..
GetQueryDefCount
Retourne le nombre de requêtes définies dans la base de données.
GetQueryDefInfo
Renvoie des informations sur une requête définie dans la base de
données.
GetRelationCount
Retourne le nombre de relations définies entre les tables de la base
de données.
GetRelationInfo
Renvoie des informations sur une relation définie entre les tables de
la base de données.
GetTableDefCount
Renvoie le nombre de tables définies dans la base de données.
GetTableDefInfo
Renvoie des informations sur une table spécifiée dans la base de
données.
Open
Établit une connexion à une base de données.
D'après source : http://msdn.microsoft.com
H. Schyns
7.1
Accès C++ aux bases de données
7.2.
7 - Annexe
CDaoRecordset
Construction
CDaoRecordset
Construit un objet CDaoRecordset.
Close
Ferme le recordset.
Open
Crée un nouveau recordset de type table, feuille de réponse
dynamique ou instantané.
Attributs
CanAppend
Renvoie une valeur non nulle si les nouveaux enregistrements
peuvent être ajoutés au recordset via AddNew
CanBookmark
Indique si le recordset supporte les signets.
CanRestart
Indique si Requery peut être utilisé pour le rafraîchissement des
informations.
CanScroll
Renvoie une valeur non nulle s'il est permis de faire défiler les
enregistrements.
CanTransact
Indique si la source de données prend en charge les transactions.
CanUpdate
Indique si le recordset peut être mis à jour.
GetCurrentIndex
Renvoie un CString contenant le nom de l'index actuellement utilisé.
GetDateCreated
Renvoie la date et l'heure à laquelle la table a été créée
GetDateLastUpdated
Renvoie la date et l'heure de la dernière modification apportée à la
conception de la table
GetEditMode
Renvoie le mode d'édition de l'enregistrement en cours.
GetLastModifiedBookmark Détermine le dernier l'enregistrement ayant été ajouté ou mis à jour.
GetName
Renvoie un CString contenant le nom du recordset.
GetParamValue
Récupère la valeur courante d'un paramètre stocké dans l'objet
DAOParameter sous-jacent.
GetRecordCount
Renvoie le nombre d'enregistrements accessibles dans le recordset.
GetSQL
Renvoie la chaîne SQL utilisée pour sélectionner les enregistrements
du recordset.
GetType
Renvoie le type du recordset.
GetValidationRule
Renvoie un CString contenant la règle de validation des données
d'un champ.
GetValidationText
Renvoie le texte qui s'affiche quand une règle de validation n'est pas
satisfaite.
IsBOF
Détecte si le recordset est positionné avant le premier
enregistrement.
IsDeleted
Détecte si le recordset est positionné sur un enregistrement
supprimé.
IsEOF
Détecte si le recordset est positionné après le dernier
enregistrement.
IsFieldDirty
Détecte si le champ spécifié dans l'enregistrement en cours a été
modifié.
IsFieldNull
Détecte si le champ spécifié dans l'enregistrement en cours est
NULL.
IsFieldNullable
Détecte si le champ spécifié dans l'enregistrement en cours peut être
mis à NULL.
IsOpen
Indique si le recordset a bien été ouvert.
SetCurrentIndex
Définit un index sur un recordset de type table.
SetParamValue
Définit la valeur actuelle du paramètre spécifié.
SetParamValueNull
Définit la valeur actuelle du paramètre spécifié à NULL.
H. Schyns
7.2
Accès C++ aux bases de données
7 - Annexe
Opérations de mise à jour
AddNew
Prépare l'ajout d'un nouvel enregistrement. Il faut appeler Update
pour compléter l'insertion des données.
CancelUpdate
Annule toutes les mises à jour en attente.
Delete
Supprime l'enregistrement courant du recordset. Il faut passer à un
autre record pour compléter la suppression.
Edit
Prépare aux modifications de l'enregistrement en cours. Il faut
appeler Update pour compléter l'édition.
Update
Termine une opération AddNew ou Edit en sauvegardant les
données nouvelles ou modifiées dans la source de données.
Opérations de navigation
Find
Localise le premier, suivant, précédent ou dernier enregistrement qui
contient une chaîne particulière.
FindFirst
Localise le premier enregistrement qui correspond aux critères
définis.
FindLast
Localise le dernier enregistrement qui correspond aux critères
définis.
FindNext
Localise l'enregistrement suivant qui correspond aux critères définis.
FindPrev
Localise l'enregistrement précédent qui correspond aux critères
définis.
GetAbsolutePosition
Renvoie l'indice de l'enregistrement courant.
GetBookmark
Renvoie le signet placé sur un enregistrement.
GetPercentPosition
Renvoie la position de l'enregistrement en cours en tant que
pourcentage du nombre total d'enregistrements.
Move
Décale le recordset d'un certain nombre de positions par rapport à
l'enregistrement courant.
MoveFirst
Positionne le recordset sur le premier enregistrement.
MoveLast
Positionne le recordset sur le dernier enregistrement.
MoveNext
Positionne le recordset sur l'enregistrement suivant.
MovePrev
Positionne le recordset sur l'enregistrement précédent.
Seek
Localise l'enregistrement qui correspond aux critères spécifiés.
SetAbsolutePosition
Positionne le recordset sur l'enregistrement voulu.
SetBookmark
Positionne le recordset sur l'enregistrement correspondant à un
signet spécifié.
SetPercentPosition
Positionne le recordset sur l'enregistrement qui correspond à un
pourcentage du nombre total d'enregistrements.
Autres opérations
FillCache
Remplit le cache local d'un recordset avec les données d'une source
ODBC.
GetCacheSize
Renvoie le nombre d'enregistrements contenus dans le cache.
GetCacheStart
Renvoie le signet du premier enregistrement à mettre en cache.
GetFieldCount
Renvoie le nombre de champs dans un recordset.
GetFieldInfo
Renvoie les types d'informations spécifiques sur les champs du
recordset.
GetFieldValue
Renvoie la valeur d'un champ dans un recordset.
GetIndexCount
Récupère le nombre d'index disponibles dans un recordset.
GetIndexInfo
Renvoie les informations sur un index.
GetLockingMode
Renvoie le type de verrouillage en vigueur lors de l'édition.
Requery
Relance une requête afin d'actualiser les enregistrements
sélectionnés.
SetCacheSize
Définit le nombre d'enregistrements à mettre en cache localement.
H. Schyns
7.3
Accès C++ aux bases de données
7 - Annexe
SetCacheStart
Définit le signet du premier enregistrement à mettre en cache.
SetFieldDirty
Marque le champ spécifié de l'enregistrement en cours comme
modifié.
SetFieldNull
Définit la valeur d'un champ à NULL.
SetFieldValue
Définit la valeur d'un champ dans un enregistrement.
SetFieldValueNull
Définit la valeur d'un champ d'un enregistrement à NULL.
SetLockingMode
Définit le type de verrouillage à mettre en œuvre lors de l'édition.
Divers
DoFieldExchange
Echange des données entre l'enregistrement du recordset et celui de
la source de données.
GetDefaultDBName
Renvoie le nom de la source de données par défaut.
GetDefaultSQL
Renvoie la chaîne SQL exécutée par défaut.
D'après source : http://msdn.microsoft.com
H. Schyns
7.4
Accès C++ aux bases de données
7.3.
7 - Annexe
CDatabase
Construction
CDatabase
Construit un objet CDatabase. L'objet doit ensuite être initialisé en
appelant OpenEx ou Ouvrir.
Close
Ferme la connexion à la source de données.
Open
Établit une connexion à une source de données
(via un pilote ODBC).
OpenEx
Établit une connexion à une source de données
(via un pilote ODBC).
Attributs
CanTransact
Détecte si la source de données prend en charge les transactions.
CanUpdate
Détecte si l'objet CDatabase est modifiable (et non en lecture seule).
GetBookmarkPersistence
Identifie les opérations par lesquelles des signets persistent sur les
objets recordset.
GetConnect
Renvoie la chaîne de connexion ODBC utilisée pour connecter l'objet
CDatabase à une source de données.
GetCursorCommitBehavior Identifie l'effet de l'ouverture d'une transaction.
GetCursorRollback
Identifie l'effet de l'annulation d'une transaction.
Behavior
GetDatabaseName
Renvoie le nom de la base de données en cours d'utilisation.
IsOpen
Identifie si l'objet CDatabase est connecté à une source de données.
SetLoginTimeout
Définit le délai d'attente d'une tentative de connexion à une source
de données.
SetQueryTimeout
Définit le délai d'attente pour l'exécution d'une requête.
Opérations
BeginTrans
Débute une «transaction», c'est-à-dire une série d'appels réversibles
à AddNew, Edit, Delete, etc sur la source de données ouverte.
BindParameters
Attache des paramètres à une requête SQL avant de l'exécuter.
Cancel
Annule une opération asynchrone ou un processus.
CommitTrans
Clôture la définition et effectue une série d'opérations commencée
par BeginTrans. Les commandes de la transaction qui modifient la
source de données sont effectuées.
ExecuteSQL
Exécute une instruction SQL qui ne renvoie pas de données
(requêtes "Action" uniquement).
Rollback
Annule les modifications effectuées pendant la transaction en cours.
La source de données retourne l'état précédent défini lors de l'appel
à BeginTrans.
Options modifiables
OnSetOptions
Définit les options de connexion standard.
D'après source : http://msdn.microsoft.com
H. Schyns
7.5
Accès C++ aux bases de données
7.4.
7 - Annexe
CRecordset
Data membres
m_hstmt
Contient la handle ODBC du recordset.
m_nFields
Contient le nombre d'attributs du jeu d'enregistrements.
m_nParams
Contient le nombre de paramètres dans le jeu d'enregistrements.
m_pDatabase
Contient un pointeur vers l'objet CDatabase reliée à la source de
données.
m_strFilter
Contient la clause WHERE utilisée comme un filtre pour sélectionner
les enregistrements.
m_strSort
Contient la clause ORDER BY utilisée pour trier les enregistrements.
Construction
Close
Ferme le recordset et la liaison ODBC qui associée.
CRecordset
Construit un objet CRecordset. Toute classe dérivée doit fournir un
constructeur qui appelle celui-ci.
Open
Ouvre le recordset en accédant à la table désignée ou en exécutant
la requête.
Attributs
CanAppend
Indique s'il est permis d'ajouter de nouveaux enregistrements au
recordset.
CanBookmark
Indique si le recordset supporte les signets.
CanRestart
Indique s'il est permis de réexécuter la requête qui a servi à
alimenter le recordset.
CanScroll
Indique s'il est permis de naviguer dans les enregistrements.
CanTransact
Indique si la source de données prend en charge les transactions.
CanUpdate
Indique si le jeu d'enregistrements peut être mis à jour.
GetODBCFieldCount
Renvoie le nombre de champs du recordset.
GetRecordCount
Renvoie le nombre d'enregistrements dans le recordset.
GetSQL
Renvoie l'expression SQL utilisée pour sélectionner des
enregistrements du recordset.
GetStatus
Renvoie l'état du recordset.
GetTableName
Renvoie le nom de la table sur laquelle le recordset est basé.
IsBOF
Indique si le début du recordset a été atteint.
IsDeleted
Indique si l'enregistrement courant a été supprimé.
IsEOF
Indique si la fin du recordset a été atteinte.
IsOpen
Indique si le recordset a bien été ouvert.
Opérations de mise à jour
AddNew
Prépare le recordset pour l'ajout d'un nouvel enregistrement.
CancelUpdate
Annule toutes les mises à jour en attente.
Delete
Supprime l'enregistrement courant du recordset.
Edit
Prépare des modifications à l'enregistrement en cours.
Update
Termine une opération AddNew ou Edit en sauvegardant les
données du recordset vers la base de données.
Opérations de navigation
GetBookmark
Renvoie la valeur du signet associé à un record donné.
Move
Décale la position du recordset d'un certain nombre de records à
compter à partir du record courant.
MoveFirst
Positionne le recordset sur le premier record.
MoveLast
Positionne le recordset sur le dernier record.
MoveNext
Positionne le recordset sur le record suivant.
H. Schyns
7.6
Accès C++ aux bases de données
7 - Annexe
MovePrev
Positionne le recordset sur le record précédent.
SetAbsolutePosition
Positionne l'index du recordset sur le record qui correspond à la
position spécifiée.
SetBookmark
Positionne l'index du recordset sur l'enregistrement indiqué par le
signet.
Autres opérations
Cancel
Annule une opération asynchrone ou un processus à partir d'un
deuxième thread.
FlushResultSet
Indique si un autre jeu de données doit encore être extrait à extrait
lors de l'utilisation d'une requête prédéfinie.
GetFieldValue
Renvoie la valeur d'un champ du record courant.
GetODBCFieldInfo
Renvoie des informations sur les types des champs dans un record.
GetRowsetSize
Renvoie le nombre d'enregistrements à récupérer au cours d'une
seule lecture.
GetRowsFetched
Renvoie le nombre de lignes effectivement récupérées au cours
d'une lecture.
GetRowStatus
Renvoie l'état de la ligne après une extraction.
IsFieldDirty
Indique si le champ spécifié dans le record courant a été modifié.
IsFieldNull
Indique si le champ spécifié dans le record courant est NULL.
IsFieldNullable
Indique si le champ spécifié dans le record courant peut être mis à
NULL.
RefreshRowset
Actualise les données et l'état de la ligne spécifiée.
Requery
Réexécute la requête liée au recordset afin d'actualiser les
enregistrements sélectionnés.
SetFieldDirty
Marque le champ spécifié du record courant comme "modifié".
SetFieldNull
Met la valeur du champ spécifié dans le record courant à NULL.
SetLockingMode
Définit le mode de verrouillage des enregistrements de la base de
données ("optimiste" (par défaut) ou "pessimiste").
SetParamNull
Met la valeur du paramètre spécifié à NULL.
SetRowsetCursorPosition Positionne le curseur du recordset sur le record indiqué.
Options modifiables
CheckRowsetError
Vérifie si des erreurs ont été générées pendant le chargement.
DoBulkFieldExchange
Echange un bloc de lignes de données entre la base de données et
le recordset.
DoFieldExchange
Echange des données (dans les deux sens) entre le recordset et
l'enregistrement correspondant de la source de données.
GetDefaultConnect
Renvoie la chaîne de connexion par défaut.
GetDefaultSQL
Renvoie la chaîne SQL à exécuter par défaut.
OnSetOptions
Définit les options (utilisé sur la sélection) pour la déclaration ODBC
spécifiée.
OnSetUpdateOptions
Définit les options (utilisés sur les mises à jour) pour le compte de
ODBC spécifiée.
SetRowsetSize
Indique le nombre d'enregistrements à récupérer lors d'une lecture.
D'après source : http://msdn.microsoft.com
H. Schyns
7.7
Accès C++ aux bases de données
7.5.
7 - Annexe
Utilisation d'un objet COleVariant
A la base d'un type COleVariant, on trouve une union des différents types
possibles. Ceci signifie que l'information contenue dans un variant ne peut être
que d'un et un seul type mais qu'elle peut éventuellement être interprétée comme
étant d'un type différent
Type C/C++
Type
symbolique
(typedef)
Data
Membre
Type C/C++
Type
symbolique
(typedef)
Data
Membre
BSTR
bstrVal
LONGLONG
llVal
BSTR*
pbstrVal
LONGLONG*
pllVal
byte
BYTE
bVal
PVOID
byref
byte*
BYTE*
pbVal
SAFEARRAY*
parray
char
CHAR
cVal
SAFEARRAY**
pparray
char*
CHAR*
pcVal
SCODE
scode
CY
cyVal
SCODE*
pscode
CY*
pcyVal
short int
SHORT
iVal
date
DATE
date
short int*
SHORT*
piVal
date*
DATE*
pdate
unsigned int
UINT
uintVal
DECIMAL*
pdecVal
unsigned int*
UINT*
puintVal
double
DOUBLE
dblVal
unsigned long
ULONG
ulVal
double*
DOUBLE*
pdblVal
unsigned long*
ULONG*
pulVal
float
FLOAT
fltVal
ULONGLONG
ullVal
float*
FLOAT*
pfltVal
ULONGLONG*
pullVal
IDispatch*
pdispVal
unsigned short
USHORT
uiVal
IDispatch**
ppdispVal
unsigned short* USHORT*
int
INT
intVal
VARIANT*
pvarVal
int*
INT*
pintVal
VARIANT_BOOL
boolVal
IUnknown*
punkVal
VARIANT_BOOL*
pboolVal
IUnknown**
ppunkVal
_VARIANT_BOOL
bool
long int
LONG
lVal
_VARIANT_BOOL* pbool
long int*
LONG*
plVal
void*
puiVal
Comment convertir un type COleVariant en un type CString
void main (void)
{
COleVariant var;
CDaoRecordset recset;
CString str;
// toute autre déclaration utile...
var = recset.GetFieldValue(i);
switch (var.vt)
{
case VT_BSTR:
str = (LPCSTR) var.bstrVal; break;
case VT_I2:
str.Format("%d", (int) var.iVal); break;
case VT_I4:
str.Format("%d", var.lVal); break;
case VT_R4:
str.Format("%10.2f", (double) var.fltVal); break;
H. Schyns
7.8
Accès C++ aux bases de données
7 - Annexe
case VT_R8:
str.Format("%10.2f", var.dblVal); break;
case VT_CY:
str = COleCurrency(var).Format(); break;
case VT_DATE:
str = COleDateTime(var).Format(); break;
case VT_BOOL:
str = (var.boolVal == 0) ? "FALSE" : "TRUE"; break;
case VT_NULL:
str = "----"; break;
default:
str.Format("Unk type %d\n", var.vt);
}
}
d'après source : http://bbs.csdn.net/topics/454895
Pour les conversions entre les différents types de chaînes de caractères, voir la
référence indiquée dans les sources.
7.6.
CDBVariant
Data Members
m_boolVal
Valeur de type BOOL.
m_chVal
Valeur de type unsigned char.
m_dblVal
Valeur de type double.
m_dwType
Contient le type de la valeur conservée.
m_fltVal
Valeur de type float.
m_iVal
Valeur de type short.
m_lVal
Valeur de type long.
m_pbinary
Pointeur vers un objet CLongBinary.
m_pdate
Pointeur vers un objet de type TIMESTAMP_STRUCT.
m_pstring
Pointeur vers un objet CString.
m_pstringA
Pointeur vers un objet CString ASCII.
m_pstringW
Pointeur vers un objet CString Unicode.
Construction
CDBVariant
Constructeur.
Operations
Clear
Efface l'objet CDBVariant.
D'après source : http://msdn.microsoft.com
7.7.
Fonctions de mappage RFX
Fonction MFC
Type C++
RFX_Bool
BOOL
RFX_Byte
BYTE
RFX_Binary
CByteArray
RFX_Double
double
RFX_Single
float
RFX_Int
int
RFX_Long
long
RFX_LongBinary
CLongBinary
RFX_Text
CString
RFX_Date
CTime
H. Schyns
7.9
Accès C++ aux bases de données
7.8.
7 - Annexe
Principales instructions SQL
Database management
Créer une base de données
Supprimer une base de données
CREATE DATABASE db_name
DROP DATABASE db_name
Table management
Créer une table
Supprimer une table
Ajouter une colonne
Supprimer une colonne
Créer un index
Supprimer un index
Créer une relation
Supprimer une relation
Créer une table virtuelle
CREATE TABLE tbl_name
( col_name1 data_type1,
col_name2 data_type2,
col_name2 data_type3, ...)
DROP TABLE tbl_name
ALTER TABLE tbl_name
ADD col_name data_type
ALTER TABLE tbl_name
DROP COLUMN col_name
CREATE [UNIQUE] INDEX idx_name
ON tbl_name (col_name)
DROP INDEX tbl_name.idx_name
DROP INDEX idx_name ON tbl_name
DROP INDEX idx_name
ALTER TABLE tbl_name
DROP INDEX idx_name
ALTER TABLE child_name
ADD FOREIGN KEY (fkey_name)
REFERENCES parent_name(pkey_name)
ALTER TABLE child_name
DROP FOREIGN KEY fkey_name
CREATE VIEW view_name AS
SELECT col_name(s)
FROM tbl_name
WHERE condition
(SQL Server)
(MS Access)
(DB2/Oracle)
(MySQL)
Data management
INSERT INTO tbl_name
[(column1, column2, column3,...)]
VALUES (value1, value2, value3,....)
SELECT *|col_name(s)
Copier entre tables
INTO new_tbl_name [IN externaldatabase]
FROM old_tbl_name
DELETE [*] FROM tbl_name
Effacer des enregistrements
[WHERE some_column=some_value]
Effacer tous les enregistrements TRUNCATE TABLE tbl_name
Mettre les enregistrements à jour UPDATE tbl_name
SET column1=value, column2=value,...
WHERE some_column=some_value
SELECT [DISTINCT] col_name(s)
Sélectionner des données
FROM tbl_name
[WHERE col_name operator value]
[ORDER BY col_name [ASC|DESC]]
SELECT [DISTINCT] col_name(s)
Sélectionner des données dans
FROM tbl_name1
plusieurs tables
INNER|LEFT|RIGHT|FULL JOIN
tbl_name2 ON
tbl_name1.col_name=tbl_name2.col_name
SELECT TOP number|percent col_name(s)
Sélectionner les premières
FROM tbl_name
données
SELECT col_name(s) FROM tbl_name1
Combiner des données
UNION
SELECT col_name(s) FROM tbl_name2
Ajouter des enregistrements
D'après source : http://www.w3schools.com/sql/default.asp
H. Schyns
7.10
Accès C++ aux bases de données
8.
8 - Sources
Sources
8.1.
Ouvrages
-
Pont entre C et C++
P.-N. Lapointe
Addison-Wesley
ISBN 2-87908-094-0µ
-
Visual C++ 4.0
P. Longuet
Collection "Livre d'Or"
Sybex
ISBN 2-7361-1646-1
-
Professional C++
N. A. Solter, S. J. Kleper
Wrox
ISBN 0-7645-7484-1
8.2.
-
Sites web
Using the CDatabase class to read an Access databases
Zhaque (pseudo)
Code Project
http://www.codeproject.com/Articles/980/Using-the-CDatabase-class-to-read-an-Access-databa
Un bon point de départ pratique.
-
CString Management
Joseph M. Newcomer
Code Project
http://www.codeproject.com/Articles/542/CString-Management
Probablement la meilleure référence en ce qui concerne le fonctionnement des
objets CString.
-
Que sont DAO et ODBC et articles connexes
Microsoft : MSDN Library
http://msdn.microsoft.com/fr-fr/library/et1kh6d3%28v=vs.80%29.aspx
-
MFC Reference
Microsoft : MSDN Library
http://msdn.microsoft.com/fr-fr/library/d06h2x6e%28v=vs.80%29.aspx
Le plan de tous les objets de la librairie MFC… Documentation très complète
mais très touffue. Excellente pour approfondir le sujet mais à éviter absolument
pour une première prise de contact.
-
Data source name et articles connexes
Œuvre collective
Wikipedia
http://en.wikipedia.org/wiki/Data_source_name
H. Schyns
8.1
Accès C++ aux bases de données
-
8 - Sources
XL97: Using System, User, and File Data Sources
Microsoft : Aide et Support
http://support.microsoft.com/kb/159557
-
Définition et manipulation de données avec DAO
Christophe WARIN
Developpez.com
http://warin.developpez.com/access/dao
-
FAQ VC++ et MFC
Œuvre collective
Developpez.com
http://cpp.developpez.com/faq/vc/?page=ODBC
-
How to: Convert Between Various String Types
Microsoft : Aide et Support
http://msdn.microsoft.com/en-us/library/ms235631%28VS.80%29.aspx
-
CString Management
Joseph M. Newcomer
Code Project
http://www.codeproject.com/Articles/542/CString-Management
-
The Connection String Reference
Œuvre collective
http://www.connectionstrings.com/
Une ressource de tout premier plan qui fournit les chaînes de connexion pour
des centaines de types de bases de données !
-
SQL Quick Reference From W3Schools
Œuvre collective
W3School
http://www.w3schools.com/sql/sql_quickref.asp
Une excellente ressource qui propose un ensemble de tutoriels très clairs pour
tous les langages associés au web dynamique : HTML, CSS, JAVASCRIPT,
JQUERY, XML, ASP.NET, PHP, SQL, etc
-
C++ Documentation & didacticiels
Œuvre collective
Cplusplus
http://www.cplusplus.com/
Une ressource qui remplace avantageusement l'aide de Visual Studio
H. Schyns
8.2