On souhaite réaliser la connexion d'un programme avec des sources de données Ici, des bases de données relationnelles Deux approches sont possibles qui mettent en œuvre chacune son propre jeu de classes MFC La première approche concerne l'interface DAO ( Data Access Objects). L'approche DAO est une interface sur le moteur de données Jet qui permet de stocker et d'extraire des données dans différents SGBD. Jet est le moteur utilisé par le SGBD Microsoft Access (.mdb) Aussi si l'application à développer concerne essentiellement des bases de données Access, l'approche DAO peut être conseillée. 1 La deuxième approche concerne l'interface ODBC (Open Database Connectivity). Cette approche permet de manier tous les formats de données pour lesquels le pilote ODBC approprié existe. Ce pilote existe pour les bases comme : Microsoft Access, Oracle, DBase 5, Si l'application doit travailler sur différentes sources de données dans un environnement client/serveur, l'approche ODBC est préférable. ODBC est une interface, indépendante du système, avec un environnement de bases de données avec un pilote ODBC. Le pilote définit l'interface entre les appels pour des opérations sur les bases de données (indépendants du système) et les spécificités de la mise en œuvre d'une BD particulière. Nous allons uniquement étudier l'approche ODBC, avec ici, une base de données relationnelle sous Microsoft Access et créer une application capable d'aller interroger la base de données 2 Schéma de la base de données On considère une base de données sur des étudiants, dont le schéma est le suivant. ETUDIANT NoEtudiant FACULTE UNIVERSITE Nom NoFaculte Est attaché à Prenom 1,1 NoUniversite Nom Adresse Adresse Age Obtient Note EPREUVE NoEpreuve Coefficient 1,1 Appartient A reussi Année Obtention MATIERE NoMatiere Fait partie 1,1 DIPLOME NoDiplome Option MODULE NoModule 3 Enregistrement d'une Base de Données pour ODBC Avant de pouvoir utiliser une BD avec OBDC, il faut l'inscrire dans la base de registres. Ceci se fait par l'intermédiaire de la boîte de dialogue "Administrateur de sources de données ODBC" accessible sous : Démarrer -> Panneau de configuration (->Outils d'administration) -> Sources de données ODBC 4 Enregistrement d'une Base de Données ODBC Pour ajouter une nouvelle source de données, on clique sur le bouton Ajouter, la fenêtre suivante apparaît : 5 6 1
On sélectionne le driver souhaité : ici une Base de Données Microsoft Access, puis on clique sur Terminer et on obtient : Puis on sélectionne, la Base de Données avec l'option Sélectionner : 7 8 A ce niveau, la Base de Données Base.mdb est accessible via l'interface ODBC. A partir de là, on peut construire une application Visual C++ qui pourra accéder à la Base de Données La création d'un squelette d application qui manipule une Base de Données peut se faire en sélectionnant certaines options lors de la création du projet sous AppWizard. Application Visual C++ ODBC Base de Données 9 On peut sélectionner : Une application «Single document» avec une architecture «Document/View». Et l'option «Vue d'une Base de Données» (sans gestion de fichiers), on ne souhaite pas conserver les données dans des fichiers. 10 On sélectionne la Source de données pour choisir la base de données et le mode de travail. On obtient ensuite une fenêtre permettant de sélectionner une (ou plusieurs tables) sur laquelle on veut travailler (d'autres pourront être ajoutées ensuite ). 11 12 2
Les étapes suivantes sont inchangées. On obtient enfin : Les classes CBDView et CBDSet vont servir à stocker et visualiser les données de la table Etudiant sélectionnée précédemment. Aussi on renomme ces classes : CBDView CEtudiantView BDView.h EtudiantView.h BDView.cpp EtudiantView.cpp CBDSet CEtudiantSet BDSet.h EtudiantSet.h BDSet.cpp EtudiantSet.cpp Les classes MFC prenant en charge ODBC La prise en charge d'odbc par les MFC est faite à l'aide de 5 classes : CDatabase, CRecordSet, CRecordView, CFieldExchange, CDBException. 13 14 La classe CDataBase Un objet de la classe CDatabase représente une connexion à la base de données manipulée. Cette connexion doit être faite avant toute opération sur la base. Cette classe possède un certain nombre de méthodes permettant de se connecter à la base (Open, OpenEx), se déconnecter (Close), tester les propriétés de la base (GetConnect, IsOpen, GetDatabaseName, CanUpdate, CanTransact, ), faire des opérations sur la base. 15 La classe CRecordSet Un objet d'une classe dérivée de cette classe représente le résultat d'une opération SELECT de SQL. Lorsqu'une requête est exécutée sur la base, le résultat peut être manipulé grâce à la classe CRecorSet ou plus exactement une classe dérivée de CRecordSet. Cette classe possède plusieurs attributs : m_hstmt : Numéro de handle de la connexion m_nfields : Nombre d'attributs de l'enregistrement m_nparams : Nombre de paramètres (pour la requête) m_pdatabase : Pointeur sur un objet de type CDatabase (connexion) m_strfilter : Chaîne CString qui spécifie la partie WHERE d'une requête SELECT de SQL (Filtre pour la récupération des informations) m_strsort : Chaîne CString qui spécifie une requête SQL avec ORDER BY pour effectuer un tri 16 La classe CRecordSet Cette classe possède de nombreuses méthodes dont certaines sont à surcharger. Elles permettent : de construire un objet de la classe, de tester les propriétés, de mettre à jour les valeurs, de parcourir les valeurs, etc 17 La classe CRecordView Un objet d'une classe dérivée de CRecordView permet : d'afficher la ligne en cours d'un jeu de lignes (c'est-àdire l'enregistrement courant parmi l'ensemble des enregistrements). Cet objet «vue» utilise une boîte de dialogue pour afficher les données. Il existe des mécanismes automatiques de mise à jour des contrôles de la boîte de dialogue échange d'informations entre la boîte de dialogue, la classe CRecordSet et la base de données. 18 3
La classe CRecordView L'aspect par défaut d'une fenêtre d'affichage dérivée de CRecordView est celle ci-dessus. La classe CFieldExchange Cette classe permet l'échange de données entre la base de données et un jeu de lignes (i.e. un objet de type CRecordSet). Des mécanisme automatiques permettent ces échanges. La méthode SetFieldType permet de définir le mode d'échange souhaité. 19 20 La classe CDBException Cette classe est la classe des exceptions produites lors d'opérations erronées avec OBDC. Les attributs de cette classe sont : m_nretcode : Code d'erreur ODBC m_strerror : Chaîne décrivant l'erreur m_strstatenativeorigin : Chaîne décrivant l'erreur (associé au code OBBC) Comme nous l'avons vu précédemment, on a créé automatiquement un squelette d'application avec les classes CEtudiantView, CEtudiantSet, CBDDoc, CBDApp et CMainFrame. 21 22 Les classes CMainFrame, CBDApp et CBDDoc Les classes CMainFrame et CBDApp créées sont standard et CApp possède la méthode InitInstance permettant la création de l'architecture document/vue. La classe CBDDoc ne diffère de la classe généralement créée que par l'insertion d'un attribut de type CEtudiantSet : class CBDDoc : public CDocument protected: CBDDoc(); DECLARE_DYNCREATE(CBDDoc) CEtudiantSet m_etudiantset; virtual BOOL OnNewDocument(); virtual ~CBDDoc(); protected: DECLARE_MESSAGE_MAP() ; Cette classe permet l'extraction d'informations de la base de données. Ici compte-tenu des options choisies lors de la création du projet avec la sélection de la table Etudiant, la classe CEtudiantSet va permettre de stocker les informations concernant un étudiant; 23 24 4
class CEtudiantSet : public CRecordSet CEtudiantSet(CDatabase* pdatabase = NULL); DECLARE_DYNAMIC(CEtudiantSet) // Field/Param Data //AFX_FIELD(CEtudiantSet, CRecordset) long m_noetudiant; CStringW m_nom; CStringW m_prenom; // CStringW = CString en unicode CStringW m_adresse; long m_age; CStringW m_sexe; //AFX_FIELD // Overrides // ClassWizard generated virtual function overrides //AFX_VIRTUAL(CEtudiantSet) virtual CString GetDefaultConnect(); // Default connection string virtual CString GetDefaultSQL(); // default SQL for Recordset virtual void DoFieldExchange(CFieldExchange* pfx); // RFX support //AFX_VIRTUAL ; Le constructeur et les attributs Les attributs qui permettent le stockage des informations sur un étudiant sont automatiquement générés à partir des nom des attributs de la table Etudiant de la Base de Données (qui avait été sélectionnée). Le constructeur possède un paramètre de type pointeur sur CDatabase CEtudiant :: CEtudiantSet(CDatabase* pdatabase = NULL); Ce paramètre permet de spécifier une connexion existante à une base de données ou vaut NULL si aucune connexion n'a encore été réalisée. Ce constructeur est appelé pour initialiser l'attribut m_etudiantset de type CEtudiantSet de la classe CBDDoc. Pour cet attribut, la valeur par défaut NULL du constructeur est utilisée. Dans ce cas, le constructeur appelle la méthode GetDefaultConnect pour définir la base de données à utiliser pour l'objet CEtudiantSet. 25 26 - La connexion à la base de données La méthode GetDefaultConnect est une méthode virtuelle pure de la classe de base CRecordSet qui doit donc être surchargée dans la classe dérivée CEtudiantSet. Cette méthode retourne une chaîne de caractères qui permet l'identification de la base de données. CString CEtudiantSet::GetDefaultConnect() return _T("ODBC;DSN=Etudiants"); Ici la base de données s'appelle Etudiants et l'accès se fait par une interface de type ODBC. On peut aussi d'autres informations comme le nom de connexion de l'utilisateur de la base et son mot de passe. CString CEtudiantSet::GetDefaultConnect() return _T("ODBC;DSN=Etudiants";UID=Cullot;PWD=secret); 27 L interrogation de la base de données Comme nous l'avons vu précédemment, la classe CEtudiantSet possède un attribut pour chaque champ de la table Etudiant Le type de l'attribut est identique à celui du champ de la table de la base de données. // Field/Param Data //AFX_FIELD(CEtudiantSet, CRecordset) long m_noetudiant; CStringW m_nom; CStringW m_prenom; CStringW m_adresse; long m_age; CStringW m_sexe; //AFX_FIELD 28 L'opération SQL qui renseigne le jeu de lignes i.e. les données membres CEtudiantSet est spécifié dans la méthode GetDefaultSQL(). Attention, il est préférable de conserver toutes les colonnes même si elles ne sont pas utilisées ou bien de les supprimer à l'aide de AppWizard mais sans supprimer les clés primaires. CString CEtudiantSet::GetDefaultSQL() return _T("[Etudiant]"); Ici, toute la table Etudiant de la base de données est sélectionnée. Cela correspond à une requête de la forme : SELECT * FROM Etudiant pour la base de données Etudiants. En fait, la requête est construite à partir de la chaîne retournée par la fonction GetDefaultSQL(). D'autres informations pour décrire les clauses WHERE ou ORDER BY peuvent être ajoutées. 29 Les échanges de données entre la base et le jeu de lignes Les échanges sont gérés grâce à la méthode DoFieldExchange de la classe CEtudiantSet. Cette méthode fonctionne de façon un peu similaire à la méthode DoDataExchange qui assure les échanges entre les contrôles d'une boîte de dialogue et les attributs de la classe associée à la boîte. void CEtudiantSet::DoFieldExchange(CFieldExchange* pfx) //AFX_FIELD_MAP(CEtudiantSet) pfx->setfieldtype(cfieldexchange::outputcolumn); RFX_Long(pFX, _T("[NoEtudiant]"), m_noetudiant); RFX_Text(pFX, _T("[Nom]"), m_nom); RFX_Text(pFX, _T("[Prenom]"), m_prenom); RFX_Text(pFX, _T("[Adresse]"), m_adresse); RFX_Long(pFX, _T("[Age]"), m_age); RFX_Text(pFX, _T("[Sexe]"), m_sexe); //AFX_FIELD_MAP 30 5
Le paramètre pfx est une pointeur sur une objet de type CFieldExchange. Le mode pour de transfert pour les fonctions RFX_ est fixé par la méthode : SetFieldType avec l'appel : pfx->setfieldtype(cfieldexchange::outputcolumn); Ici, il s'agit d'un transfert de la base vers le jeu de lignes. La correspondance entre la valeur d'un attribut de la base et l'attribut de la classe est fait par les macros AFX_. RFX_Long(pFX, _T("[NoEtudiant]"), m_noetudiant); Ici le champs NoEtudiant de la base Etudiants est associé à la variable m_noetudiant de la classe CEtudiantSet. Le type de cette variable est long. 31 Le constructeur de la classe CEtudiantSet initialise les diiférents attributs de la base. CEtudiantSet::CEtudiantSet(CDatabase* pdb) : CRecordset(pdb) //AFX_FIELD_INIT(CEtudiantSet) m_noetudiant = 0; m_nom = _T(""); m_prenom = _T(""); m_adresse = _T(""); m_age = 0; m_sexe = _T(""); m_nfields = 6; //AFX_FIELD_INIT m_ndefaulttype = snapshot; 32 Le «dessin» de la vue est défini à l'aide d'une boîte de dialogue et la vue est liée à un objet du jeu de lignes. Le mécanisme complet d'échange est le suivant : Base de données Etudiants Table : Etudiant NoEtudiant Nom. DoFieldExchange DoDataExchange Classe CEtudiantSet long m_noetudiant CStringW m_nom Boîte de dialogue de la classe CEtudiantView Edit (m_noetudiant). 33 Entête de la classe CEtudiantView : class CEtudiantView : public CRecordView protected: CEtudiantView(); DECLARE_DYNCREATE(CEtudiantView) //AFX_DATA(CEtudiantView) enum IDD = IDD_BD_FORM ; CEtudiantSet* m_pset; //AFX_DATA CBDDoc* GetDocument(); virtual CRecordset* OnGetRecordset(); virtual BOOL PreCreateWindow(CREATESTRUCT& cs); protected: virtual void DoDataExchange(CDataExchange* pdx); virtual void OnInitialUpdate(); DECLARE_MESSAGE_MAP() ; 34 possède un attribut de type pointeur sur CEtudiantSet. Cet attribut "met en œuvre" le lien entre la ligne de données et la vue. De même, un type énuméré est défini : enum IDD = IDD_BD_FORM ; ou IDD_BD_FORM est l'identifiant de la ressource boîte de dialogue de la vue. Cette boîte de dialogue est en partie prédéfinie dans le squelette de l'application proposée, il suffit de compléter cette boîte avec les contrôles d'édition souhaités. 35 Le constructeur de la classe CEtudiantView CEtudiantView::CEtudiantView() : CRecordView(CEtudiantView::IDD) m_pset = NULL; Le constructeur par défaut de la classe CEtudiantView appelle le constructeur avec paramètre de la classe CRecordView avec l'identifiant de la ressource de dialogue qui doit être associée à la vue. Le pointeur sur une ligne est initialisé à NULL 36 6
Initialisation de la vue La valeur du pointeur m_pset est affectée lors de la création de la vue avec l'appel de la méthode OnInitialUpdate(); Elle est affectée à la valeur du pointeur m_etudiantset défini dans la classe CBDDoc. Ce pointeur a été initialisé lors de la création de squelette avec les choix de la base de données et de la table. Les deux pointeurs (celui de CBDDoc et de CEtudiantView) vont référencer le même objet; 37 void CEtudiantView::OnInitialUpdate() m_pset = &GetDocument()->m_etudiantSet; CRecordView::OnInitialUpdate(); GetParentFrame()->RecalcLayout(); ResizeParentToFit(); Les autres instructions assurent l'initialisation CRecordView::OnInitialUpdate(); et le bon dimensionnement de la fenêtre cadre par rapport à la boîte de dialogue. GetParentFrame()->RecalcLayout(); ResizeParentToFit(); 38 Création de la boîte de dialogue de vue Cette étape consiste à compléter la boîte de dialogue en introduisant des contrôles d'édition pour les différents attributs de la classe CEtudiantSet. Forme initiale de la boîte de dialogue proposée : On construit donc la boîte de dialogue souhaitée : 39 40 Pour chaque attribut, un contrôle statique et un contrôle d'édition sont définis. L assistant détermine un nom de variable pour un contrôle d'édition en utilisant le texte du contrôle statique qui le précède. L'ordre est déterminé par la séquence de touches Tab. 41 Liaison entre les contrôles de la boîte et le jeu de lignes Pour que les échanges entre la boîte de dialogue de la vue et le jeu de lignes s'effectuent correctement, il faut que la méthode DoDataExchange de la classe CEtudiantView soit complétée. Il faut définir le lien entre les contrôles d'édition et l'attribut m_pset de la classe CEtudiantView. 42 7
On complète la méthode DoDataExchange : void CEtudiantsView::DoDataExchange(CDataExchange* pdx) CRecordView::DoDataExchange(pDX); //AFX_DATA_MAP(CEtudiantsView) DDX_FieldText(pDX, IDC_NOM, m_pset->m_nom, m_pset); DDX_FieldText(pDX, IDC_PRENOM, m_pset->m_prenom, m_pset); DDX_FieldText(pDX, IDC_ADRESSE, m_pset->m_adresse, m_pset); DDX_FieldText(pDX, IDC_SEXE, m_pset->m_sexe, m_pset); DDX_FieldText(pDX, IDC_NOETUDIANT, m_pset->m_noetudiant, m_pset); DDX_FieldText(pDX, IDC_AGE, m_pset->m_age, m_pset); //AFX_DATA_MAP Test de l application complète L'application développée permet d'accéder à la base de données "Etudiants" par une interface ODBC. Cette application récupère toutes les informations de la table "Etudiant" et permet leur affichage dans une vue spécialisée. La vue dispose d'outils pour faire "défiler" les enregistrements, se placer sur le 1er enregistrement ou le dernier. 43 44 On peut décider d'ajouter d'autres jeux de lignes pour traiter les informations d'autres tables de la base de données. Ajout de la classe CObtientSet Le principe consiste à définir Une nouvelle classe dérivée de CRecordSet, par exemple CObtientSet si on s'intéresse aux épreuves obtenues par un étudiant. Une classe «vue» associée à cette table, pour afficher les informations (aussi dérivée de CRecordView) L'application devra prendre en charge l'affichage des différentes vues. 45 46 Comme précédemment, la classe CObtientSet a été créée class CObtientSet : public CRecordset long m_noetudiantparam; CObtientSet(CDatabase* pdatabase = NULL); DECLARE_DYNAMIC(CObtientSet) //AFX_FIELD(CObtientSet, CRecordset) long m_noetudiant; long m_noepreuve; float m_note; //AFX_FIELD //AFX_VIRTUAL(CObtientSet) virtual CString GetDefaultConnect(); virtual CString GetDefaultSQLvirtual void DoFieldExchange(CFieldExchange* pfx); //AFX_VIRTUAL ; 47 Un attribut de type pointeur sur sur CObtientSet a aussi été ajouté dans la classe cbddoc : CObtientSet m_obtientset; 48 8
Création de la ressource dialogue Puis on associe une classe à cette boîte de dialogue. La classe CObtientView qui dérive de la classe CRecordView. 49 50 Bilan Utilisation des différentes vues Ces deux ensembles d'objet de jeux de lignes : CEtudiantSet et CObtientSet et les vues associées CEtudiantsView et CObtientView permettent de travailler avec les tables «Etudiant» et «Obtient» de la base de données «Etudiants». L'application devra se charger de gérer ces différentes vues. 51 L'exemple présenté montre la lecture d'information dans une base de données l'affichage de ces informations dans des vues. La mise à jour des données de la base nécessite de définir des notions complémentaires comme les transactions, les opérations de mises à jour etc 52 9