53
ENSEIGNEMENT DE PROMOTION SOCIALE —————————————————————— Cours de LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - —————————————————————— H. Schyns v 1.0 Avril 2013

Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

  • Upload
    dinhnga

  • View
    221

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

ENSEIGNEMENT DE PROMOTION SOCIALE

—————————————————————— Cours de

LANGAGE ORIENTE OBJET

- Accès C++ aux bases de données -

——————————————————————

H. Schyns

v 1.0

Avril 2013

Page 2: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données Sommaire

H. Schyns S.1

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. DSN 2.3.2. Chaîne de connexion 2.3.3. ODBC 2.3.4. DAO 2.3.5. MFC

3. LES PREMIERS PAS EN DAO

3.1. Créer la base de données 3.2. Se connecter à la base de données 3.3. Lire les enregistrements de la table T_Villes 3.4. Ajouter des enregistrements dans la table T_Villes 3.5. Modifier des enregistrements de la table T_Villes 3.6. Supprimer des enregistrements de la table T_Villes

4. PREMIERS PAS EN ODBC

4.1. Créer la base de données 4.2. Se connecter à la base de données 4.3. Lire les enregistrements de la table T_Villes 4.4. Mapper la table sur un recordset

4.4.1. Position du problème 4.4.2. Créer une classe dérivée de CRecordset 4.4.3. Lire les enregistrements de la table T_Villes 4.4.4. Ajouter des enregistrements dans la table T_Villes 4.4.5. Modifier des enregistrements de la table T_Villes 4.4.6. Supprimer des enregistrements de la table T_Villes

4.5. Utiliser le mode SQL 4.5.1. Position du problème 4.5.2. Ajouter des enregistrements dans la table T_Villes 4.5.3. Modifier des enregistrements de la table T_Villes 4.5.4. Supprimer des enregistrements de la table T_Villes

5. POUR ALLER PLUS LOIN…

6. CONCLUSION

Page 3: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données Sommaire

H. Schyns S.2

7. ANNEXE

7.1. CDaoDatabase 7.2. CDaoRecordset 7.3. CDatabase 7.4. CRecordset 7.5. Utilisation d'un objet COleVariant 7.6. CDBVariant 7.7. Fonctions de mappage RFX 7.8. Principales instructions SQL

8. SOURCES

8.1. Ouvrages 8.2. Sites web

Page 4: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 1 - Introduction

H. Schyns 1.1

1. 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

Page 5: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.1

2. 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.

Page 6: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.2

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).

Page 7: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.3

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.

Page 8: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.4

2.3.3. 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.

Page 9: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.5

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 !

Page 10: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 2 - Principes de base et vocabulaire

H. Schyns 2.6

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.

Page 11: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

fig. 2.5 Ensemble des classes de la bibliothèque MFC

Page 12: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.1

3. 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)

- idVille : identifiant de la ville, de type numérique entier long, - Nom : nom de la ville, de type texte, pouvant contenir 50 caractères.

Page 13: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.2

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.

Page 14: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.3

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.

// TestDataBase.cpp #define WINVER 0x0501 // version pour Windows XP #include <afxdao.h> // bibliothèque des objets DAO #include <iostream> using namespace std;

1 Si ça ne marche pas, nous prendrons l'option "Utiliser les MFC dans une bibliothèque statique"

Page 15: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.4

void main(void) { CDaoDatabase dbUrbain; // objet DataBase CString sPathDb("E:\\Lang_Cpp\\urbain.mdb"); // 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 = Windows XP 0x0502 = Windows Server 2003 0x0600 = Windows Vista 0x0601 = Windows 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 Create CreateRelation Execute IsOpen 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.

Page 16: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.5

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! -- // Affichage à l'écran -- DB deconnectee et fermee! -- Appuyez sur une touche pour continuer...

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 ".

Page 17: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.6

3.3. 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

Page 18: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.7

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(); // aller au premier record de la table while(!rsVilles.IsEOF()) // boucler sur les records { x // etc rsVilles.MoveNext(); }

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

Page 19: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.8

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; // variables non typées x // etc rsVilles.GetFieldValue(0,vIdVille); // premier champ rsVilles.GetFieldValue(1,vNom); // deuxieme champ x // 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

Page 20: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.9

-- DB trouvee et connectee! -- // Affichage à l'écran 1 - Liege 2 - Bruxelles 3 - Antwerpen 4 - Namur 5 - Charleroi -- DB deconnectee et fermee! -- Appuyez sur une touche pour continuer...

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 }

Page 21: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.10

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! -- // Affichage à l'écran 1 - Liege 2 - Bruxelles 3 - Antwerpen 4 - Namur 5 - Charleroi 6 - Arlon // nouveau record ! -- DB deconnectee et fermee! --

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

Page 22: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.11

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 }

Page 23: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.12

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(); // préparer le record local rsVilles.SetFieldValue(0,vIdVille); // y mettre les valeurs rsVilles.SetFieldValue(1,vNom); rsVilles.Update(); // transférer vers la table

Voici ce que doit afficher notre programme de test lors de l'exécution :

-- DB trouvee et connectee! -- // Affichage à l'écran avant : 1 - Liege apres : 1001 - Liegexxx avant : 2 - Bruxelles apres : 1002 - Bruxellesxxx avant : 3 - Antwerpen apres : 1003 - Antwerpenxxx avant : 4 - Namur apres : 1004 - Namurxxx avant : 5 - Charleroi apres : 1005 - Charleroixxx avant : 6 - Arlon apres : 1006 - Arlonxxx -- DB deconnectee et fermee! --

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.

Page 24: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.13

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) // si IdVille est impair {wcout << "effacer : " ; rsVilles.Delete(); // effacer le record }

Voici ce que doit afficher notre programme de test lors de l'exécution :

-- DB trouvee et connectee! -- // Affichage à l'écran effacer : 1001 - Liegexxx conserver : 1002 - Bruxellesxxx effacer : 1003 - Antwerpenxxx conserver : 1004 - Namurxxx effacer : 1005 - Charleroixxx conserver : 1006 - Arlonxxx -- DB deconnectee et fermee! --

Les suppressions ont bien été effectuées dans la base de données MS Access (fig. 3.9).

Page 25: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 3 - Les premiers pas en DAO

H. Schyns 3.14

fig. 3.9 Les enregistrements impairs sont supprimés dans la table T_Villes

Page 26: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.1

4. 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 .

// TestDataBase.cpp #define WINVER 0x0501 // version pour Windows XP #include <afxdb.h> // bibliothèque des objets ODBC #include <iostream> using namespace std; void main(void) { CDatabase dbUrbain; // objet database ODBC CString sDSN ("GestionChantiers"); // source ODBC

Page 27: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.2

//--- 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 // etc // ---- définir les objets nécessaires ---- CRecordset rsVilles(&dbUrbain); CString sSQL("SELECT * FROM T_Villes"); CDBVariant vIdVille; CStringA sNom; // ---- 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; }

Page 28: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.3

// ---- 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.

Page 29: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.4

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! -- // Affichage à l'écran 3 - Antwerpen 2 - Bruxelles 5 - Charleroi 1 - Liege 4 - Namur -- DB deconnectee et fermee! --

4.4. 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.

// CRecordsetV.h #ifndef WINVER // pourrait avoir été défini ailleurs #define WINVER 0x0501 // version pour Windows XP #endif #include <afxdb.h>

Page 30: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.5

class CRecordsetV : public CRecordset // héritage { public: // public et non protected! long m_idVille; // data membres correspondant CStringA m_Nom; // aux champs de la table public: // constructeur avec param CRecordsetV (CDatabase* pDatabase = NULL); // fonction de mappage virtual void 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().

Page 31: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.6

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.

// TestDataBase.cpp #ifndef WINVER // pourrait avoir été défini ailleurs #define WINVER 0x0501 // selon la séquence de compilation #endif #include <afxdb.h> #include <iostream> #include "crecordsetv.h" // inclure notre recordset dérivé using namespace std; 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

Page 32: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.7

CString 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) // TestDataBase.cpp { CDatabase dbUrbain; x // 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 }

Page 33: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.8

// ---- 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(); // 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

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! -- // Affichage à l'écran 3 - Antwerpen 7 - Arlon 2 - Bruxelles 5 - Charleroi 1 - Liege 8 - Mons 4 - Namur 6 - Verviers -- DB deconnectee et fermee! --

4.4.5. 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é.

Page 34: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.9

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) // TestDataBase.cpp { CDatabase dbUrbain; x // 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! -- // Affichage à l'écran 1003 - Antwerpenxxx 1007 - Arlonxxx 1002 - Bruxellesxxx 1005 - Charleroixxx 1001 - Liegexxx 1008 - Monsxxx 1004 - Namurxxx 1006 - Verviersxxx -- DB deconnectee et fermee! --

4.4.6. 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 :

Page 35: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.10

void main(void) // TestDataBase.cpp { CDatabase dbUrbain; x // 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! -- // Affichage à l'écran Effacer Antwerpenxxx Effacer Arlonxxx Conserver Bruxellesxxx Effacer Charleroixxx Effacer Liegexxx Conserver Monsxxx Conserver Namurxxx Conserver Verviersxxx 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.

Page 36: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.11

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 Nom 6 Verviers 7 Arlon 8 Mons

Nous créerons pour chacun des enregistrements une requête qui aura la forme ci-dessous :

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) // TestDataBase.cpp { CDatabase dbUrbain; x // 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 }

Page 37: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.12

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! -- // Affichage à l'écran INSERT INTO T_Villes (idVille, Nom) VALUES (6, 'Verviers'); INSERT INTO T_Villes (idVille, Nom) VALUES (7, 'Arlon'); INSERT INTO T_Villes (idVille, Nom) VALUES (8, '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';

Page 38: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.13

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) // TestDataBase.cpp { CDatabase dbUrbain; x // 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; // ---- lister tous les records de la table ---- x // 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.

Page 39: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 4 - Premiers pas en ODBC

H. Schyns 4.14

void main(void) // TestDataBase.cpp { CDatabase dbUrbain; x // 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! --

Page 40: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 5 - Pour aller plus loin…

H. Schyns 5.1

5. 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.

Page 41: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 6 - Conclusion

H. Schyns 6.1

6. 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.

Page 42: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.1

7. 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

Page 43: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.2

7.2. 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.

Page 44: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.3

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.

Page 45: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.4

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

Page 46: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.5

7.3. 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 Behavior

Identifie l'effet de l'annulation d'une transaction.

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

Page 47: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.6

7.4. 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.

Page 48: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.7

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

Page 49: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.8

7.5. 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 void* 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* puiVal 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

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;

Page 50: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.9

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

Page 51: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 7 - Annexe

H. Schyns 7.10

7.8. Principales instructions SQL

Database management Créer une base de données CREATE DATABASE db_name Supprimer une base de données DROP DATABASE db_name Table management Créer une table CREATE TABLE tbl_name

( col_name1 data_type1, col_name2 data_type2, col_name2 data_type3, ...)

Supprimer une table DROP TABLE tbl_name Ajouter une colonne ALTER TABLE tbl_name

ADD col_name data_type Supprimer une colonne ALTER TABLE tbl_name

DROP COLUMN col_name Créer un index CREATE [UNIQUE] INDEX idx_name

ON tbl_name (col_name) Supprimer un index DROP INDEX tbl_name.idx_name (SQL Server)

DROP INDEX idx_name ON tbl_name (MS Access) DROP INDEX idx_name (DB2/Oracle) ALTER TABLE tbl_name DROP INDEX idx_name (MySQL)

Créer une relation ALTER TABLE child_name ADD FOREIGN KEY (fkey_name) REFERENCES parent_name(pkey_name)

Supprimer une relation ALTER TABLE child_name DROP FOREIGN KEY fkey_name

Créer une table virtuelle CREATE VIEW view_name AS SELECT col_name(s) FROM tbl_name WHERE condition

Data management Ajouter des enregistrements INSERT INTO tbl_name

[(column1, column2, column3,...)] VALUES (value1, value2, value3,....)

Copier entre tables SELECT *|col_name(s) INTO new_tbl_name [IN externaldatabase] FROM old_tbl_name

Effacer des enregistrements DELETE [*] FROM tbl_name [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

Sélectionner des données SELECT [DISTINCT] col_name(s) FROM tbl_name [WHERE col_name operator value] [ORDER BY col_name [ASC|DESC]]

Sélectionner des données dans plusieurs tables

SELECT [DISTINCT] col_name(s) FROM tbl_name1 INNER|LEFT|RIGHT|FULL JOIN tbl_name2 ON tbl_name1.col_name=tbl_name2.col_name

Sélectionner les premières données

SELECT TOP number|percent col_name(s) FROM tbl_name

Combiner des données SELECT col_name(s) FROM tbl_name1 UNION SELECT col_name(s) FROM tbl_name2

D'après source : http://www.w3schools.com/sql/default.asp

Page 52: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 8 - Sources

H. Schyns 8.1

8. 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

Page 53: Langage Orienté Objet - Bienvenuenotesdecours.drivehq.com/courspdf/LangCPP_AccesSGBD.pdf · LANGAGE ORIENTE OBJET - Accès C++ aux bases de données - ... (SGBD). La base de données

Accès C++ aux bases de données 8 - Sources

H. Schyns 8.2

- 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