IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
logo

FAQ VC++ et MFCConsultez toutes les FAQ

Nombre d'auteurs : 20, nombre de questions : 545, dernière mise à jour : 5 avril 2013  Ajouter une question

 

Cette faq a été réalisée pour répondre aux questions les plus fréquement posées sur le forum Développement Visual C++

Je tiens à souligner que cette faq ne garantit en aucun cas que les informations qu'elle contient sont correctes ; Les auteurs font le maximum, mais l'erreur est humaine. Si vous trouvez une erreur, ou si vous souhaitez devenir redacteur, lisez ceci.

Sur ce, je vous souhaite une bonne lecture. Farscape

SommaireBase de DonnéesODBC (48)
précédent sommaire suivant
 

La prise en charge des bases de données par les MFC fait appel à SQL pour formuler les requêtes et opérations exécutées sur les tables de base de données, par l'intermédiaire d'un ensemble de classes spécialisées.
Lorsque nous faisons appel à SQL dans un programme MFC, nous n'avons généralement pas besoin d'écrire d'instructions SQL complètes, car le squelette ou canevas de base de l'application se charge de construire une instruction et de la fournir au moteur de base de données utilisé.

Mis à jour le 4 avril 2005 Gabrielly

Les requêtes SQL sont transmises sous forme de chaînes. Si le nom d'une table comporte des espaces comme Product ID, la syntaxe SQL exige qu'il soit placé entre guillements comme "Product ID".

Ex1: SELECT "Product ID" FROM Products
Or en C++ les guillements délimitent les chaînes de caractères ce qui prête confusion si nous encadrons le nom des objets de base de données (lignes ou colonnes) par des guillements.
C'est pourquoi, lorsque nous référençons dans l'environnement Visual C++ une table de base de données ou un nom de colonne comprenant des espaces, nous devons remplacer les guillemets par les crochets. En l'ocurrence, écrivons [Product ID].

Ex2: SELECT [Product ID] FROM Products

Mis à jour le 4 avril 2005 Gabrielly

Les MFC abordent les base de données selon deux approches chacune d'elle utilisant son propre jeu de classes MFC. Il s'agit de DAO et ODBC. Mais il y en a de plus récentes comme OLE DB et ADO.NET.

La première approche DAO (Data Acess Objects) propose une interface avec le moteur de base de données Jet, élément logiciel généralisé qui permet de stocker des données dans divers systèmes de gestion de base de données et de les extraires.
Jet est le moteur utilisé par le SGBD Microsoft Access. Chaque fois que vous manipulez une base de données sous Access, c'est en réalité Jet qui fournit l'essentiel du travail.
Jet est optimisé pour l'accès direct aux fichiers de base de données Access (.mdb), mais il vous permet également de vous connecter à toutes base de données prennant en charge ODBC.
Outre Microsoft Acess, Jet vous permet d'accéder à des bases de données comme Oracle, dBase 5, Btrieve 6.0 et FoxPro 2.6 et d'autres encore.

La seconde approche est spécifique à ODBC (Open DataBase Connectivity).
Vous pouvez manier des bases de données de tout format pour lesquelles vous possédez le pilote ODBC approprié et même pour les fichiers .mdb d'Access.

Mis à jour le 4 avril 2005 Gabrielly

ODBC est une interface, indépendante du système, avec un environnement de base de données qui exige un pilote ODBC pour chaque système de base de données à partir duquel vous voulez gérer les données.
ODBC définit un ensemble d'appels de fonctions associées à des opérations de base de données, indépendants du système.
Vous pouvez utiliser une base de données avec ODBC seulement si vous disposez de la DLL qui contient le pilote compatible avec le format de fichier de cette base de données.
L'objectif du pilote est d'assurer l'interface entre le jeu d'appels indépendants du système, pour des opérations de base de données, utilisés dans votre programme, et les spécificités de la mise en oeuvre d'une base de données particulière.

Mis à jour le 4 avril 2005 Gabrielly

ODBC fait appel aux cinq classes suivantes:

CDatabase : Un objet de cette classe représente une connexion à votre base de données, que vous devez établir
avant d'effectuer des opérations sur la base de données. Aucune classe d'espace de travail n'est
utilisée avec une base de données ODBC.

CRecordset : Un objet d'une classe dérivée de cette classe représente le résultat d'une opération SQL SELECT,
Il s'agit du concept mentionné lors de la description de la classe CDaoRecordset

CRecordView : Un objet d'une classe dérivée de cette classe permet d'afficher les informations en cours d'un jeu de lignes associé.
Il s'agit du concept mentionné lors de la description de la classe CDaoRecordView

CFieldExchange : Cette classe permet l'échange de données entre la base de données et un jeu de lignes, comme
pour les bases de données DAO.

CDBException : Les objets de cette classe constituent des exceptions survenant dans les opérations de base de
données ODBC.

Les classes ODBC ressemblent étrangement à un sous-ensemble des classes DAO, ce qui s'explique dans la mesure où l'interface qu'elles proposent est identique à celle des classes DAO équivalentes.
Elles diffèrent par leur processus sous-jacent d'accès à la base de données.

Mis à jour le 4 avril 2005 Gabrielly

Avant de pouvoir utiliser une base de données via ODBC, vous devez l'inscrire dans la base de registres.
Pour ce faire cliquer sur le panneaux de configuration, sélectionner Outils d'administration et enfin Source de données ODBC.

Une fois que la boîte s'affiche à l'écran sélectionner l'onglet DSN utilisateur ( Source de données utilisateur).
Cliquez sur Ajouter... pour ajouter une source de données.

Une autre boîte s'affiche, et vous sélectionner le pilote soit ODBC Microsoft Acess Driver (*.mdb) (c'est un exemple).
Cliquez sur Terminer.

Une autre boite s'affiche à nouveau, et vous tapez le nom de votre source de données. Ce nom sera utilisé pour identifier
la base de données lors de la création de votre application par l'intermédiaire de AppWizard (VC++ 6.0).
Cliquez sur le bouton Sélectionner... pour accéder à la dernière boîte de dialogue où vous pouvez sélectionner le fichier qui représente votre base de donnée.( par exemple un fichier *.mdb)

Enfin vous cliquez sur tous les trois boutons OK et l'enregistrement est terminer.

Vérifier à l'aide de l'explorateur Window que votre fichier(base de données) n'est pas en lecture seule.

Mis à jour le 4 avril 2005 Gabrielly

On exécute l'assistant AppWizard avec l'interface SDI ou MDI qui présente à sa seconde étape des options comme la prise en charge de fichiers avec les options d'affichage de base de données.

La prise en charge des fichiers fait référence à la sérialisation du document, qui n'est généralement pas nécessaire puisque les jeux de lignes de votre application se charge de toutes entrées-sorties nécessaire sur une base de données.

Quant au document, il est en quelque sorte accessoire pour les opérations sur une base de données, car ce sont les objets jeu de lignes et vue de lignes qui se chargent de l'essentiel.
Le rôle principal du document consiste à stocker les jeux de lignes c'est à dire les CRecordset.

On choisit par exemple l'option "Database view without file support" on clique sur Data Source...
et on indique la base de données que l'application va utiliser. L'option ODBC est déjà sélectionnée.
AppWizard fournit automatiquement une classe jeu de lignes et vue de lignes.
L'option "Table" s'applique seulement si vous sélectionnez DAO.

ODBC offre le choix entre deux options "Snapshot" et "Dynaset".

Mis à jour le 4 avril 2005 Gabrielly

En effet, dans la configuration de notre projet ODBC, deux types de jeux de lignes interviennent.

Il s'agit du jeu de lignes instantané (snapshot) et de la feuille de réponse dynamique (dynaset).
D'une manière très générale on peut répondre comme ceci.

Si votre table est en lecture seule alors choisissez l'option snapshot.
Si votre table permet les mises à jours alors choisissez l'option dynaset.

N'oubliez pas que ce sont des conclusions générales mais si vous voulez comprendre le pourquoi de ces conclusions, les questions suivantes développées dans les Q/R qui suivent en explique la raison.
Il s'agit des questions suivantes :

Mis à jour le 4 avril 2005 Gabrielly

Ces mots "jeu de lignes" comme vous avez pu le constater reviennent très souvent.
C'est un objet de la classe dérivée de CRecordset

Voici un exemple de cette classe qui s'appelle "CDBSet" appartenant à un projet SDI nommé "DB".

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class CDBSet : public CRecordset 
{ 
public: 
    CDBSet(CDatabase* pDatabase = NULL); 
    DECLARE_DYNAMIC(CDBSet) 
  
// Field/Param Data 
//{{AFX_FIELD(CDBSet, CRecordset) 
    longm_ProductID; 
    CStringm_ProductName; 
    longm_SupplierID; 
    longm_CategoryID; 
    CStringm_QuantityPerUnit; 
    CStringm_UnitPrice; 
    intm_UnitsInStock; 
    CStringm_UnitsOnOrder; 
    intm_ReorderLevel; 
    BOOLm_Discontinued; 
//}}AFX_FIELD 
  
// Overrides 
// ClassWizard generated virtual function overrides 
//{{AFX_VIRTUAL(CDBSet) 
public: 
    virtual CString GetDefaultConnect();// Default connection string 
    virtual CString GetDefaultSQL(); // default SQL for Recordset 
    virtual void DoFieldExchange(CFieldExchange* pFX);// RFX support 
//}}AFX_VIRTUAL 
  
// Implementation 
#ifdef _DEBUG 
    virtual void AssertValid() const; 
    virtual void Dump(CDumpContext& dc) const; 
#endif 
  
};

Nous savons qu'une table de base de données est constituée des colonnes qui représentent les champs de la table et des lignes qui représentent les enregistrements.
Très souvent et la plupart des cas les colonnes sont beaucoup moins nombreuses que les lignes.
Par exemple une table peut avoir une dizaine de colonnes mais des milliers voir même des centaines de milliers de lignes et d'ailleurs jusqu'au million de lignes et même plus.

Dans un programme MFC avec les bases de données, le programmeur ne voit pas dans son code toutes ces lignes simultanément mais il les voit une par une car son champ de vision de la table est réduit à une ligne de cette table.
Et bien, c'est le cas du jeu de ligne qui représente un et un seul enregistrement de la table.
Cela lui vaut bien son nom de "recordset" celui qui définit un enregistrement.

Voici les champs d'une table attaché au recordset CBDSet :

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
// Field/Param Data 
//{{AFX_FIELD(CDBSet, CRecordset) 
    longm_ProductID; 
    CStringm_ProductName; 
    longm_SupplierID; 
    longm_CategoryID; 
    CStringm_QuantityPerUnit; 
    CStringm_UnitPrice; 
    intm_UnitsInStock; 
    CStringm_UnitsOnOrder; 
    intm_ReorderLevel; 
    BOOLm_Discontinued; 
//}}AFX_FIELD

NB : La table je l'ai crée sur Access et s'appelle Product.

Par conséquent si le programmeur veut voir les autres lignes, il doit se déplacer de ligne en ligne dans la table en utilisant les fonctions membres héritées de CRecordset.

Le jeu de ligne représente un enregistrement de la table, et c'est par lui que se fait l'opération SQL SELECT
Toutes les opérations SQL que l'on fait sur SELECT s'appliquent sur le jeu de ligne.
Si je veux sélectionner quelques champs de ma table, ou je veux trier, ou alors placer des conditions de sélection dans la clause WHERE, c'est le jeu de ligne que j'utilise.
C'est la classe MFC ODBC préparée à cela.
Voir les classes ODBC dans les Q/R plus haut.

Mis à jour le 4 avril 2005 Gabrielly

NB: Pour mieux comprendre les différences entre snapshot et dynaset je vous prie de vous reporter à la question  Qu'est-ce qu'un jeu de lignes ?

Votre objet jeu de ligne snapshot vous fournit le résultat d'une opération SELECT sur la base de données.
Dans le cas d'un jeu de lignes snapshot, la requête est exécutée une seule fois et le résultat est stocké dans la mémoire.
Votre objet jeu de ligne peut ensuite rendre disponible n'importe quelle ligne de la table issue de la requête.
Par conséquent un snapshot est principalement statique par nature.
Aucune modification apportée à la base de données à la suite d'une mise à jour par d'autres utilisateurs n'est répercutée dans les données que vous obtenez à l'aide de votre jeu de lignes snapshot.
Si vous voulez voir ces modifications, vous devez réexécuter l'instruction SELECT.
Le choix entre DAO et ODBC détermine une autre fonctionnalité des jeux de ligne snaphot.
Votre programme ne peut pas modifier un jeu de ligne snapshot DAO: il est en lecture seule.
Un snapshot ODBC peut être en lecture seule ou modifiable .
Un snapshot modifiable écrit toutes les modifications apportées à la table dans la base de données sous-jacente, et votre programme peut détecter les changements.
D'autres programmes comportant un snapshot de la base de données ne détectent pas cependant les modifications tant qu'ils n'interrogent pas de nouveau la base.

Mis à jour le 4 avril 2005 Gabrielly

NB: Pour mieux comprendre les différences entre snapshot et dynaset je vous prie de vous reporter à la question  Qu'est-ce qu'un jeu de lignes ?

Votre objet jeu de lignes dynaset rafraîchit automatiquement la ligne en cours de la base de données lorsque que vous passez d'une ligne à une autre de la table créée par la requête du jeu de lignes.
En conséquence, la ligne disponible dans le jeu de lignes reflète l'état de la base de données lors de l'accès à la ligne, non lors de la première ouverture du jeu de lignes.
Le rafraîchissement survient uniquement lorsque votre objet jeu de lignes accède à la ligne.
Si un autre utilisateur modifie les données de la ligne en cours, ce changement est pris en compte dans votre objet jeu de lignes uniquement lorsque vous passez à une autre ligne puis revenez vers la ligne initiale.
Un jeu de ligne dynaset génère le contenu de chaque ligne de façon dynamique, au moyen d'un index désignant les tables de base de données concernées.

Mis à jour le 4 avril 2005 Gabrielly

Après avoir lu les définitions sur un jeu de ligne snapshot et dynaset. Nous pouvons affirmer ceci:

Pour un jeu de ligne snapshot, l'utilisateur de la base de données se rendra compte qu'une table a été modifiée lorsqu'il reprend son instruction SELECT sur la table à l'aide de son snapshot c'est à dire son recordset.
Ainsi entre la première ouverture et la reprise de l'instruction SELECT, d'autres utilisateurs peuvent modifier la table à l'insu du premier utilisateur et donc tant qu'il n'a pas repris l'instruction SELECT il croit toujours que les données de sa table sont saines alors qu'elles sont déjà altérées.

Pour un jeu de ligne dynaset, l'utilisateur se rendra compte de toutes modifications sur une table de la base de données lorsqu'il se déplace simplement d'une ligne à une autre de la table sans qu'il n'ait besoin de réexécuter l'instruction SELECT car le dynaset met automatiquement à jour dès qu'il accède à une ligne de la table.
C'est pourquoi il est dit dynamique et l'utilisateur peut se rendre compte des changements qui se produisent dans son programme un peu plus tôt que celui qui utilise un snapshot.

Par conséquent un snapshot convient pour les tables en lecture seule où aucun utilisateur ne modifie la table ou convient alors dans les situations où l'utilisateur est seul à manipuler la table c'est à dire dans un environnement non client/serveur.
Et un dynaset convient pour les tables qui acceptent les mise à jours et dans le cas où plusieurs utilisateurs peuvent interroger la base sur une même table cas d'un environnement client/serveur.

Mis à jour le 4 avril 2005 Gabrielly

Pour les jeux de lignes snapshot on peut y associer plusieurs tables. Cela équivaut à une opération de jointure des tables.
Tandis que pour les jeux de lignes dynaset on y associe qu'une seule table car les classes de base de données des MFC ne prennent pas en charge la mise à jour de jeux de lignes impliquant la liaison de plusieurs tables.
Si nous revenons à la configuration d'un projet ODBC, en cliquant sur OK, l'assistant affiche toutes les tables de la base de données.
On peut faire une sélection unique ou multiple dans le cas d'un snapshot mais une sélection unique dans le cas d'un dynaset.

Pour le CDBSet j'ai réalisé une sélection unique et j'ai choisit un jeu de ligne snapshot.
On peut le voir dans le constructeur

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
CDBSet::CDBSet(CDatabase* pdb) 
: CRecordset(pdb) 
{ 
//{{AFX_FIELD_INIT(CDBSet) 
m_ProductID = 0; 
m_ProductName = _T(""); 
m_SupplierID = 0; 
m_CategoryID = 0; 
m_QuantityPerUnit = _T(""); 
m_UnitPrice = _T(""); 
m_UnitsInStock = 0; 
m_UnitsOnOrder = _T(""); 
m_ReorderLevel = 0; 
m_Discontinued = FALSE; 
m_nFields = 10; 
//}}AFX_FIELD_INIT 
m_nDefaultType = snapshot;       //  here 
}

Mis à jour le 4 avril 2005 Gabrielly

Voici la déclaration de cette fonction dans CDBSet

Code c++ : Sélectionner tout
virtual CString GetDefaultConnect();// Default connection string
Elle est virtuelle!!! Voici son implémentation

Code c++ : Sélectionner tout
1
2
3
4
CString CDBSet::GetDefaultConnect() 
{ 
return _T("ODBC;DSN=Gestion de stock"); 
}
Elle est courte!!! Rappelez vous de la classe document ici chez moi CDBDoc Voici la déclaration dans CDBDoc

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include "DBSet.h" 
  
  
class CDBDoc : public CDocument 
{ 
protected: // create from serialization only 
CDBDoc(); 
DECLARE_DYNCREATE(CDBDoc) 
  
// Attributes 
public: 
CDBSet m_dBSet; 
  
//... 
};
Lorsque le programme démarre, l'objet document est construit et l'objet m_dBSet aussi.

Voici la déclaration de son construteur

Code c++ : Sélectionner tout
CDBSet(CDatabase* pDatabase = NULL);
Et donc si le jeu de ligne m_dBSet est construit avec un argument NULL dans son constructeur, ce qui est le cas, alors la charpente d'application crée pour nous le CDatabase qui représente notre connexion à notre base de données et appelle la fonction virtuelle GetDefaultConnect() qui retourne une chaîne intéressante.
Ce qui est encore plus intéressant c'est qu'un CRecordset est toujours associé à un CDatabase car il représente une connexion à la base de données.

NB: Vous n'avez pas à appeler GetDefaultConnect, c'est le canevas de l'application qui le fait pour vous.
Vous n'avez qu'à définir la chaîne.

soit "ODBC;DSN=Gestion de stock" où ODBC représente l'interface, et Gestion de stock le DSN que j'ai fournit à l'administrateur ODBC dans Panneaux de configuration.

C'est au moyen de cette chaîne que l'on se connecte à notre base de données car la charpente s'en sert comme identification de notre base de données.

Si on veux utiliser un ID utilisateur et un password on écrit

Code c++ : Sélectionner tout
1
2
3
4
CString CDBSet::GetDefaultConnect() 
{ 
return _T("ODBC;DSN=Gestion de stock;UID=Gabrielly;PWD=Angel"); 
}
Si l'on veut ouvrir une boite de dialogue pour se connecter on écrit;

Code c++ : Sélectionner tout
return _T("ODBC;");
Voir aussi la Q/R "Comment récupérér une connexion à une base de données."

Mis à jour le 4 avril 2005 Gabrielly

Cette fonction retourne un CString qui représente la requête SELECT par défaut appliqué au jeu de ligne.
Déclaration dans CDBSet

Code c++ : Sélectionner tout
virtual CString GetDefaultSQL(); // default SQL for Recordset

Implémentation :

Code c++ : Sélectionner tout
1
2
3
4
CString CDBSet::GetDefaultSQL() 
{ 
    return _T("[Product]");    // équivalent à SELECT * FROM Product 
}

N'oubliez pas les MFC transmettent les requêtes sous forme de chaines et fabrique la syntaxe SQL correcte qu'il fournit au moteur de base de données.
Ce qui montre que vous n'avez pas besoin d'écrire une instruction SQL complète car le canevas de l'application s'en charge.

Si le jeu de ligne est associer à deux tables on a :

Code c++ : Sélectionner tout
1
2
3
4
CString CMyRecordset::GetDefaultSQL() 
{ 
    return _T("[Table1], [Table2]");    // équivalent à SELECT * FROM Table1, Table2   
}

Comme résultat, c'est une jointure des deux tables
NB : Ici aussi vous n'avez pas à appeler GetDefaultSQL, c'est le travail de la charpente d'application.

Mis à jour le 4 avril 2005 Gabrielly

Le rôle de cette fonction est d'assurer l'échange de données entre la base de données et le jeu de lignes.
Voici un exemple de code :

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void CDBSet::DoFieldExchange(CFieldExchange* pFX) 
{ 
    //{{AFX_FIELD_MAP(CDBSet) 
    pFX->SetFieldType(CFieldExchange::outputColumn); 
    RFX_Long(pFX, _T("[ProductID]"), m_ProductID); 
    RFX_Text(pFX, _T("[ProductName]"), m_ProductName); 
    RFX_Long(pFX, _T("[SupplierID]"), m_SupplierID); 
    RFX_Long(pFX, _T("[CategoryID]"), m_CategoryID); 
    RFX_Text(pFX, _T("[QuantityPerUnit]"), m_QuantityPerUnit); 
    RFX_Text(pFX, _T("[UnitPrice]"), m_UnitPrice); 
    RFX_Int(pFX, _T("[UnitsInStock]"), m_UnitsInStock); 
    RFX_Text(pFX, _T("[UnitsOnOrder]"), m_UnitsOnOrder); 
    RFX_Int(pFX, _T("[ReorderLevel]"), m_ReorderLevel); 
    RFX_Bool(pFX, _T("[Discontinued]"), m_Discontinued); 
    //}}AFX_FIELD_MAP 
}

Elle fonctionne exactement de la même manière que DoDataExchange avec les boites de dialogues et les vues.
NB: Là aussi c'est la charpente qui fait automatiquement le stockage et l'extraction de données de la base de données.
Le mode CFieldExchange::outputColumn indique que les données doivent être échangées entre la colonne de la base de données et l'argument correspondant précisé dans chacun des appels suivants de fonctions RFX_()

Mis à jour le 4 avril 2005 Gabrielly

C'est un objet de la classe dérivée de CRecordView. En d'autres termes il s'agit d'un objet représentant une sorte de formulaire et d'ailleurs CRecordView dérive de CFormView.

Voici un exemple de cette classe qui s'appelle "CDBView" appartenant à un projet SDI nommé "DB".

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class CDBSet; 
  
class CDBView : public CRecordView 
{ 
protected: // create from serialization only 
    CDBView(); 
    DECLARE_DYNCREATE(CDBView) 
  
public: 
    //{{AFX_DATA(CDBView) 
    enum{ IDD = IDD_DB_FORM }; 
    CDBSet* m_pSet; 
    // NOTE: the ClassWizard will add data members here 
    //}}AFX_DATA 
  
    // Attributes 
public: 
    CDBDoc* GetDocument(); 
  
    // Operations 
public: 
  
    // Overrides 
    // ClassWizard generated virtual function overrides 
    //{{AFX_VIRTUAL(CDBView) 
public: 
    virtual CRecordset* OnGetRecordset(); 
    virtual BOOL PreCreateWindow(CREATESTRUCT& cs); 
    protected: 
    virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support 
    virtual void OnInitialUpdate(); // called first time after construct 
    virtual BOOL OnPreparePrinting(CPrintInfo* pInfo); 
    virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo); 
    virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo); 
    //}}AFX_VIRTUAL 
  
// Implementation 
public: 
    virtual ~CDBView(); 
#ifdef _DEBUG 
    virtual void AssertValid() const; 
    virtual void Dump(CDumpContext& dc) const; 
#endif 
  
protected: 
  
    // Generated message map functions 
protected: 
    //{{AFX_MSG(CDBView) 
    // NOTE - the ClassWizard will add and remove member functions here. 
    //    DO NOT EDIT what you see in these blocks of generated code ! 
    //}}AFX_MSG 
    DECLARE_MESSAGE_MAP() 
};

L'objectif d'un objet vue de ligne consiste à afficher les informations d'un objet jeu de ligne dans la fenêtre de l'application.
Autrement dit il affiche les informations de la table enregistrement par enregistrement car il les récupère du recordset et donc ligne par ligne.
Ce qui est à remarquer est qu'à un objet vue de lignes, on associe toujours un objet jeu de ligne et bien sur le formulaire d'ID IDD_DB_FORM.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CDBSet; 
  
class CDBView : public CRecordView 
{ 
protected: // create from serialization only 
    CDBView(); 
    DECLARE_DYNCREATE(CDBView) 
  
public: 
    //{{AFX_DATA(CDBView) 
    enum{ IDD = IDD_DB_FORM }; 
    CDBSet* m_pSet; 
    // NOTE: the ClassWizard will add data members here 
    //}}AFX_DATA 
  
    //... 
};

m_pSet est le membre de CDBView qui stocke l'adresse de l'objet jeu de ligne CDBSet.
La charpente fournit toujours ce pointeur pour associer une vue de lignes à un jeu de lignes.
Autrement dit sans jeu de lignes, la vue de lignes est sans intérêt.

Mis à jour le 4 avril 2005 Gabrielly

D'abord il faut placer les contrôles sur le formulaire. Soient des contrôles static et des edits.
Et à l'aide de classwizard on fait l'associer directement avec les membres de l'objet jeu de lignes m_pSet de CDBView en écrivant m_pSet->.
Voici DodataExchange après associations

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
void CDBView::DoDataExchange(CDataExchange* pDX) 
{ 
    CRecordView::DoDataExchange(pDX); 
    //{{AFX_DATA_MAP(CDBView) 
    DDX_FieldText(pDX, IDC_CATEGORYID, m_pSet->m_CategoryID, m_pSet); 
    DDX_FieldText(pDX, IDC_PRODUCTID, m_pSet->m_ProductID, m_pSet); 
    DDX_FieldText(pDX, IDC_PRODUCTNAME, m_pSet->m_ProductName, m_pSet); 
    DDX_FieldText(pDX, IDC_UNITPRICE, m_pSet->m_UnitPrice, m_pSet); 
    DDX_FieldText(pDX, IDC_UNITSINSTOCK, m_pSet->m_UnitsInStock, m_pSet); 
    DDX_FieldText(pDX, IDC_UNITSONORDER, m_pSet->m_UnitsOnOrder, m_pSet); 
    //}}AFX_DATA_MAP 
}

Notre vue de lignes est prêt et est directement connecté aux champs de la table Product par le CDBSet.

Mis à jour le 4 avril 2005 Gabrielly

1. A la source nous avons la base de données, avec une table Product.
2. Ensuite le jeu de lignes CDBSet qui est positionné sur une ligne de la table Product et est déclaré dans CDBDoc
3. A l'aide de CDBSet::DoFieldExchange les échanges entre le jeu de lignes et la table sont assurés.
4. A l'autre extrémité j'ai une vue de lignes CDBView qui contient un pointeur m_pSet sur CDBSet.
5. A l'aide de CDBView::DoDataExchange les échanges entre les champs de la table Product par CDBSet et les contrôles de CDBView sont assurés.
6. Et avec CDBView::OnInitialUpdate les données sont affichées.

Mis à jour le 4 avril 2005 Gabrielly

Pour associer l'objet vue de lignes CDBOrderView au jeu de lignes CDBOrderSet, il faut d'abord créer une ressource boîte de dialogue comme IDD_ORDERS_FORM avec les options de style Child et celui de la bordure None (pour notre application SDI DB).

À l'aide de ClassWizard on crée la classe qui dérive de CRecordView. En cliquant sur OK, l'assistant vous demande de choisir l'objet jeu de lignes à associer à la vue.
Cette boîte doit vous proposer CDBOrderSet; à défaut sélectionnez cette option dans la liste et cliquez sur le bouton OK.

Vérifiez le destructeur de la classe CDBOrderView, que ClassWizard a mis en œuvre au moyen du code suivant :

Code C++ : Sélectionner tout
1
2
3
4
5
CDBOrderView::~CDBOrderView() 
{ 
    //if (m_pSet) 
    //    delete m_pSet; 
}

Le membre m_pSet désigne un pointeur sur CDBOrderSet qui est déclaré dans le document et donc nous pouvons supprimer sa destruction dans la vue comme nous l'avons fait.

Mis à jour le 19 septembre 2005 Gabrielly

Voici un code très court de CDBView::OnInitialUpdate pour notre projet SDI nommé DB connecté à la table Product

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
void CDBView::OnInitialUpdate() 
{ 
      // On récupère le jeu de lignes déjà construit  
     // et connecté à la base de données 
  
     m_pSet = &GetDocument()->m_dBSet; 
     CRecordView::OnInitialUpdate(); 
     GetParentFrame()->RecalcLayout(); 
     ResizeParentToFit(); 
}
1. A partir du document on récupère le jeu de lignes déjà construit et connecté à la base de données (voir Q/R plus haut)
2. On l'associe au jeu de lignes par son membre m_pSet
3. On appelle CRecordView::OnInitialUpdate() qui ouvre le jeu de lignes pour transférer les données de la base vers le jeu
4. On redimensionne le formulaire

A ce niveau on peut tester l'application. Vérifier que votre table a des données une fois créée.

Mis à jour le 4 avril 2005 Gabrielly

Dans le contexte MFC/ODBC; Pour trier sur un champ d'une table, il suffit de définir le membre CRecordset::m_strSort comme une chaîne contenant le nom de colonne sur lequel nous voulons effectuer le tri.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
void CDBView::OnInitialUpdate() 
{ 
    m_pSet = &GetDocument()->m_dBSet; 
    m_pSet->m_strSort = "[ProductID]";// trie suivant la colonne ProductID de la table Product 
    CRecordView::OnInitialUpdate(); 
    GetParentFrame()->RecalcLayout(); 
    ResizeParentToFit(); 
}

Dans le jeu de lignes, nous avons attribué à m_strSort le nom de la colonne ProductID.
Les crochets sont utiles même si un nom est dépourvu d'espace, car ils différencient les chaînes contenant ces noms d'autres chaînes; pour vous permettre de sélectionner immédiatement le nom de colonnes.

Pour effectuer le tri sur plusieurs colonnes, indiquez le nom de chacune d'elles dans la chaîne, en les séparant par une virgule.

Code C++ : Sélectionner tout
m_strSort = "[ProductID], [ProductName]"

Mis à jour le 19 septembre 2005 Gabrielly

Un seul niveau des transactions est pris en charge ; vous ne pouvez pas imbriquer les transactions.
Pour exécuter une transaction dans un jeu d'enregistrements
Appelez la fonction membre BeginTrans de l'objet CDatabase.
Appelez aussi souvent que nécessaire les fonctions membres AddNew/Update, Edit/Update et Delete d'un ou de plusieurs objets de jeu d'enregistrements de la même base de données.
Enfin, appelez la fonction membre CommitTrans de l'objet CDatabase.
Si une erreur se produit pendant l'une des mises à jour ou que vous décidiez d'annuler les modifications, appelez la fonction membre Rollback.

Voici un exemple qui ilustre deux jeux d'enregistrements pour supprimer l'inscription d'un étudiant d'une base de données gérant les inscriptions d'une école.
L'étudiant est ainsi retiré de tous les cours pour lesquels il était inscrit.
Les appels Delete doivent réussir dans les deux jeux d'enregistrements, ce qui implique l'utilisation d'une transaction.
Cet exemple suppose l'existence de m_dbStudentReg, variable membre de type CDatabase déjà connectée à la source de données, et des classes de jeux d'enregistrements CEnrollmentSet et CStudentSet. La variable strStudentID contient une valeur fournie par l'utilisateur.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
  
BOOL CEnrollDoc::RemoveStudent( CString strStudentID ) 
{ 
    // la fonction recoit l'ID de l'étudiant à supprimer 
  
    if ( !m_dbStudentReg.BeginTrans( ) ) // On démarre la transaction 
        return FALSE; 
  
    // Je constuis le recordset associé à une table qui reprend tous les cours des étudiants 
    CEnrollmentSet rsEnrollmentSet(&m_dbStudentReg); 
  
    // je prépare le filtre  
    rsEnrollmentSet.m_strFilter = "StudentID = " + strStudentID; 
  
    // j'exécute la requête suivant le filtre. Le recordset est modifiable 
    // Cette requête me donne tous les cours associés à cet étudiant 
    if ( !rsEnrollmentSet.Open(CRecordset::dynaset) ) 
        return FALSE; 
  
    // Je construis le recordset associé à une table qui reprend tous les étudiants enrollés 
    CStudentSet rsStudentSet(&m_dbStudentReg); 
  
    // je prépare le filtre 
    rsStudentSet.m_strFilter = "StudentID = " + strStudentID; 
  
    // j'exécute la requête suivant le filtre. Le recordset est modifiable 
    // Cette requête me donne en principe un seul enregistrement qui reprend 
    // les informations de l'étudiant à supprimer 
    if ( !rsStudentSet.Open(CRecordset::dynaset) ) 
        return FALSE; 
  
    TRY  // Biensûr un bloc try-catch est nécessaire pour les exceptions éventuelles 
    { 
        while ( !rsEnrollmentSet.IsEOF( ) ) // je défile en avant 
        { 
            rsEnrollmentSet.Delete( ); 
            rsEnrollmentSet.MoveNext( ); 
        }  
        // je viens de supprimer tous les cours relatifs à l'étudiant 
        // Je supprime également l'étudiant de la table des étudiants enrollés 
        rsStudentSet.Delete( ); 
  
        // enfin je valide toutes les opérations effectués pour affecter la source de données 
        m_dbStudentReg.CommitTrans(); 
        // après le commit, 
        // j'aurais pu appeler CDatabase::GetCursorCommitBehavior() 
        // pour tester l'effet de la transaction sur mes recordsets 
    } 
  
    CATCH_ALL(e) 
    { 
        // tiens!!! j'ai échoué.  
        // Je supprime donc la transaction 
        m_dbStudentReg.Rollback(); 
        // après le rollback, 
        // j'aurais pu appeler CDatabase::GetCursorRollbackBehavior() 
        // pour tester l'effet de la transaction sur mes recordsets 
        return FALSE; 
    } 
    END_CATCH_ALL 
  
    rsEnrollmentSet.Close();  // bon je ferme mes recordset 
    rsStudentSet.Close();  // le destructeur pouvait fair ça à ma place 
  
    return TRUE; 
}
Remarque Le fait d'appeler une nouvelle fois BeginTrans sans appeler CommitTrans ou Rollback constitue une erreur.
Car tentative d'imbrication des transaction.

Mis à jour le 19 septembre 2005 Gabrielly

Une transaction constitue une façon de regrouper une série de mises à jour dans une source de données de manière à ce qu'elles soient toutes validées en même temps, ou qu'aucune ne soit validée si vous annulez la transaction.
Si vous n'utilisez pas une transaction, les modifications apportées à la source de données sont validées automatiquement au lieu d'être validées sur demande.
Tous les pilotes de base de données ODBC prennent en charge les transactions.
Appelez la fonction membre CanTransact de votre objet CDatabase ou CRecordset pour savoir si votre pilote prend en charge les transactions pour une base de données particulière.
Sachez cependant que CanTransact ne vous indique pas si la source de données assure une prise en charge complète des transactions.
Vous devez également appeler CDatabase::GetCursorCommitBehavior et CDatabase::GetCursorRollbackBehavior après CommitTrans et Rollback pour vérifier l'effet de la transaction sur l'objet CRecordset ouvert.
Les appels adressés aux fonctions membres AddNew et Edit d'un objet CRecordset affectent immédiatement la source de données lorsque vous appelez Update.
Les appels Delete entrent également en vigueur immédiatement.
En revanche, vous pouvez utiliser une transaction composée de plusieurs appels à AddNew, Edit, Update et Delete, qui sont exécutés mais pas validés tant que vous n'avez pas explicitement appelé CommitTrans.
En établissant une transaction, vous pouvez exécuter une série d'appels de ce type tout en conservant la possibilité de les annuler.
Si une ressource indispensable n'est pas disponible ou que tout autre facteur empêche l'exécution de l'intégralité de la transaction, vous pouvez annuler cette transaction au lieu de la valider. Dans ce cas, aucune des modifications appartenant à la transaction n'affecte la source de données.
Les transactions sont particulièrement utiles lorsque vous devez mettre à jour plusieurs enregistrements à la fois.
Dans ce cas, vous devez éviter qu'une transaction soit seulement à moitié terminée, ce qui peut se produire si une exception est générée avant l'exécution de la dernière mise à jour.
Le groupement de ces mises à jour dans une transaction permet une récupération (annulation) après modifications et fait repasser les enregistrements dans l'état qu'ils avaient avant la transaction.
Un seul niveau des transactions est pris en charge.
Il est impossible d'imbriquer des transactions et une transaction ne peut pas englober plusieurs objets de base de données.

Mis à jour le 19 septembre 2005 Gabrielly

Pour lancer une nouvelle requête sur un objet jeu d'enregistrements Appelez la fonction membre Requery de l'objet.
Autre solution, fermez le jeu d'enregistrements original et réouvrez-le.
Dans les deux cas, le nouveau jeu d'enregistrements représente l'état courant de la source de données.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
  
//... 
if( !rsStudent.Open( ) )  // Est-il ouvert? 
    return FALSE; 
  
// Ok, je prépare à nouveau mes trie et mes filtres. 
// rsStudent.Open(); // non non non, le recordset est déjà ouvert!!! 
rsStudent.Requery(); // c'est comme ça qu'on relance une nouvelle fois 
//...

Mis à jour le 19 septembre 2005 Gabrielly

C'est en procédant par des verrouillages des enregistrements.
Lorsque vous utilisez un jeu d'enregistrements pour modifier un enregistrement de la source de données,
votre application peut verrouiller l'enregistrement afin qu'aucun autre utilisateur ne puisse le modifier en même temps.
Comme l'état d'un enregistrement modifié simultanément par deux utilisateurs est imprévisible,
il importe que le système puisse garantir que deux utilisateurs ne peuvent pas modifier simultanément un même enregistrement.

Il existe deux types de verrouillage supportés par la classe CRecordset.
-le verrouillage optimiste (mode par défaut)
-le verrouillage pessimiste.

Le verrouillage optimiste verrouille l'enregistrement de la source de données uniquement pendant l'appel de la fonction Update.
Si vous utilisez le verrouillage optimiste dans un environnement multi-utilisateur, l'application doit gérer une condition d'échec de la fonction Update.
Le verrouillage pessimiste verrouille l'enregistrement dès que vous appelez Edit et ne le libère pas tant que vous n'avez pas appelé Update
(les échecs sont signalés via CDBException, et non par la valeur FALSE retournée par Update).
Le verrouillage pessimiste pénalise potentiellement les performances des autres utilisateurs,
car les accès simultanés au même enregistrement doivent attendre la fin complète du processus Update de votre application.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  
//... 
rsStudent.SetLockingMode(CRecordset::optimistic);  // je choisit ce mode de vérouillage car je suis optimiste  
rsStudent.Edit();   // le vérouillage n'a pas encore commencé 
  
// j'édite mes quelques champs  
rsStudent.m_strStreet = strNewStreet; 
rsStudent.m_strCity = strNewCity; 
rsStudent.m_strState = strNewState; 
rsStudent.m_strPostalCode = strNewPostalCode; 
  
if( !rsStudent.Update() )  // bon, je vérouille pour garantir que je suis seul à modifier cet enregistrement 
{ 
    AfxMessageBox( "Impossible de mettre à jours." ); 
    return FALSE; 
} 
// le vérouillage est libéré après l'appel à Update.  
// d'autres utilisateurs peuvent maintenant modifier cet enregistrement.
Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  
//... 
rsStudent.SetLockingMode(CRecordset::pessimistic);  // je suis vraiment pessimiste!!! 
rsStudent.Edit();   // je préfère commencer le vérouillage ici 
  
// j'édite mes quelques champs  
rsStudent.m_strStreet = strNewStreet; 
rsStudent.m_strCity = strNewCity; 
rsStudent.m_strState = strNewState; 
rsStudent.m_strPostalCode = strNewPostalCode; 
//Hé oui, jusqu'à Update je suis vraiment le seul utilisateur à modifier cet enregistrement 
if( !rsStudent.Update() ) 
{ 
    AfxMessageBox( "Impossible de mettre à jours." ); 
    return FALSE; 
} 
// le vérouillage est libéré après l'appel à Update.  
// d'autres utilisateurs peuvent maintenant modifier cet enregistrement. 
// Excusez-moi les autres users si les appels entre Edit et Update me prennent 5 secondes de temps processeur. 
// Car je suis vraiment pessimiste et je veux que personne n'y accède pendant que je modifie.
En spécifiant CRecordset::pessimistic ou CRecordset::optimistic à la fonction membre SetLockingMode Le nouveau mode de verrouillage demeure effectif tant que vous ne le modifiez pas à nouveau ou que le jeu d'enregistrements n'est pas fermé.
Remarque: Très peu de pilotes ODBC prennent actuellement en charge le verrouillage pessimiste.

Mis à jour le 19 septembre 2005 Gabrielly

Vous pouvez supprimer des enregistrements si la fonction membre CanUpdate retourne une valeur différente de zéro.
Assurez-vous que le jeu d'enregistrements est modifiable.
Accédez à l'enregistrement à mettre à jour.
Appelez la fonction membre Delete de l'objet jeu d'enregistrements.
Delete marque immédiatement l'enregistrement comme supprimé, à la fois dans le jeu d'enregistrements et dans la source de données.
Contrairement à AddNew et Edit, Delete n'appelle pas la fonction Update.
Accédez par défilement à un autre enregistrement.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
  
if( !rsStudent.Open( ) )  // Est-il ouvert? 
    return FALSE; 
  
if( !rsStudent.CanUpdate() )  //Puisse-je modifier?  
    return FALSE; 
  
rsStudent.SetAbsolutePosition(3);   // j'accède à l'enregistrement à supprimer 
rsStudent.Delete();  // je le détruit 
  
if(rsStudent.IsDelete()) // Est-il vraiment détruit?   
   rsStudent.MoveNext();  // bon, je me positionne sur l'enregistrement suivant

Mis à jour le 19 septembre 2005 Gabrielly

Vous pouvez modifier des enregistrements existants si la fonction membre CanUpdate retourne une valeur différente de zéro.
Assurez-vous que le jeu d'enregistrements est modifiable.
Accédez à l'enregistrement à mettre à jour.
Appelez la fonction membre Edit de l'objet jeu d'enregistrements.
Edit prépare le jeu d'enregistrements de telle sorte qu'il fasse office de tampon d'édition.
Tous les membres de données de type champ sont marqués de sorte que le jeu d'enregistrements peut savoir, par la suite, s'ils ont été modifiés.
Les nouvelles valeurs des membres de données de type champ modifiés sont écrites dans la source de données quand vous appelez Update.
Définissez les valeurs des membres de données de type champ du nouvel enregistrement.
Affectez des valeurs aux membres de données de type champ. Les membres auxquels ne sont pas attribuées de valeurs demeurent inchangés.
Appelez la fonction membre Update de l'objet jeu d'enregistrements.
Update termine la modification en écrivant l'enregistrement modifié dans la source de données.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  
if( !rsStudent.Open( ) )  // Est-il ouvert? 
    return FALSE; 
  
if( !rsStudent.CanUpdate() )  //Puisse-je modifier?  
    return FALSE; 
  
rsStudent.SetAbsolutePosition(3);   // j'accède à l'enregistrement à mettre à jours 
rsStudent.Edit( );   // bon, je prépare le tampon d'édition 
  
// j'édite mes quelques champs  
rsStudent.m_strStreet = strNewStreet; 
rsStudent.m_strCity = strNewCity; 
rsStudent.m_strState = strNewState; 
rsStudent.m_strPostalCode = strNewPostalCode; 
  
if( !rsStudent.Update() )  // je n'oublie de mettre à jours. C'est la validation 
{ 
    AfxMessageBox( "Impossible de mettre à jours." ); 
    return FALSE; 
}
Conseil:
Pour annuler un appel de AddNew ou Edit, effectuez simplement un nouvel appel de AddNew ou Edit ou appelez Move avec le paramètre AFX_MOVE_REFRESH.
Les membres de données sont réinitialisés à leurs valeurs précédentes et vous demeurez en mode Edit ou Add.

Attention:
Lorsque vous vous préparez à modifier un jeu d'enregistrements en appelant Update,
prenez soin que le jeu d'enregistrements comporte toutes les colonnes composant la clé primaire de la table (ou toutes les colonnes d'un index unique de la table).
Dans certains cas, l'infrastructure ne peut utiliser que les colonnes sélectionnées dans le jeu d'enregistrements pour identifier l'enregistrement de la table à modifier.
Sans toutes les colonnes nécessaires, plusieurs enregistrements risquent d'être modifiés dans la table et l'intégrité référentielle de celle-ci peut s'en trouver altérée.
Dans ce cas, l'infrastructure lève une exception quand vous appelez Update.
De préférence placez le code dans les blocs try-catch pour récupérer d'éventuelles exceptions.

Mis à jour le 19 septembre 2005 Gabrielly

Vous pouvez ajouter de nouveaux enregistrements à un jeu d'enregistrements si sa fonction membre CanAppend retourne une valeur différente de zéro.
Assurez-vous que des enregistrements peuvent être ajoutés au jeu d'enregistrements.
Appelez la fonction membre AddNew de l'objet jeu d'enregistrements.
AddNew prépare le jeu d'enregistrements de telle sorte qu'il fasse office de tampon d'édition.
Tous les membres de données de type champ sont définis avec la valeur particulière Null et marqués comme non modifiés,
de sorte que seules les valeurs modifiées (« dirty ») sont écrites dans la source de données lorsque vous appelez Update.
Définissez les valeurs des membres de données de type champ du nouvel enregistrement.
Affectez des valeurs aux membres de données de type champ.
Ceux qui ne reçoivent pas de valeurs ne sont pas écrits dans la source de données.
Appelez la fonction membre Update de l'objet jeu d'enregistrements.
Update termine l'ajout en écrivant le nouvel enregistrement dans la source de données.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  
if( !rsStudent.Open( ) )  // Est-il ouvert? 
    return FALSE; 
  
if( !rsStudent.CanAppend() )  //Puisse-je ajouter?  
    return FALSE;   
  
rsStudent.AddNew();   // bon, je prépare le tampon d'édition 
  
// j'édite mes quelques champs        
rsStudent.m_strName = strName; 
rsStudent.m_strCity = strCity; 
rsStudent.m_strStreet = strStreet; 
  
if( !rsStudent.Update( ) )  // je n'oublie de mettre à jours. C'est la validation 
{ 
    AfxMessageBox( "Impossible de mettre à jours" ); 
    return FALSE; 
}
Conseil: Pour annuler un appel de AddNew ou Edit, effectuez simplement un nouvel appel de AddNew ou Edit ou appelez Move avec le paramètre AFX_MOVE_REFRESH.
Les membres de données sont réinitialisés à leurs valeurs précédentes et vous demeurez en mode Edit ou Add.

Attention:
Lorsque vous vous préparez à modifier un jeu d'enregistrements en appelant Update,
prenez soin que le jeu d'enregistrements comporte toutes les colonnes composant la clé primaire de la table (ou toutes les colonnes d'un index unique de la table).
Dans certains cas, l'infrastructure ne peut utiliser que les colonnes sélectionnées dans le jeu d'enregistrements pour identifier l'enregistrement de la table à modifier.
Sans toutes les colonnes nécessaires, plusieurs enregistrements risquent d'être modifiés dans la table et l'intégrité référentielle de celle-ci peut s'en trouver altérée.
Dans ce cas, l'infrastructure lève une exception quand vous appelez Update.
De préférence placez le code dans les blocs try-catch pour récupérer d'éventuelles exceptions.

Mis à jour le 19 septembre 2005 Gabrielly

En principe, lorsque vous ajoutez, modifiez ou supprimez des enregistrements, le jeu d'enregistrements modifie aussitôt la source de données.
Le tableau suivant résume les options disponibles pour les jeux d'enregistrements, accompagnées des différentes caractéristiques de mise à jour.

Type Lecture Modification
d'un enregistrement
suppression
d'un enregistrement
Ajout
d'un enregistrement
En lecture Seule oui non non non
Ajout Seul oui non non oui
Pleinement Modifiable oui oui oui oui

Un objet jeu d'enregistrements est modifiable si la source de données l'est et que le jeu d'enregistrements a été ouvert comme étant modifiable.
C'est le cas lorsque le recordset est ouvert avec comme type CRecordset::dynaset.
Son caractère modifiable dépend également de l'instruction SQL que vous utilisez, des capacités du pilote ODBC et de l'éventuel chargement en mémoire de la bibliothèque de curseurs ODBC.
Il est impossible de modifier une source de données ou un jeu d'enregistrements en lecture seule.
C'est le cas lorsqu'il est ouvert comme type CRecordset::snapshot avec pour option CRecordset::readOnly.
Si le jeu d'enregistrement doit permettre uniquement l'ajout d'enregistrement le recordset peut être ouvert avec l'option CRecordset::appendOnly.
Pour déterminer si le jeu d'enregistrements est modifiable.
Appelez la fonction membre CanUpdate de l'objet jeu d'enregistrements.
CanUpdate retourne une valeur différente de zéro si le jeu d'enregistrements est modifiable.
Par défaut, les jeux d'enregistrement sont entièrement modifiables (vous pouvez effectuer les opérations AddNew, Edit et Delete).
Mais vous pouvez également utiliser l'option appendOnly pour ouvrir des jeux d'enregistrements modifiables.
Un jeu d'enregistrements ouvert de cette façon ne permet que l'ajout de nouveaux enregistrements à l'aide de AddNew.
Il n'est pas possible de modifier ou de supprimer des enregistrements existants.
Vous pouvez tester si un jeu d'enregistrements est ouvert uniquement pour permettre l'ajout en appelant la fonction membre CanAppend.
CanAppend retourne une valeur différente de zéro si le jeu d'enregistrements est entièrement modifiable ou ouvert uniquement pour permettre l'ajout.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  
if( !rsStudentSet.Open() )  // rsStudentSet est de type CStudentSet dérivée de CRecordset 
    return FALSE; 
  
if( !rsStudentSet.CanUpdate() ) // vérifie s'il est modifiable 
{ 
    AfxMessageBox( "Impossible de mettre à jours le recordset." ); 
    return; 
} 
  
if( !rsStudentSet.CanAppend() )  // vérifie s'il permet l'ajout d'enregistrement 
{ 
    AfxMessageBox("Impossible d'ajouter des enregistrements"); 
    return; 
}

Mis à jour le 19 septembre 2005 Gabrielly

Dans la classe du jeu d'enregistrement ou jeu de lignes: les champs et les paramètres

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
  
class CStudentSet : public CRecordset 
{ 
    // Field Data 
    CString m_strFirstName; 
    CString m_strLastName; 
    long m_StudentID; 
    CString m_strGradYear; 
  
    // Param Data 
    CString m_strGradYrParam;  
    long m_StudebtIDParam;    
};
Dans le constructeur: les champs et les paramètres

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  
CStudentSet::CStudentSet(CDatabase* pdb) 
   : CRecordset(pdb) 
{  
    // Field Data 
    m_strFirstName = _T(""); 
    m_strLastName = _T(""); 
    m_StudentID = 0; 
    m_strGradYear = _T(""); 
    m_nFields = 4; 
  
    // Param Data 
    m_strGradYrParam = _T(""); 
    m_StudentIDParam = 0; 
    m_nParams = 2; 
}
Dans DoFieldExchange: les champs et les paramètres

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
  
void CStudentSet::DoFieldExchange(CFieldExchange* pFX) 
{ 
    // Field Data 
    pFX->SetFieldType(CFieldExchange::outputColumn); 
    RFX_Text(pFX, _T("[FirstName]"), m_strFirstName); 
    RFX_Text(pFX, _T("[LastName]"), m_strLastName); 
    RFX_Long(pFX, _T("[StudentID]"), m_StudentID); 
    RFX_Text(pFX, _T("[GradYear]"), m_strGradYear); 
  
    // Param Data 
    // l'ordre des appels RFX_() sur les paramètres est important!!! 
    pFX->SetFieldType(CFieldExchange::param); 
    RFX_Text(pFX, _T("[GradYear]"), m_strGradYrParam);  // 1 ier paramètre 
    RFX_Long(pFX, _T("[StudentID]"), m_StudentIDParam); // 2 ième paramètre 
}
Dans GetDefaultSQL

Code c++ : Sélectionner tout
1
2
3
4
5
  
CString CStudentSet::GetDefaultSQL() 
{ 
    return _T("[Student]");  // la requête SELECT par défaut de la table Student 
}
Dans une méthode quelconque...

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
  
CDatabase mydb; 
  
if( !mydb.OpenEx( _T( "ODBC;DSN=MYDATASOURCE;UID=JOES" )) 
{ 
    AfxMessageBox("Impossible d'ouvrir la base de donnée"); 
    return; 
} 
  
CStudentSet rsStudents(mydb); 
rsStudents.m_strFilter = "[GradYear] <= ? AND [StudentID] >= ? AND [StudentID] <= '1000'"; // je prépare le filtre 
  
// Hééée!!! l'ordre des points d'interrogation est important 
// je dois avoir 2 points d'interrogations car m_nParams = 2 
// le premier est pour m_strGradYrParam et le deuxième pour m_StudentIDParam dans cet ordre définit dans DoFieldExchange!!! 
// rsStudents.m_strFilter = "[StudentID] >= ? AND [StudentID] <= '1000' AND [GradYear] <= ?";  // very bad 
  
rsStudent.m_strSort = "LastName DESC, FirstName DESC"; // je prépare le trie 
  
// Cette fonction d'aide me donne l'année académique courante 
CString strGradYear = GetCurrentAcademicYear();  
  
// Cette fonction d'aide me donne un ID d'étudiant inférieure à 1000   
long StudentID = GetCurrentStudentID(); 
  
// j'initialise mes paramètres 
rsStudents.m_strGradYrParam = strGradYear; 
rsStudents.m_StudentIDParam = StudentID; 
  
rsStudents.Open(); // j'ouvre le jeu 
  
if(rsStudents.IsOpen()) 
{ 
    // bon il est ouvert je peux naviguer à travers les enregistrements 
} 
  
rsStudents.Close(); // bon, je préfère fermer explicitement  
mydb.Close(); // je ferme aussi la connexion
La requête complète est celle donnée par GetDefaultSQL(), CRecordsert::m_strFilter et CRecordsert::m_strSort.

Mis à jour le 19 septembre 2005 Gabrielly

Vous pouvez définir une ou plusieurs colonnes à partir desquelles effectuer le tri, et choisir un ordre de tri ascendant ou descendant (ASC ou DESC, ASC étant la valeur par défaut) pour chaque colonne.
Par exemple, si vous indiquez deux colonnes, les enregistrements sont d'abord triés sur la première colonne, puis sur la seconde.
La clause SQL ORDER BY définit une instruction de tri.
Quand l'infrastructure ajoute la clause ORDER BY à la requête SQL du jeu d'enregistrements, la clause contrôle le tri de la sélection.

Vous devez établir un ordre de tri du jeu d'enregistrements après avoir construit l'objet mais avant d'appeler sa fonction membre Open (ou la fonction membre Requery d'un objet jeu d'enregistrements existant dont la fonction membre Open a été appelée préalablement).

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
  
CStudentSet rsStudent( NULL ); 
// définit le trie  
rsStudent.m_strSort = "LastName DESC, FirstName DESC"; // good 
// rsStudent.m_strSort = "ORDER BY LastName DESC, FirstName DESC"; // bad 
// exécute la requête 
rsStudent.Open();
Le jeu d'enregistrements contient tous les étudiants, triés par ordre décroissant (de Z à A) sur le nom, puis sur le prénom. La requête complète est fournit par GetDefaultSQL() et CRecordset::m_strSort

Mis à jour le 19 septembre 2005 Gabrielly

Filtrer un jeu d'enregistrements c'est sélectionner un sous-ensemble particulier au sein des enregistrements disponibles.
Par exemple, considérons le recordset suivant:

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
  
class CCourse : public CRecordset 
{ 
public: 
   CCourse(CDatabase* pDatabase = NULL); 
   ... 
   CString m_strCourseID; 
   CString m_strCourseTitle; 
   CString m_strIDParam; 
};
vous pourriez vouloir ne sélectionner que les sections classe d'un cours donné, comme MATH101.
Un filtre est une condition de recherche définie par le contenu d'une clause SQL WHERE.
Quand l'infrastructure l'ajoute à l'instruction SQL du jeu d'enregistrements, la clause WHERE limite la sélection.
Vous devez établir un filtre d'objet jeu d'enregistrements après avoir construit l'objet mais avant d'appeler
sa fonction membre Open (ou la fonction membre Requery d'un objet jeu d'enregistrements existant dont la fonction membre Open a été appelée préalablement).

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
  
CCourse rsCourseSet( NULL ); 
  
rsCourseSet.m_strFilter = "CourseID = 'MATH101'"; // very good 
// rsCourseSet.m_strFilter = "WHERE CourseID = 'MATH101'"; // very bad, pas de WHERE 
// rsCourseSet.m_strFilter = "CourseID = MATH101"; // very bad, mais où sont les guillemets simples?  
  
rsCourseSet.Open( CRecordset::snapshot, NULL, CRecordset::readOnly ); 
  
if( !rsCourseSet.IsOpen() ) 
    return; 
  
// Affiche tous les titres pour lequel CourseID = 'MATH101' 
while ( !rsCourseSet.IsEOF( ) ) 
{ 
    CString strTitle = rsCourseSet.m_strCourseTitle; // j'obtient un titre 
    TRACE("%s\n", (LPCTSTR) strTitle);  
    rsCourseSet.MoveNext( ); 
} 
  
rsCourseSet.m_strFilter = "CourseID = ?"; 
rsCourseSet.m_strIDParam = "PROGRAMMATION"; // very good 
// rsCourseSet.m_strIDParam = "'PROGRAMMATION'"; // very bad, pourquoi des guillemets simples? 
  
// aller on refait la requête si m_strIDParam a été definit correctement (m_nParams et DoFieldExchange) 
rsCourseSet.Requery();  
  
// Réaffiche à nouveau tous les titres pour lequel CourseID = 'PROGRAMMATION' 
while ( !rsCourseSet.IsEOF( ) ) 
{ 
    CString strTitle = rsCourseSet.m_strCourseTitle; // j'obtient un titre 
    TRACE("%s\n", (LPCTSTR) strTitle);  
    rsCourseSet.MoveNext( ); 
}
La requête complète est fournit par GetDefaultSQL() et CRecordset::m_strFilter

Mis à jour le 19 septembre 2005 Gabrielly

Lorsque vous naviguez au sein d'un jeu d'enregistrements, il arrive fréquemment que vous ayez besoin de revenir sur un enregistrement donné.
Deux méthodes, le signet et la position absolue, permettent de répondre à ce besoin.

Signets dans ODBC MFC
Un signet identifie un enregistrement de façon unique.
Lorsque vous naviguez au sein d'un jeu d'enregistrements,
vous ne pouvez pas toujours vous fier à la position absolue d'un enregistrement dans la mesure où les enregistrements peuvent être supprimés du jeu d'enregistrements.
La solution la plus fiable pour conserver la trace de la position d'un enregistrement consiste à utiliser un signet.

La classe CRecordset propose des fonctions membres pour :
obtenir le signet de l'enregistrement courant, de telle sorte que vous puissiez le sauvegarder dans une variable (GetBookmark) ;
atteindre rapidement un enregistrement donné en indiquant son signet, préalablement sauvegardé dans une variable (SetBookmark).

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
  
CCustSet rsCustSet( NULL );  
// ouvre le recordset; 
rsCustSet.Open( CRecordset::snapshot, NULL, CRecordset::useBookmarks);  // utilise des signets 
  
//... 
  
CString strSaveRecord = rsCustSet.m_strData; 
  
CDBVariant varRecordToReturnTo; 
  
if(rsCustSet.CanBookmark()) 
   rsCustSet.GetBookmark( varRecordToReturnTo );// pour l'enregistrement courant on obtient un signet dans un CDBVariant 
  
// lignes de code de défilement du recordset 
//... 
  
if(rsCustSet.CanBookmark()) 
   rsCustSet.SetBookmark( varRecordToReturnTo );  // à l'aide du signet j'atteint immédiatement mon enregistrement 
  
if(strSaveRecord == rsCustSet.m_strData) 
{ 
   AfxMessageBox("j'ai retrouvé ma position"); 
}
Remarque : Vous devez également vérifier que les signets sont toujours présents après certaines opérations sur les jeux d'enregistrements.
Par exemple, si vous effectuez une opération Requery (lancement d'une nouvelle requête) sur un jeu d'enregistrements, il se peut que les signets ne soient plus valides.
Appelez CDatabase::GetBookmarkPersistence pour vérifier si vous pouvez faire appel à SetBookmark en toute sécurité.

Positions absolues dans ODBC MFC
Outre les signets, la classe CRecordset permet de définir l'enregistrement courant en indiquant sa position ordinale.
Cette méthode est appelée positionnement absolu.
Le positionnement absolu n'est pas disponible pour les jeux d'enregistrement en avant seulement (CRecordset::forwardOnly).
Pour déplacer le pointeur de l'enregistrement courant à l'aide du positionnement absolu,
appelez CRecordset::SetAbsolutePosition. Lorsque vous transmettez une valeur à SetAbsolutePosition,
l'enregistrement correspondant à cette position ordinale devient l'enregistrement courant.

Code c++ : Sélectionner tout
1
2
3
4
5
6
  
long nRows = 3; // je n'oublie pas de revenir au 3 ième enregistrement comme enregistrement courant  
  
// lignes de code de défilement du recordset 
//... 
rsCustSet.SetAbsolutePosition(nRows); // bon, je veux revenir au 3 ième enregistrement

Mis à jour le 19 septembre 2005 Gabrielly

La classe CRecordset propose les fonctions membres Move pour faire défiler un jeu d'enregistrements.
Ces fonctions déplacent l'enregistrement courant par jeu de lignes.
Si vous avez implémenté l'extraction globale de lignes,
l'opération Move repositionne le jeu d'enregistrements en fonction de la taille du jeu de lignes.
Dans le cas contraire, l'appel de la fonction repositionne le jeu d'enregistrements d'un enregistrement à la fois.

Pour défiler :

vers l'avant d'un enregistrement ou d'un jeu de lignes à la fois, appelez la fonction membre MoveNext ;
vers l'arrière d'un enregistrement ou d'un jeu de lignes à la fois, appelez la fonction membre MovePrev ;
jusqu'au premier enregistrement du jeu d'enregistrements, appelez la fonction membre MoveFirst ;
jusqu'au dernier enregistrement du jeu d'enregistrements ou jusqu'au dernier jeu de lignes, appelez la fonction membre MoveLast ;
de N enregistrements à partir de la position courante, appelez la fonction membre Move.

Pour tester le début ou la fin d'un jeu d'enregistrements

-Vous êtes-vous déplacé au-delà du dernier enregistrement ? Appelez la fonction membre IsEOF.
-Si vous avez atteint le premier enregistrement (déplacement vers l'arrière), Appelez la fonction membre IsBOF.

IsEOF (End Of File) retourne une valeur différente de zéro si le jeu d'enregistrements est positionné au-delà du dernier enregistrement.
IsBOF (Begin Of File) retourne une valeur différente de zéro si le jeu d'enregistrements est positionné avant le premier enregistrement.
Dans les deux cas, il n'y a pas d'enregistrement actuel sur lequel opérer.
Si vous appelez MovePrev alors que IsBOF a déjà la valeur TRUE,
ou si vous appelez MoveNext alors que IsEOF a déjà la valeur TRUE,
l'infrastructure lève une exception CDBException.
Vous pouvez aussi utiliser IsBOF et IsEOF pour vérifier si un jeu d'enregistrements est vide.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
  
CCustSet rsCustSet( NULL );  
// ouvre le recordset; l'enregistrement courant est en première position  
rsCustSet.Open( ); 
  
// Suis-je avant le premier enregistrement? 
if( rsCustSet.IsBOF( ) )  
    return; // le recordset est vide 
// je lis le champ Data du premier enregistrement     
Cstring strFirstData = rsCustSet.m_strData;  
  
if( !rsCustSet.CanScroll() )   // Puisse-je défiler? 
   return;   // dommage!!! 
  
// Scroll à la fin du recordset, jusqu'à se positionner au-delà du dernier enregistrement 
while ( !rsCustSet.IsEOF( ) ) 
{ 
 // je lis le champ Data de l'enregistrement courant  
    CString strCurrentData = rsCustSet.m_strData; 
    rsCustSet.MoveNext( ); 
} 
  
// je move au dernier record 
rsCustSet.MoveLast( ); 
  
// je lis le champ Data du dernier enregistrement 
CString strLastData = rsCustSet.m_strData;  
  
// Scroll au début du recordset, jusqu'à se positionner avant le premier enregistrement 
while( !rsCustSet.IsBOF( ) ) 
{ 
// je lis le champ Data de l'enregistrement courant 
    CString strCurrentData = rsCustSet.m_strData;  
    rsCustSet.MovePrev( ); 
} 
  
// je move vers le premier record 
rsCustSet.MoveFirst( ); 
// je lis le champ Data du premier enregistrement   
strFirstData = rsCustSet.m_strData;

Mis à jour le 19 septembre 2005 Gabrielly

De la façon la plus naturelle, il s'agit d'un appel au couple Open()/Close() du recordset.
D'abord on suppose que notre jeu d'enregistrement est construit à partir d'une classe dérivée de CRecordset.
Dans le constructeur, transmettez un pointeur à un objet de CDatabase,
ou la valeur NULL pour utiliser un objet temporaire de base de données que l'infrastructure construira et
ouvrira en fonction de la chaîne de connexion retournée par la fonction membre GetDefaultConnect.
L'objet de CDatabase peut ou non être déjà connecté à une source de données.
Et enfin appelez la fonction membre Open de l'objet.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
  
// Construit un snapshot 
CStudentSet rsStudent( NULL ); // construction implicite d'un CDatabase non connecté à une base de données 
// définit les options d'ouverture 
// utilise la chaîne de connexion par défaut de CRecordset::GetDefaultConnect 
if(!rsStudent.Open(CRecordset::snapshot, NULL, CRecordset::readOnly))   
    return FALSE; 
// Utilise le snapshot pour agir sur ses enregistrements... 
// ... 
rsStudent.Close();
Lorsque vous avez fini d'utiliser votre jeu d'enregistrements, vous devez désallouer sa mémoire par sa méthode Close() par oubli le destructeur le fait pour vous.

Mis à jour le 19 septembre 2005 Gabrielly

L'architecture d'un jeu d'enregistrements (jeu de lignes) est définit par ses données membres.
En effet considérons une classe dérivée de CRecordset

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
class CCourse : public CRecordset 
{ 
public: 
   CCourse(CDatabase* pDatabase = NULL); 
   ... 
   CString m_strCourseID; 
   CString m_strCourseTitle; 
   CString m_strIDParam; 
};

les membres de données qui composent l'architecture d'un objet jeu d'enregistrements sont :
  • les membres de données de type champ
  • les membres de données de type paramètre.


Membres de données de type champ :
Ce sont les membres des données les plus important.
Il s'agit de :
  • m_strCourseID pour la colonne "CourseID" de la table Course
  • m_strCourseTitle pour la colonne "CourseTitle" de la table Course


Pour que les données de la source de données soient fournies à vos membres de données de type champ la fonction CCourse::DoFieldExchange doit être implémentée et le nombre de champs CRecordset::m_nFields pris en charge par votre jeu d'enregistrement doit être fixé de préférence dans le constructeur de CCourse

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CCourse::DoFieldExchange(CFieldExchange* pFX) 
{ 
   pFX->SetFieldType(CFieldExchange::outputColumn); 
   RFX_Text(pFX, "CourseID", m_strCourseID); 
   RFX_Text(pFX, "CourseTitle", m_strCourseTitle); 
} 
  
CCourse::CCourse(CDatabase* pdb) 
   : CRecordset(pdb) 
{ 
   m_strCourseID = ""; 
   m_strCourseTitle = ""; 
   m_nFields = 2;  // absolument recommandé  
}

Membres de données de type paramètre:
Si la classe est « paramétrée », elle possède un ou plusieurs membres de données de type paramètre.
Il s'agit de m_strIDParam.
Une classe paramétrée vous permet de baser une requête de jeu d'enregistrements sur des informations obtenues ou calculées au moment de l'exécution.
En général, le paramètre vous aide à affiner la sélection, l'objet jeu d'enregistrements pourrait exécuter l'instruction SQL suivante :
SELECT [CourseID], [CourseTitle] FROM [Course] WHERE [CourseID] = ? Le « ? » constitue un emplacement réservé pour la valeur de paramètre fournie à l'exécution.
Lorsque vous construisez le jeu d'enregistrements et définissez son membre de données m_strIDParam sur « MATH101 » , l'instruction SQL effective du jeu d'enregistrements devient :
SELECT [CourseID], [CourseTitle] FROM [Course] WHERE [CourseID] = MATH101 Pour que ODBC le considère comme paramètre il faut fixer le nombre de paramètres CRecordset::m_nParams et ensuite ajouter un peu de code dans DoFieldExchange()

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void CCourse::DoFieldExchange(CFieldExchange* pFX) 
{ 
   pFX->SetFieldType(CFieldExchange::outputColumn); 
   RFX_Text(pFX, "CourseID", m_strCourseID); 
   RFX_Text(pFX, "CourseTitle", m_strCourseTitle); 
  
   pFX->SetFieldType(CFieldExchange::param); 
   RFX_Text(pFX, "CourseID", m_strIDParam);  // le second argument est ignoré 
} 
  
CCourse::CCourse(CDatabase* pdb) 
   : CRecordset(pdb) 
{ 
   m_strCourseID = ""; 
   m_strCourseTitle = ""; 
   m_nFields = 2; 
   m_nParams = 1;  // absolument recommandé  
}

Mis à jour le 19 septembre 2005 Gabrielly

Pour vous connecter à une source de données spécifique, votre source de données doit déjà être configurée à l'aide de l'Administrateur ODBC

Construisez un objet CDatabase.
Appelez sa fonction membre OpenEx ou Open.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
  
CDatabase mydb; 
  
if( !mydb.OpenEx( _T( "ODBC;DSN=MYDATASOURCE;UID=JOES" ),  
CDatabase::openReadOnly) 
{ 
    AfxMessageBox("Impossible d'ouvrir la base de donnée en lecture seule"); 
    return; 
} 
else 
{ 
    AfxMessageBox("Test d'ouverture réussi"); 
    mydb.ExecuteSQL("CREATE TABLE OFFICES (OfficeID TEXT(4)" ",  
                         OfficeName TEXT(10))"); 
} 
  
CRecorset r(&mydb); 
  
//... utilisation 
  
r.Close();  // Je ferme d'abord le recordset 
mydb.Close();  // Je me déconnecte 
  
// je me reconnecte à une autre source de données 
  
if( !mydb.OpenEx( _T( "ODBC;DSN=SQLServer_Source;Trusted_Connection=Yes;" ), 
 CDatabase::forceOdbcDialog) 
{ 
    AfxMessageBox("Impossible d'ouvrir la base de donnée à l'aide de l'authentification Windows NT"); 
    return; 
} 
else 
{ 
    AfxMessageBox("Test d'ouverture réussi"); 
} 
  
// ...
Si la chaîne de connexion ne contient pas suffisamment d'informations alors la boite dialogue de connexion ODBC s'ouvre.
Vous devez fermer tous les jeux de lignes (CRecordset) ouverts avant d'appeler la fonction membre Close de CDatabase.
Dans les jeux de lignes associés à l'objet CDatabase que vous voulez fermer, toutes les instructions AddNew ou Edit et toutes les transactions en attente sont annulées.

Pour vous déconnecter d'une source de données
Appelez la fonction membre Close de l'objet CDatabase.
Détruisez l'objet, sauf si vous voulez le réutiliser.
Vous pouvez réutiliser un objet CDatabase après une déconnexion, que ce soit pour vous reconnecter à la même source de données ou pour vous connecter à une source de données différente.
Pour réutiliser un objet CDatabase
Fermez la connexion initiale de l'objet.
Plutôt que de détruire l'objet, appelez à nouveau sa fonction membre OpenEx ou Open.

Mis à jour le 19 septembre 2005 Gabrielly

Avant tout établissement de communications avec un système de gestion de base de données (SGBD) pour accéder aux données, une chaîne de connexion est demandée.
C'est chaîne comprend des informations qui renseignent le pilote ODBC sur la façon d'ouvrir une connexion à la base de donnée.
Pour des projets générés à l'aide des assistants code, ces derniers fournissent une chaîne de connexion par défaut qui est retournée par la fonction membre virtuelle CRecordset::GetDefaultConnect.
Cette fonction est appelée par la charpente MFC lors de votre première communication à la base de données.
Vous pouvez redéfinir cette fonction pour imposer aux utilisateurs de votre application un mode d'ouverture à votre base de données.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
  
CString CMyAppSet::GetDefaultConnect() 
{ 
   // les utilisateurs seront forcés de sélectionner une source de données,  
   // et de rentrer un username et un password à travers une boîte de dialogue de connexion  
   return "ODBC;";  
}
Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
  
CString CMyAppSet::GetDefaultConnect() 
{ 
   // les utilisateurs seront forcés de rentrer un username et un password 
   // à travers une boîte de dialogue de connexion  
   // mydb est le nom de ma source de données 
   return "ODBC;DSN=mydb;";  
}
Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
  
CString CMyAppSet::GetDefaultConnect() 
{ 
   // les utilisateurs seront forcés de rentrer un password qui 
   // correspond au username myuserid attendu à travers une boîte 
   // de dialogue de connexion   
   return "ODBC;DSN=mydb;UID=myuserid;";  
}
Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
  
CString CMyAppSet::GetDefaultConnect() 
{ 
   // la chaîne est codée en dure et la connexion est directe 
   // Il convient d'éviter de coder de manière irréversible un mot de passe,  
   // ou de spécifier un mot de passe vide puisque cela compromet sérieusement la sécurité. 
   return "ODBC;DSN=mydb;UID=sa;PWD=777;"; 
}
Code c++ : Sélectionner tout
1
2
3
4
5
6
7
  
CString CApp1Set::GetDefaultConnect() 
{ 
   // Cette chaîne de connexion spécifie une connexion approuvée  
   // (qui utilise la sécurité intégrée de Windows NT).  
   return "ODBC;DSN=afx;Trusted_Connection=Yes;"; 
}

Mis à jour le 19 septembre 2005 Gabrielly

L'Administrateur ODBC est utilisé pour configurer vos sources de données.
Vous pouvez également utiliser l'Administrateur ODBC après l'installation pour ajouter ou supprimer des sources de données.
Lorsque vous créez des applications,
vous pouvez diriger vos utilisateurs vers l'Administrateur ODBC pour leur permettre d'ajouter des sources de données,
ou créer cette fonctionnalité dans votre application en effectuant des appels d'installation ODBC directs.

Vous pouvez utiliser un fichier Excel comme source de données que vous devez configurer afin qu'il soit enregistré et
qu'il apparaisse dans la boîte de dialogue Source de données.

Pour utiliser un fichier Excel comme source de données
Cliquez sur le panneau de configuration, sélectionnez Outils d'administration, et enfin Source de données ODBC.
Sous l'onglet DSN fichier, cliquez sur Ajouter.
Dans la boîte de dialogue Créer une nouvelle source de données, sélectionnez un pilote Excel, puis cliquez sur Suivant.
Cliquez sur Parcourir et sélectionnez le nom du fichier à utiliser comme source de données.
Comme remarque vous devrez peut-être sélectionner Tous les fichiers dans le menu déroulant pour afficher tous les fichiers .xls.
Cliquez sur Suivant, puis sur Terminer.
Dans la boîte de dialogue Installation ODBC pour Microsoft Excel, sélectionnez la version et le classeur de la base de données.

Pour utiliser votre base de donnée access comme source de données la procédure est semblable.
Cliquez sur le panneau de configuration, sélectionnez Outils d'administration, et enfin Source de données ODBC.
Une fois que la boîte s'affiche à l'écran sélectionner l'onglet DSN utilisateur ( Source de données utilisateur).
Cliquez sur Ajouter... pour ajouter une source de données.
Une autre boîte s'affiche, et vous sélectionnez le pilote ODBC Microsoft Access Driver (*.mdb)
Cliquez sur Terminer.
Une autre boite s'affiche à nouveau, et vous tapez le nom de votre source de données.
Ce nom sera utilisé pour identifier la base de données dans vos projets VC++.
Cliquez sur le bouton Sélectionner... pour accéder à la dernière boîte de dialogue où vous pouvez sélectionner le fichier qui représente votre base de donnée.(fichier *.mdb)
Enfin vous cliquez sur tous les trois boutons OK et la configuration est terminée.

Mis à jour le 19 septembre 2005 Gabrielly

La connexion à une source de données signifie l'établissement de communications avec un système de
gestion de base de données (SGBD) pour accéder aux données. Lorsque vous vous connectez à une source
de données à partir d'une application par le biais d'un pilote ODBC, le pilote établit la connexion pour vous,
localement ou via un réseau.

Vous pouvez vous connecter à toute source de données pour laquelle vous possédez un pilote ODBC.
Les utilisateurs de votre application doivent également disposer du même pilote ODBC pour leur source de données.

La gestion des connexions passe

  • Par la configuration de la source de données,
  • Par son accès dans un environnement multi-utilisateur
  • Par la généralisation d'une chaîne de connexion à la source de données
  • Par la façon de se connecter et de se déconnecter à la source de données
  • Par la réutilisation de la connexion c'est à dire celle de l'objet CDatabase.

Mis à jour le 19 septembre 2005 Gabrielly

Dans la terminologie de base de données, une source de données représente un ensemble spécifique de données,
les informations nécessaires pour accéder à ces données et l'emplacement de la source de données,
pouvant être décrite par un nom de source de données.

Pour utiliser la classe CDatabase, vous devez configurer la source de données à l'aide de l'Administrateur ODBC.
Une base de données distante s'exécutant sur Microsoft SQL Server au sein d'un réseau ou un fichier
Microsoft Access dans un répertoire local constitue des exemples de sources de données.
Vous pouvez accéder à toute source de données pour laquelle vous possédez un pilote ODBC à partir de votre application.

Une ou plusieurs sources de données peuvent être actives simultanément dans votre application,
Chacune étant représentée par un objet CDatabase.
Plusieurs connexions simultanées à une source de données peuvent également être actives.
Vous pouvez vous connecter à des sources de données distantes ou locales,
selon les pilotes installés et les fonctionnalités de vos pilotes ODBC.

Mis à jour le 19 septembre 2005 Gabrielly

C'est en transmettant le membre CRecordset::m_pDatabase d'un jeu de lignes à un autre.
m_pDatabase est un pointeur sur un objet CDatabase qui représente une connexion à une base de données.
Puisque les jeux de lignes CDBset et CDBOrderSet sont attachés aux tables [Product], [Orders] et [Order Details] d'une même base de données. Il serait une perte de temps de créer à chaque fois un objet CDatabase pour un CRecordset.

Code c++ : Sélectionner tout
1
2
3
  
CDBSet(CDatabase* pDatabase = NULL);  // pour notre CDBSet 
CDBOrderSet(CDatabase* pDatabase = NULL); // pour notre CDBOrderSet
Ainsi il faut utiliser un CDatabase déjà ouvert et le transmettre à un autre CRecordset attaché à la mème base de données. Mais quand est-ce que CDatabase est construit et que son membre Open() est appelé?

Pour construire le CDatabase nous aurions pu écrire

Code c++ : Sélectionner tout
1
2
  
m_pSet->m_pDataBase = new CDatabase();  // m_pSet est un pointeur sur un CRecordset
Dans le cas de notre exemple;
Les deux jeux de lignes sont déclarés dans le document CDBDoc. Dans le constructeur CDBDoc::CDBDoc les constructeurs des jeux sont appelés avec un pointeur NULL (argument par défaut) et les membres des deux jeux CRecordset::m_pDatabase sont nuls.
Le CDatabase est construit implicitement dans les coulisses des appels des membres CRecordset::Open ou CRecordView::OnInitialUpdate qui en fin de compte appelle CRecordset::Open.
Si CRecordset::Open se rend compte que CDatabase n'est pas crée, il le construit puis appelle CDatabase::Open Pour le cas de notre exemple le CDatabase est construit au premier affichage de CDBView par son membre CDBView::OnInitialUpdate() qui appelle soit CRecordView::OnInitialUpdate ou directement CRecordset::Open.
Mais quant à CDBOrderSet, son membre m_pDatabase est toujours nul d'où la réutilisation.

Code c++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
  
void CDBOrderView::OnInitialUpdate() 
{ 
BeginWaitCursor(); 
  
CDBDoc* pDoc = (CDBDoc*) GetDocument(); // Obtient le document 
m_pSet = &pDoc->m_OrderSet;// Obtient le jeu de ligne associé 
  
// Utilise la BD ouverte pour le jeu de lignes CDBSet 
m_pSet->m_pDatabase = pDoc->m_dBSet.m_pDatabase; // Réutilisation de la connexion  
  
// Définit le code produit en cours comme paramètre 
// Initialise le paramètre du filtre 
m_pSet->m_ProductIDParam = pDoc->m_dBSet.m_ProductID;  
  
// Définit le filtre comme colonne code produit 
m_pSet->m_strFilter = "[ProductID] = ? AND [Orders].[OrderID] = [Order Details].[OrderID]"; 
  
m_pSet->Open();   // Appel du membre CDBOrderSet::Open 
  
if (m_pSet->IsOpen()) 
{ 
CString strTitle = "Nom de la table :"; 
// la chaîne est vide Pourquoi? Parce que nous avons 2 tables 
CString strTable = m_pSet->GetTableName();  
     // et donc 2 noms de tables 
  
if (!strTable.IsEmpty()) 
strTitle += _T(":") + strTable; 
  
GetDocument()->SetTitle(strTitle); 
} 
CRecordView::OnInitialUpdate(); 
EndWaitCursor(); 
}

Mis à jour le 19 septembre 2005 Gabrielly

Lire Comment placer la clause WHERE dans une requête SQL? pour comprendre l'enchaînement.
Soit m_ProductIDParam une variable de type long utilisée comme paramètre de filtre

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class CDBOrderSet : public CRecordset 
{ 
public: 
    CDBOrderSet(CDatabase* pDatabase = NULL); 
    DECLARE_DYNAMIC(CDBOrderSet) 
  
    // Field/Param Data 
    //{{AFX_FIELD(CDBOrderSet, CRecordset) 
    longm_OrderDetailID; 
    longm_OrderID; // champ de la table [Orders] attaché à la colonne [Orders].[OrderID] 
    longm_ProductID; 
    //... d'autres déclarations des colonnes 
    longm_OrderID2;// champ de la table [Order Detail] attaché à la colonne [Order Details].[OrderID] 
    //... d'autres déclarations des colonnes 
    //}}AFX_FIELD 
  
    long m_ProductIDParam;// définition du paramètre qui remplace le symbole ? dans le filtre 
    //...

Dans le constructeur :

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
CDBOrderSet::CDBOrderSet(CDatabase* pdb) 
: CRecordset(pdb) 
{ 
    //{{AFX_FIELD_INIT(CDBOrderSet) 
    m_OrderDetailID = 0; 
    m_OrderID = 0; 
    m_ProductID = 0; 
    //... autres initialisations 
    m_OrderID2 = 0; 
    //... autres initialisations 
    m_nFields = 29;     //nombre de champs  
    //}}AFX_FIELD_INIT 
  
    m_ProductIDParam = 0L;//initialisation du paramètre 
    m_nParams = 1;//nombre de paramètres 
    m_nDefaultType = snapshot; //type de jeu de lignes 
}

On initialise le paramètre m_ProductIDParam et on fixe le nombre de paramètres à l'aide du membre CRecordset::m_nParams. Comme vous le constatez on peut utiliser plusieurs paramètres et fixer ainsi leur nombre à l'aide du membre m_nParams.
Il doit y avoir concordance sinon le fonctionnement est compromis.

Pour identifier la variable m_ProductIDParam de la classe comme un paramètre de substitution dans le filtre de CDBOrderSet nous devons également ajouter du code au membre de la classe DoFieldExchange()

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void CDBOrderSet::DoFieldExchange(CFieldExchange* pFX) 
{ 
    //{{AFX_FIELD_MAP(CDBOrderSet) 
    pFX->SetFieldType(CFieldExchange::outputColumn); 
    RFX_Long(pFX, _T("[OrderDetailID]"), m_OrderDetailID); 
    RFX_Long(pFX, _T("[Order Details].[OrderID]"), m_OrderID); 
    RFX_Long(pFX, _T("[ProductID]"), m_ProductID); 
    //... autres définitions de mappage 
    RFX_Long(pFX, _T("[Orders].[OrderID]"), m_OrderID2); 
    //... autres définitions de mappage 
    //}}AFX_FIELD_MAP 
  
    //Définit le type de colonne comme paramètre 
    pFX->SetFieldType(CFieldExchange::param);// définit le mode des appels RFX_()suivants sur param 
    RFX_Long(pFX, _T("ParamProductID"), m_ProductIDParam); 
  
}

SetFieldType(CFieldExchange::param) définit le mode des appels RFX_() suivants sur param.
Le troisième argument de tous les appels RFX_() suivants est alors interprété comme un paramètre qui doit remplacer un point d'interrogation dans le filtre du jeu de lignes.
Si vous avez plusieurs paramètres, ils remplacent les points d'interrogation dans la chaîne m_strFilter, de gauche à droite.
Il importe de veiller à ce que les appels RFX_() soient dans le bon ordre.
Lorsque le mode est défini sur param, le second argument de l'appel RFX_() est ignoré, vous pouvez donc le mettre à NULL ici, ou une autre chaîne si vous voulez.

En quelques mots :
Déclarer les paramètres dans le jeu de lignes.
Initialiser ces paramètres et fixer le nombre de paramètres à l'aide de m_nParams. Utitliser DoFieldExchange pour les identifier comme paramètres de substitution.

Mis à jour le 19 septembre 2005 Gabrielly

Dans le context ODBC avec les MFC;
c'est en ajoutant un filtre à la requête par l'attribution d'une chaîne au membre CRecordset::m_strFilter de l'objet jeu de lignes.

Soit l'instruction suivante :

Code C++ : Sélectionner tout
m_pSet->m_strFilter = "[ProductID] = ? AND [Orders].[OrderID] = [Order Details].[OrderID]";

Ici, dans le cas de notre exemple, m_pSet est le pointeur sur CDBOrderSet de la classe CDBOrderView.
[Orders] et [Order Details] sont deux tables associés au jeu de lignes CDBOrderSet.
Chacune des tables définit son champs [OrderID]. Ici le champ est précédé du nom de sa table.
[ProductID] est un champ de la table [Order Details]

Relativement à cette clause WHERE, il s'agit de produire une table à l'aide de l'opération SQL SELECT sur l'objet CDBOrderSet pour lequel les champs [OrderID] des deux tables sont égaux et le champ ProductID de CDBOrderSet correspond à la valeur actuelle du champs ProductID de CDBSet attaché à la table [Product].

Cette valeur courante de ProductID de CDBSet est représentée par le point d'interrogation Ce symbole ? sera remplacé par un paramètre du filtre qu'il faut définir...

Voir Comment définir un paramètre de filtre dans la clause WHERE d'une requête SQL?

Mis à jour le 19 septembre 2005 Gabrielly

C'est en ajoutant des jeux de lignes supplémentaires à notre application.
Si notre premier jeu de lignes CDBSet est connecté à la table Product qui fournit les informations sur les produits nous pouvons insérer une seconde table qui elle gèrera les informations de commandes sur les produits de la base de données.

En effet, soit CDBOrderSet un autre recordset attaché à deux tables [Orders] et [Order Details] qui contiennent toutes les commandes correspondant à un produit et soit CDBOrderView l'objet vue de lignes associé qui affiche les données de CDBOrderSet.

À l'aide de classwizard (VC++ 6.0), on clique sur Add Class... puis sur New.
On saisit le nom de la classe CDBOrderSet et on sélectionne la classe de base CRecordset.
Cliquez ensuite sur OK, Classwizard vous amène à la boite de dialogue permettant de sélectionner la base de données associées et sous recordset type on conserve le paramétrage snapshot.
Cliquez ensuite sur OK qui nous amène à la sélection des tables de la base de données.
Soient deux tables Orders et Orders Détails préalablement créées qu'on associe à CDBOrderSet, puis cliquez sur OK pour terminer la procédure.

voici le code généré:

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class CDBOrderSet : public CRecordset 
{ 
public: 
    CDBOrderSet(CDatabase* pDatabase = NULL); 
    DECLARE_DYNAMIC(CDBOrderSet) 
  
    // Field/Param Data 
    //{{AFX_FIELD(CDBOrderSet, CRecordset) 
    longm_OrderDetailID; 
    longm_OrderID; 
    longm_ProductID; 
    CTimem_DateSold; 
    doublem_Quantity; 
    CStringm_UnitPrice; 
    doublem_Discount; 
    CStringm_SalePrice; 
    CStringm_SalesTax; 
    CStringm_LineTotal; 
    longm_OrderID2; 
    longm_CustomerID; 
    longm_EmployeeID; 
    CTimem_OrderDate; 
    CStringm_PurchaseOrderNumber; 
    CTimem_RequiredByDate; 
    CTimem_PromisedByDate; 
    CStringm_ShipName; 
    CStringm_ShipAddress; 
    CStringm_ShipCity; 
    CStringm_ShipState; 
    CStringm_ShipStateOrProvince; 
    CStringm_ShipPostalCode; 
    CStringm_ShipCountry; 
    CStringm_ShipPhoneNumber; 
    CTimem_ShipDate; 
    longm_ShippingMethodID; 
    CStringm_FreightCharge; 
    doublem_SalesTaxRate; 
    //}}AFX_FIELD 
    //...

Deux tables [Orders] et [Order Details] sont associées à notre snapshot.
On peut ne pas avoir besoin de toutes les colonnes. On peut supprimer quelques unes à l'aide de ClassWizard.

Ajoutons un membre donnée m_OrderSet dans la définition de la classe document CDBDoc et en insérant le fichier "DBOrderSet.h" dans "DBDoc.h"

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include "DBSet.h" 
#include "DBOrderSet.h" 
  
class CDBDoc : public CDocument 
{ 
protected: // create from serialization only 
    CDBDoc(); 
    DECLARE_DYNCREATE(CDBDoc) 
  
    // Attributes 
public: 
    CDBSet m_dBSet; 
    CDBOrderSet m_OrderSet; 
    //...

Mis à jour le 19 septembre 2005 Gabrielly

C'est en appelant le membre

Code C++ : Sélectionner tout
CString CDatabase::GetDatabaseName() const

de CDatabase pour obtenir le nom de la base de données.

Pour obtenir le nom de la table attachée au recordset c'est le membre

Code C++ : Sélectionner tout
const CString& CRecordset::GetTableName() const
Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void CDBView::OnInitialUpdate() 
{ 
    m_pSet = &GetDocument()->m_dBSet; 
    // trie suivant la colonne ProductID de la table Product 
    m_pSet->m_strSort = "[ProductID]"; 
    // ouverture du jeu de ligne 
    CRecordView::OnInitialUpdate(); 
  
    if(m_pSet->IsOpen()) 
    { 
        CString strTitle = m_pSet->m_pDatabase->GetDatabaseName(); 
        CString strTable = m_pSet->GetTableName(); 
  
        if(!strTable.IsEmpty()) 
            strTitle += _T(":") + strTable; 
  
        GetDocument()->SetTitle(strTitle); 
    } 
  
    GetParentFrame()->RecalcLayout(); 
    ResizeParentToFit(); 
}

En général, vous devez vous assurer que vous obtenez une chaîne retournée par la fonction GetTableName().
Plusieurs conditions peuvent empêcher la définition d'un nom de table, par exemple le jeu de lignes peut faire intervenir plusieurs tables.

Mis à jour le 19 septembre 2005 Gabrielly

Le transfert de données entre la base de données et le jeu de lignes a lieu par un appel direct à CRecordset::Open() de l'objet jeu de lignes.
Pour vérifier que le jeu est déjà ouvert on appelle CRecordset::IsOpen().

Dans l'exemple de notre programme le membre OnInitialUpdate de la classe de base de notre vue CRecordView appelle la fonction membre Open() de l'objet jeu de lignes.
C'est pourquoi la définition du membre m_strSort pour le tri est définit avant l'appel à OnInitialUpdate de la classe de base.

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
void CDBView::OnInitialUpdate() 
{ 
    //... 
    m_pSet->m_strSort = "[ProductID]";// trie suivant la colonne ProductID de la table Product 
    CRecordView::OnInitialUpdate();// appel implicite de CRecordset::Open 
    //... 
}

Voici le code de CRecordView::OnInitialUpdate() définit dans <DBVIEW.cpp>

Code C++ : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CRecordView::OnInitialUpdate() 
{ 
    CRecordset* pRecordset = OnGetRecordset(); 
    // recordset must be allocated already 
    ASSERT(pRecordset != NULL); 
  
    if (!pRecordset->IsOpen()) 
    { 
        CWaitCursor wait; 
        // ouverture du jeu de ligne par l'appel à OnInitialUpdate() 
        pRecordset->Open();  
    } 
    CFormView::OnInitialUpdate(); 
}

On peut ouvrir directement le jeu sans passer par OnInitialUpdate par un appel directe à Open() du jeu.

Mis à jour le 19 septembre 2005 Gabrielly

Proposer une nouvelle réponse sur la FAQ

Ce n'est pas l'endroit pour poser des questions, allez plutôt sur le forum de la rubrique pour ça


Réponse à la question

Liens sous la question
précédent sommaire suivant
 

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2024 Developpez Developpez LLC. Tous droits réservés Developpez LLC. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents et images sans l'autorisation expresse de Developpez LLC. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.