Hypercell ein ] Hypercell aus ] Zeige Navigation ] Verstecke Navigation ]
c++.de  
   
Forentreff 2012     
Bücher-Shop mit Amazon (Buchkategorien)C++ : Referenzen zu C++ : C++ Builder : Visual C++ : C# : Java : Spieleprogrammierung : Systemprogrammierung Linux : Software-Entwicklung : .NET : Compilertechnik : Algorithmen & Datenstrukturen : Objektorientierung : Entwurfsmuster : UML : eXtreme Programming : Scrum : Projektmanagement : Software-Testing : Datenbanken : Tom DeMarco : Dilbert : User Friendly
C/C++ Forum :: Die Artikel ::  Datenbankzugriff mit CRecordset (und CDatabase) - Teil 1     Zeige alle Beiträge auf einer Seite Auf Beitrag antworten
Autor Nachricht
estartu
Moderator

Benutzerprofil
Anmeldungsdatum: 05.09.2003
Beiträge: 11494
Beitrag estartu Moderator 18:06:16 06.11.2006   Titel:   Datenbankzugriff mit CRecordset (und CDatabase) - Teil 1            Zitieren

1 Einleitung

Wer mit der MFC arbeitet und eine Datenbank nutzen will, der hat mehrere Möglichkeiten, dies zu tun. Eine davon ist CRecordset und darum geht es in diesem Artikel.

Online ist kaum etwas zu finden und eine der wenigen (offline) Anleitungen für Anfänger ist Kapitel 14 aus "Visual C++6 in 21 Tagen".
Sie ist aber maximal für einen kleinen Einstieg geeignet und man wird schnell mit vielen Fragen alleingelassen. Dann gibt man entweder auf, oder man kämpft sich durch die MSDN und das Internet, um es nach und nach zu lernen.
Ich habe die zweite Möglichkeit gewählt und möchte meine Erkenntnisse jetzt an Sie weitergeben.

Im Folgenden lernen Sie u.a.
  • was der ganze automatisch erstellte Code bedeutet.
  • wie sie statische Recordsets benutzen um Daten zu lesen und zu manipulieren.
  • welche Stolperfallen es gibt und wie Sie sie vermeiden können.
  • wie sie Recordsets im Nachinein verändern können. (Teil 2)
  • wie Sie sich das Leben mit einer eigenen Basisklasse vereinfachen können. (Teil 2)

Da ich nicht alles erklären kann und will, habe ich häufig Links zur MSDN eingebaut, so dass Sie dort weiterlesen können.

2 Grundlegendes

Eine von CRecordset abgeleitete Klasse repräsentiert immer eine Tabelle bzw. einen View.
Views sind allerdings schreibgeschützt, daher verwende ich sie so selten wie möglich. Auch wenn Sie beim Erstellen der Recordsetklasse mehrere Tabellen auswählen, ist dies ein View.
Noch dazu kann es passieren, dass Sie viel mehr Datensätze erhalten als es eigentlich wären.
Das passiert, wenn Sie in der Datenbank keine Beziehungen zwischen den Tabellen gesetzt haben.

3 Was bedeutet der automatisch generierte Code?

Wenn Sie eine CRecordset-Klasse mit Hilfe des Assistenten erzeugen, finden Sie bereits eine Menge Code vor.
Damit Sie effektiv damit arbeiten können, sollten Sie aber auch verstehen, was Ihnen da serviert wird, denn nur so können Sie später auch selbst etwas verändern.

3.1 Klassenkopf

C/C++ Code:
class CDemoSet : public CRecordset
C/C++ Code:
class CDemoSet : public CRecordset
C/C++ Code:
class CDemoSet : public CRecordset
Diese Klasse ist von CRecordset abgeleitet.
Wenn Sie später eine eigene Basisklasse haben, ist es trotzdem einfacher, neue Recordsets per Assistent erzeugen zu lassen und dann den Code anzupassen.

Falls Sie die Recordsetklassen in einer Dll erstellen, wird wahrscheinlich der Include in der stdafx.h fehlen. Sie müssen ihn selbst eintragen:
C/C++ Code:
#include <afxdb.h>
C/C++ Code:
#include <afxdb.h>
C/C++ Code:
#include <afxdb.h>


3.2 Die Membervariablen für die Spalten

VC 2003:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
// Feld-/Parameterdaten

// Die Zeichenfolgentypen reflektieren den eigentlichen Datentyp des Datenbankfelds
// (CStringA für ANSI-Datentypen und CStringW für Unicode-Datentypen),
// um zu verhindern, dass der ODBC-Treiber nicht erforderliche
// Konvertierungen ausführt. Sie können diese Member zu  CString-Typen ändern
//, damit der ODBC-Treiber alle erforderlichen Konvertierungen ausführt.
// Hinweis: Sie müssen mindenstens die ODBC-Treiberversion 3.5 verwenden,
// um Unicode und die Konvertierungen zu unterstützen.


    long    m_ID;
    CStringA    m_Text;
    CTime    m_Erstellt;
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Feld-/Parameterdaten

// Die Zeichenfolgentypen reflektieren den eigentlichen Datentyp des Datenbankfelds
// (CStringA für ANSI-Datentypen und CStringW für Unicode-Datentypen),
// um zu verhindern, dass der ODBC-Treiber nicht erforderliche
// Konvertierungen ausführt. Sie können diese Member zu CString-Typen ändern
//, damit der ODBC-Treiber alle erforderlichen Konvertierungen ausführt.
// Hinweis: Sie müssen mindenstens die ODBC-Treiberversion 3.5 verwenden,
// um Unicode und die Konvertierungen zu unterstützen.


long m_ID;
CStringA m_Text;
CTime m_Erstellt;
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
// Feld-/Parameterdaten

// Die Zeichenfolgentypen reflektieren den eigentlichen Datentyp des Datenbankfelds
// (CStringA für ANSI-Datentypen und CStringW für Unicode-Datentypen),
// um zu verhindern, dass der ODBC-Treiber nicht erforderliche
// Konvertierungen ausführt. Sie können diese Member zu  CString-Typen ändern
//, damit der ODBC-Treiber alle erforderlichen Konvertierungen ausführt.
// Hinweis: Sie müssen mindenstens die ODBC-Treiberversion 3.5 verwenden,
// um Unicode und die Konvertierungen zu unterstützen.


    long    m_ID;
    CStringA    m_Text;
    CTime    m_Erstellt;

bzw. bei VC6
C/C++ Code:
    // Feld-/Parameterdaten
    //{{AFX_FIELD(CHideSet, CRecordset)

    long    m_ID;
    CString    m_Text;
    CTime    m_Erstellt;
    //}}AFX_FIELD
C/C++ Code:
// Feld-/Parameterdaten
//{{AFX_FIELD(CHideSet, CRecordset)

long m_ID;
CString m_Text;
CTime m_Erstellt;
//}}AFX_FIELD
C/C++ Code:
    // Feld-/Parameterdaten
    //{{AFX_FIELD(CHideSet, CRecordset)

    long    m_ID;
    CString    m_Text;
    CTime    m_Erstellt;
    //}}AFX_FIELD

Ich persönlich finde den Code von VC6 übersichtlicher, da er aufgeräumter ist. Bei VC2003 muss man selbst für Ordnung sorgen.
Beachten Sie bitte auch den Kommentar von VC2003.

3.3 Der Konstruktor MSDN
C/C++ Code:
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
CDemoSet::CDemoSet(CDatabase* pdb)
    : CRecordset(pdb)
{
    m_ID = 0;
    m_Text = "";
    m_Erstellt;
    m_nFields = 3;
    m_nDefaultType = snapshot;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
CDemoSet::CDemoSet(CDatabase* pdb)
: CRecordset(pdb)
{
m_ID = 0;
m_Text = "";
m_Erstellt;
m_nFields = 3;
m_nDefaultType = snapshot;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
CDemoSet::CDemoSet(CDatabase* pdb)
    : CRecordset(pdb)
{
    m_ID = 0;
    m_Text = "";
    m_Erstellt;
    m_nFields = 3;
    m_nDefaultType = snapshot;
}

Hier werden die Datenmember der einzelnen Spalten initialisiert.
m_nFields ist die Anzahl der Spalten des Recordsets. Hier muss man besonders aufpassen, wenn man die Tabelle später von Hand verändert. Vergisst man m_nFields, funktioniert es nicht.

3.4 GetDefaultConnect MSDN
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=Datenbankname;LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=Datenbankname;LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=Datenbankname;LANGUAGE=Deutsch");
}

Dies ist der Connectionstring.
Hier werden alle Daten festgelegt, die notwendig sind, um sich mit der Datenquelle zu verbinden. In meinem Fall ist es eine MSDE (Microsoft SQL Server).

3.5 GetDefaultSQL MSDN
C/C++ Code:
CString CDemoSet::GetDefaultSQL()
{
    return _T("[dbo].[Tabellenname]");
}
C/C++ Code:
CString CDemoSet::GetDefaultSQL()
{
return _T("[dbo].[Tabellenname]");
}
C/C++ Code:
CString CDemoSet::GetDefaultSQL()
{
    return _T("[dbo].[Tabellenname]");
}

Hier findet man den Tabellennamen der Tabelle(n), mit der (denen) das Recordset verbunden ist.
Das dbo.Tabellenname ist nur bei MS SQL so, daher kann es bei Ihnen etwas anders aussehen.

3.6 DoFieldExchange MSDN
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
void CDemoSet::DoFieldExchange(CFieldExchange* pFX)
{
    pFX->SetFieldType(CFieldExchange::outputColumn);
// Makros, z.B. RFX_Text() und RFX_Int(), sind vom Typ
// der Membervariablen abhängig, nicht vom Typ des Felds in der Datenbank.
// ODBC konvertiert den Spaltenwert automatisch in den angeforderten Typ.

    RFX_Long(pFX, _T("[ID]"), m_ID);
    RFX_Text(pFX, _T("[Text]"), m_Text);
    RFX_Date(pFX, _T("[Erstellt]"), m_Erstellt);
    RFX_Text(pFX, _T("[Bemerkung]"), m_strBemerkung, 500);
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
void CDemoSet::DoFieldExchange(CFieldExchange* pFX)
{
pFX->SetFieldType(CFieldExchange::outputColumn);
// Makros, z.B. RFX_Text() und RFX_Int(), sind vom Typ
// der Membervariablen abhängig, nicht vom Typ des Felds in der Datenbank.
// ODBC konvertiert den Spaltenwert automatisch in den angeforderten Typ.

RFX_Long(pFX, _T("[ID]"), m_ID);
RFX_Text(pFX, _T("[Text]"), m_Text);
RFX_Date(pFX, _T("[Erstellt]"), m_Erstellt);
RFX_Text(pFX, _T("[Bemerkung]"), m_strBemerkung, 500);
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
void CDemoSet::DoFieldExchange(CFieldExchange* pFX)
{
    pFX->SetFieldType(CFieldExchange::outputColumn);
// Makros, z.B. RFX_Text() und RFX_Int(), sind vom Typ
// der Membervariablen abhängig, nicht vom Typ des Felds in der Datenbank.
// ODBC konvertiert den Spaltenwert automatisch in den angeforderten Typ.

    RFX_Long(pFX, _T("[ID]"), m_ID);
    RFX_Text(pFX, _T("[Text]"), m_Text);
    RFX_Date(pFX, _T("[Erstellt]"), m_Erstellt);
    RFX_Text(pFX, _T("[Bemerkung]"), m_strBemerkung, 500);
}

DoFieldExchange regelt, welche Spalte in welche Membervariable übertragen wird und wie.
Beachten Sie bitte die letzte Zeile: Bei mehr als 255 Zeichen in einem String muss man die Maximallänge angeben, sonst wird automatisch abgeschnitten.

4 Zugriffsfunktionen

Der Assistent legt die Membervariablen als public an.
Das ist soweit ganz praktisch, wenn man etwas quick & dirty fertig bekommen will.
Bei kleinen Hilfstools lasse ich das auch oft so, da ich sonst für die Zugriffsfunktionen mehr Zeit aufwenden würde als für den Rest des Projektes.

Bei größeren Projekten sollte man sich aber die Zeit für die Zugriffsfunktionen nehmen, da man sie oft genug damit später einsparen kann. Vor allem bei der Fehlersuche helfen sie enorm.

Was eine Get- bzw. Set-Funktion ist, muss ich Ihnen nicht mehr erklären, aber ich möchte Ihnen einige kleine Tricks zeigen, mit denen man sich viel erleichtern kann.

4.1 Get-/SetZahl mit möglichem NULL-Wert

Wenn eine Spalte NULL sein kann, müssen Sie darauf auf jeden Fall achten.
Ich habe das mit einem Hilfswert (-1) gelöst, den man der besseren Lesbarkeit halber als define ablegen sollte.
C/C++ Code:
#define DB_NULL -1
C/C++ Code:
#define DB_NULL -1
C/C++ Code:
#define DB_NULL -1

C/C++ Code:
1
2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
inline long CDemoSet::GetZahl()
{
    if (IsFieldNull(&m_lZahl)) // Ist es NULL?
    {
        return DB_NULL; // Hilfswert zurückgeben
    }
    return m_lZahl;
}
C/C++ Code:
1
2
3
4
5
6
7
8
inline long CDemoSet::GetZahl()
{
if (IsFieldNull(&m_lZahl)) // Ist es NULL?
{
return DB_NULL; // Hilfswert zurückgeben
}
return m_lZahl;
}
C/C++ Code:
1
2
3
4
5
6
7
8
inline long CDemoSet::GetZahl()
{
    if (IsFieldNull(&m_lZahl)) // Ist es NULL?
    {
        return DB_NULL; // Hilfswert zurückgeben
    }
    return m_lZahl;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline bool CDemoSet::SetZahl(long f_lZahl)
{
    if (f_lZahl == DB_NULL) // Ist es der Hilfswert?
    {
        SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
    }
    else // es ist eine reguläre Zahl
    {
        if (f_lZahl > 30) // ist sie im Wertebereich? (opt)
        {
            return false; // Fehler!
        }
        m_lZahl = f_lZahl;
    }
    return true;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline bool CDemoSet::SetZahl(long f_lZahl)
{
if (f_lZahl == DB_NULL) // Ist es der Hilfswert?
{
SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
}
else // es ist eine reguläre Zahl
{
if (f_lZahl > 30) // ist sie im Wertebereich? (opt)
{
return false; // Fehler!
}
m_lZahl = f_lZahl;
}
return true;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
inline bool CDemoSet::SetZahl(long f_lZahl)
{
    if (f_lZahl == DB_NULL) // Ist es der Hilfswert?
    {
        SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
    }
    else // es ist eine reguläre Zahl
    {
        if (f_lZahl > 30) // ist sie im Wertebereich? (opt)
        {
            return false; // Fehler!
        }
        m_lZahl = f_lZahl;
    }
    return true;
}

In der Set-Funktion wird gleich noch eine weitere Aufgabe übernommen:
Der Wertebereich wird überprüft. Sollte dieser egal sein, können Sie die if-Anweisung natürlich weglassen.

4.2 SetZahl mit Controlparameter

Wenn Sie je nachdem, was aus einer ComboBox (Dropdown-Listenfeld) gewählt wurde, einen Wert speichern möchten, kann es sehr nervig werden, ständig die Abfragen wieder und wieder zu tippen. Angenehmer wird es z.B. so:
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool CDemoSet::SetZahl(CComboBox& f_cbxZahl)
{
    int nSel = f_cbxZahl.GetCurSel();
    if (nSel == -1) // Ist nichts gewählt?
    {
        SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
    }
    else // es ist etwas gewählt
    {
        // Je nach Anwendung der Combobox entweder
        m_lZahl = nSel;
        // oder
        m_lZahl = f_cbxZahl.GetItemData(nSel);
    }
    return true;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool CDemoSet::SetZahl(CComboBox& f_cbxZahl)
{
int nSel = f_cbxZahl.GetCurSel();
if (nSel == -1) // Ist nichts gewählt?
{
SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
}
else // es ist etwas gewählt
{
// Je nach Anwendung der Combobox entweder
m_lZahl = nSel;
// oder
m_lZahl = f_cbxZahl.GetItemData(nSel);
}
return true;
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
bool CDemoSet::SetZahl(CComboBox& f_cbxZahl)
{
    int nSel = f_cbxZahl.GetCurSel();
    if (nSel == -1) // Ist nichts gewählt?
    {
        SetFieldNull(&m_lZahl); // Zahl auf NULL setzen
    }
    else // es ist etwas gewählt
    {
        // Je nach Anwendung der Combobox entweder
        m_lZahl = nSel;
        // oder
        m_lZahl = f_cbxZahl.GetItemData(nSel);
    }
    return true;
}

Ihnen ist sicherlich aufgefallen, dass ich einmal mit ItemData arbeite und einmal nicht...
Da GetCurSel oft unzuverlässig ist (z.B. bei Sortierungen oder Umstellungen), arbeite ich lieber mit ItemData. Das kann ich besser kontrollieren.
Natürlich können Sie auch eine passende GetZahl-Funktion schreiben, die dann den richtigen Eintrag aus der Liste wählt.
Da dies aber nur eine kleine Fingerübung mit einer Schleife und einer if ist, überlasse ich das Ihnen. ;)

5 Datenzugriffe

Nun haben Sie genug Grundlagen um mit CRecordset zu arbeiten.
Sie werden sehen, es ist nicht schwer.

Zuerst brauchen Sie eine Instanz Ihrer Recordsetklasse. Das kann eine lokale oder auch eine Membervariable der Klasse sein.
Falls Sie mit CRecordView arbeiten, funktioniert alles so ähnlich. Sie werden es wiedererkennen. Ich mag CRecordView nicht, weil er mich in der Anwendung des Recordsets zu sehr einschränkt und alles so kompliziert erscheinen lässt.

Eine der größten Hürden für Anfänger ist es, sich von CRecordView zu lösen.
Einige denken sogar, man könne CRecordset nicht außerhalb von CRecordView verwenden. Das ist völliger Unsinn, Sie können CRecordset verwenden, wo Sie es brauchen bzw. wo Sie wollen.

5.1 Eine Tabelle einlesen

Auch wenn Sie vermutlich mit einer leeren Tabelle starten werden, möchte ich doch zuerst erklären, wie Sie Daten aus einer Tabelle abfragen können.

In diesem kleinen Beispiel haben wir eine Tabelle (Farben) mit zwei Spalten: ID (Zahl) und Bezeichnung (Text).
Den Inhalt dieser Tabelle möchten wir in einer ComboBox (m_cbxFarben) anzeigen.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
1
2
3
4
5
6
7
8
9
10
11
12
13
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

if (!farbSet.IsBOF()) // Ist es nicht leer?
{
    while (!farbSet.IsEOF()) // Sind wir noch nicht fertig?
    {
        int nIdx = m_cbxFarben.AddString(farbSet.GetBezeichnung()); // Die Bezeichnung in die Liste packen
        m_cbxFarben.SetItemData(nIdx, farbSet.GetID()); // Die ID in ItemData merken

        farbSet.MoveNext(); // Nächste Zeile
    }
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

if (!farbSet.IsBOF()) // Ist es nicht leer?
{
while (!farbSet.IsEOF()) // Sind wir noch nicht fertig?
{
int nIdx = m_cbxFarben.AddString(farbSet.GetBezeichnung()); // Die Bezeichnung in die Liste packen
m_cbxFarben.SetItemData(nIdx, farbSet.GetID()); // Die ID in ItemData merken

farbSet.MoveNext(); // Nächste Zeile
}
}
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
13
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

if (!farbSet.IsBOF()) // Ist es nicht leer?
{
    while (!farbSet.IsEOF()) // Sind wir noch nicht fertig?
    {
        int nIdx = m_cbxFarben.AddString(farbSet.GetBezeichnung()); // Die Bezeichnung in die Liste packen
        m_cbxFarben.SetItemData(nIdx, farbSet.GetID()); // Die ID in ItemData merken

        farbSet.MoveNext(); // Nächste Zeile
    }
}

Das ist eine absolute Minimallösung, aber sie funktioniert und die gröbsten Fehler sind abgefangen.
Wenn Sie eine bessere Fehlerbehandlung einbauen möchten, lesen Sie diese Zusammenfassung von Artchi und schauen Sie dann in die MSDN, welche Exceptions Sie fangen müssen.

5.1.1 Filtern

In SQL kennen Sie sicher:
Code:
SELECT * FROM Tabelle_Soundso WHERE Bedingung
Code:
SELECT * FROM Tabelle_Soundso WHERE Bedingung
Code:
SELECT * FROM Tabelle_Soundso WHERE Bedingung

Mit CRecordset geht das natürlich auch, nur heißt es hier m_strFilter und ist ein CString.

Füllen Sie diesen String vor dem Datenholen einfach mit dem, was Sie bei SQL hinter das WHERE schreiben würden.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
long lID = 5;
farbSet.m_strFilter.Format(_T("[ID] = %d"), lID); // Bedingung setzen
// WHERE ID = 5
// oder

CString strBezeichnung = _T("Gelb");
farbSet.m_strFilter.Format(_T("[Bezeichnung] = \'%s\'"), strBezeichnung);
// WHERE Bezeichnung = 'Gelb'
farbSet.Open();
//...
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
long lID = 5;
farbSet.m_strFilter.Format(_T("[ID] = %d"), lID); // Bedingung setzen
// WHERE ID = 5
// oder

CString strBezeichnung = _T("Gelb");
farbSet.m_strFilter.Format(_T("[Bezeichnung] = \'%s\'"), strBezeichnung);
// WHERE Bezeichnung = 'Gelb'
farbSet.Open();
//...
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
long lID = 5;
farbSet.m_strFilter.Format(_T("[ID] = %d"), lID); // Bedingung setzen
// WHERE ID = 5
// oder

CString strBezeichnung = _T("Gelb");
farbSet.m_strFilter.Format(_T("[Bezeichnung] = \'%s\'"), strBezeichnung);
// WHERE Bezeichnung = 'Gelb'
farbSet.Open();
//...

Die eckigen Klammern können Sie auch weglassen. Es ist aber besser, sie zu verwenden.
Es ist ganz einfach, m_strFilter muss wirklich nur genau so aussehen, wie Sie es aus SQL kennen.
Sie können alle Befehle verwenden, die Sie kennen.
Wildcards und Unterabfragen sind ebenfalls kein Problem.

5.1.2 Sortieren

Das Sortieren funktioniert ähnlich wie das Filtern, nur dass hier die Variable m_strSort heißt und Sie den Teil aus dem SQL-Statement hinter ORDER BY verwenden.
C/C++ Code:
CFarbenSet farbSet;
farbSet.m_strSort = _T("[Bezeichnung]");
// ORDER BY Bezeichnung (aufsteigend)
farbSet.m_strSort = _T("[Bezeichnung] DESC");
// ORDER BY Bezeichnung (absteigend)
farbSet.Open();
//...
C/C++ Code:
CFarbenSet farbSet;
farbSet.m_strSort = _T("[Bezeichnung]");
// ORDER BY Bezeichnung (aufsteigend)
farbSet.m_strSort = _T("[Bezeichnung] DESC");
// ORDER BY Bezeichnung (absteigend)
farbSet.Open();
//...
C/C++ Code:
CFarbenSet farbSet;
farbSet.m_strSort = _T("[Bezeichnung]");
// ORDER BY Bezeichnung (aufsteigend)
farbSet.m_strSort = _T("[Bezeichnung] DESC");
// ORDER BY Bezeichnung (absteigend)
farbSet.Open();
//...

Auch hier gilt: Sie dürfen alles verwenden, was SQL Ihnen an Möglichkeiten bietet.

5.2 Eine neue Zeile schreiben

Nun soll die Tabelle aber nicht ewig leer bleiben.
Um eine neue Zeile hinzuzufügen brauchen Sie eine geöffnete Instanz Ihrer Recordsetklasse.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
1
2
3
4
5
6
7
8
9
10
11
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

farbSet.AddNew(); // Neue leere Zeile anfügen
// Zeile füllen

farbSet.SetBezeichnung(_T("Blau"));
if (!farbSet.Update()) // Die Änderungen schreiben
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery(); // Bei Bedarf neu laden
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

farbSet.AddNew(); // Neue leere Zeile anfügen
// Zeile füllen

farbSet.SetBezeichnung(_T("Blau"));
if (!farbSet.Update()) // Die Änderungen schreiben
{
AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery(); // Bei Bedarf neu laden
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
CFarbenSet farbSet; // Instanz anlegen
farbSet.Open(); // Die Daten holen

farbSet.AddNew(); // Neue leere Zeile anfügen
// Zeile füllen

farbSet.SetBezeichnung(_T("Blau"));
if (!farbSet.Update()) // Die Änderungen schreiben
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery(); // Bei Bedarf neu laden


5.3 Eine Zeile verändern

Wenn Sie eine Zeile verändern möchten, müssen Sie zuerst ihre Daten in das Recordset laden. Der Rest funktioniert wie in Kapitel 5.2.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Gruen"); // Cursor positionieren (siehe 5.5)

farbSet.Edit(); // Diese Zeile bearbeiten
// Daten verändern

farbSet.SetBezeichnung(_T("Grün"));
if (!farbSet.Update())
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Gruen"); // Cursor positionieren (siehe 5.5)

farbSet.Edit(); // Diese Zeile bearbeiten
// Daten verändern

farbSet.SetBezeichnung(_T("Grün"));
if (!farbSet.Update())
{
AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
11
12
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Gruen"); // Cursor positionieren (siehe 5.5)

farbSet.Edit(); // Diese Zeile bearbeiten
// Daten verändern

farbSet.SetBezeichnung(_T("Grün"));
if (!farbSet.Update())
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();


5.4 Eine Zeile löschen

Eine Zeile löschen ist dem Verändern sehr ähnlich.
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Grün");

farbSet.Delete(); // Diese Zeile löschen
if (!farbSet.Update())
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Grün");

farbSet.Delete(); // Diese Zeile löschen
if (!farbSet.Update())
{
AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();
C/C++ Code:
1
2
3
4
5
6
7
8
9
10
CFarbenSet farbSet;
farbSet.Open();
farbSet.Suche("Grün");

farbSet.Delete(); // Diese Zeile löschen
if (!farbSet.Update())
{
    AfxMessageBox(_T("Es wurde nicht gespeichert."));
}
farbSet.Requery();


5.5 Eine Zeile suchen

Oft müssen Sie den Cursor auf eine bestimmte Zeile positionieren. Daher bietet es sich an, eine Funktion dafür zu schreiben.
C/C++ Code:
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
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
bool CFarbenSet::Suche(CString strBezeichnung)
{
    // Stimmt die Kombination?
    if ((m_strBezeichnung == strBezeichnung) && (!IsEOF()))
    {
        // Schon gefunden
        return true;
    }
    else
    {
        // Einmal über alle Datensätze laufen
        if (IsBOF())
        {
            // Das Recordset ist leer
        }
        else
        {
            // Vorne anfangen
            MoveFirst();
            while(!IsEOF())
            {
                // Stimmt die Kombination?
                if (m_strBezeichnung == strBezeichnung)
                {
                    // Fertig und raus
                    return true;
                }
                MoveNext(); // Weiter
            }
        }
    }
}
C/C++ Code:
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
bool CFarbenSet::Suche(CString strBezeichnung)
{
// Stimmt die Kombination?
if ((m_strBezeichnung == strBezeichnung) && (!IsEOF()))
{
// Schon gefunden
return true;
}
else
{
// Einmal über alle Datensätze laufen
if (IsBOF())
{
// Das Recordset ist leer
}
else
{
// Vorne anfangen
MoveFirst();
while(!IsEOF())
{
// Stimmt die Kombination?
if (m_strBezeichnung == strBezeichnung)
{
// Fertig und raus
return true;
}
MoveNext(); // Weiter
}
}
}
}
C/C++ Code:
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
bool CFarbenSet::Suche(CString strBezeichnung)
{
    // Stimmt die Kombination?
    if ((m_strBezeichnung == strBezeichnung) && (!IsEOF()))
    {
        // Schon gefunden
        return true;
    }
    else
    {
        // Einmal über alle Datensätze laufen
        if (IsBOF())
        {
            // Das Recordset ist leer
        }
        else
        {
            // Vorne anfangen
            MoveFirst();
            while(!IsEOF())
            {
                // Stimmt die Kombination?
                if (m_strBezeichnung == strBezeichnung)
                {
                    // Fertig und raus
                    return true;
                }
                MoveNext(); // Weiter
            }
        }
    }
}


6 Funktionsübersicht MSDN

Open MSDN

Öffnet das Recordset und lädt die Daten aus der Datenbank.
Der Cursor steht auf dem ersten Datensatz, sofern einer vorhanden ist.
Schlägt fehl, wenn das Recordset bereits offen ist.

Requery MSDN

Lädt die Daten aus der Datenbank neu in das Recordset.
Schlägt fehl, wenn das Recordset (noch) geschlossen ist.

Close MSDN

Schließt das Recordset.

AddNew MSDN

Fügt eine neue Zeile an das Recordset an, die man danach füllen und speichern kann.
Das Recordset muss offen sein.

Edit MSDN

Schaltet die aktuelle Zeile zum Ändern frei, danach kann man ihre Inhalte ändern und speichern.
Das Recordset muss offen sein.

Delete MSDN

Löscht die aktuelle Zeile.
Danach muss noch gespeichert werden.
Das Recordset muss offen sein.

Update MSDN

Speichert das aktuelle Recordset in der Datenbank.
Das Recordset muss offen sein.

Move

Das Recordset muss offen sein.
Ist das Recordset leer, gibt es einen Fehler.
  • Move MSDN
    Bewegt den Cursor wie angegeben.
  • MoveFirst MSDN
    Setzt den Cursor auf die erste Zeile.
  • MovePrev MSDN
    Setzt den Cursor auf die voherige Zeile.
  • MoveNext MSDN
    Setzt den Cursor auf die nächste Zeile.
  • MoveLast MSDN
    Setzt den Cursor auf die letzte Zeile.


IsBOF MSDN

Gibt TRUE zurück, wenn der Cursor vor dem ersten Datensatz steht.
Das Recordset muss offen sein.

IsEOF MSDN

Gibt TRUE zurück, wenn der Cursor hinter dem letzten Datensatz steht.
Das Recordset muss offen sein.

IsOpen MSDN

Gibt TRUE zurück, wenn das Recordset offen ist.

Literatur:

ISBN:382668107X S. 629 ff
ISBN:3827220351 Kapitel 14
Grundlagen zu Exceptions

_________________
Das c-plusplus.de-Magazin sucht Mitmacher --- Die Artikel --- meine Homepage


Zuletzt bearbeitet von estartu am 16:30:55 20.09.2007, insgesamt 4-mal bearbeitet
Verwegener
Mitglied

Benutzerprofil
Anmeldungsdatum: 11.02.2002
Beiträge: 90
Beitrag Verwegener Mitglied 09:26:58 17.11.2006   Titel:   Teil 2            Zitieren

Moin,
nachdem ich jetzt Teil 1 verstanden habe, würde ich gern weiterlesen.

Wann wird das wohl gehen ??

Mfg
estartu
Moderator

Benutzerprofil
Anmeldungsdatum: 05.09.2003
Beiträge: 11494
Beitrag estartu Moderator 14:56:18 17.11.2006   Titel:   Re: Teil 2            Zitieren

Verwegener schrieb:
Wann wird das wohl gehen ??
Irgendwann nächstes Jahr.
Dieses Jahr komme ich wohl nicht mehr zum Schreiben. :(

Du kannst mir aber trotzdem gerne mal mailen, was so vorkommen sollte. (Das dürfen alle anderen auch.)
Es soll ja so vollständig wie möglich sein. :)

_________________
Das c-plusplus.de-Magazin sucht Mitmacher --- Die Artikel --- meine Homepage
Fruggle
Unregistrierter




Beitrag Fruggle Unregistrierter 02:30:11 14.01.2007   Titel:   Einfach nur Spitze!            Zitieren

Ich wünsche mir auch einen zweiten Teil :-)

Vielen vielen Dank für diesen Artikel. Er hat mir unglaublich geholfen
den Weg in Richtung Datenbank zu gehen und zu verstehen.

:live:
:o
Unregistrierter




Beitrag :o Unregistrierter 06:36:02 14.01.2007   Titel:              Zitieren

Im MFC Forum meinten vor ein paar Tagen 2 Experten das CRecordset nicht zu empfehlen ist.
estartu
Moderator

Benutzerprofil
Anmeldungsdatum: 05.09.2003
Beiträge: 11494
Beitrag estartu Moderator 09:10:17 14.01.2007   Titel:              Zitieren

:o schrieb:
Im MFC Forum meinten vor ein paar Tagen 2 Experten das CRecordset nicht zu empfehlen ist.
Tja, wer was anderes kann, darf gerne darüber schreiben. ;)

_________________
Das c-plusplus.de-Magazin sucht Mitmacher --- Die Artikel --- meine Homepage
Oka81
Unregistrierter




Beitrag Oka81 Unregistrierter 09:27:01 27.03.2007   Titel:   Recordview            Zitieren

Hallo, ich würde auch gerne eine Anwendung erstellen ohne CRecordview, aber wenn ich mit dem Klassenassistenten erstelle und am Ende statt Recordview eine andere View erstelle dann ist leider keine Set Klasse mehr vorhanden die von CRecordset abgeleitet wird.

Wenn ich es nachträglich erstelle die Klasse funzt gar nicht´s mehr. gibt es eine Lösung?

danke im Voraus.

Mfg

Özkan
estartu
Moderator

Benutzerprofil
Anmeldungsdatum: 05.09.2003
Beiträge: 11494
Beitrag estartu Moderator 13:53:06 03.04.2007   Titel:   Re: Recordview            Zitieren

Oka81 schrieb:
Wenn ich es nachträglich erstelle die Klasse funzt gar nicht´s mehr. gibt es eine Lösung?
Klar, aber stell die Frage bitte im MFC Forum, denn die Lösung könnte länger werden.

Zuerst einmal solltest du "funzt gar nicht´s mehr" genauer erklären, bei mir geht es nämlich.

_________________
Das c-plusplus.de-Magazin sucht Mitmacher --- Die Artikel --- meine Homepage


Zuletzt bearbeitet von estartu am 13:55:40 03.04.2007, insgesamt 1-mal bearbeitet
Asmo
Unregistrierter




Beitrag Asmo Unregistrierter 10:41:30 21.08.2008   Titel:   Re: Teil 2            Zitieren

[quote="estartu"]
Verwegener schrieb:
Irgendwann nächstes Jahr.


Dein nächstes Jahr ist bald zuende ;-) Wann kommt denn nun was???? *grins*

Liebe Grüsse,

Asmo
estartu
Moderator

Benutzerprofil
Anmeldungsdatum: 05.09.2003
Beiträge: 11494
Beitrag estartu Moderator 08:54:38 27.08.2008   Titel:              Zitieren

Momentan keine Chance. :(
Ich komme zu nix und zu dem Thema was ich gerne behandeln würde bin ich immer noch nicht schlauer: Dynamisch Tabellen anbinden.
Sollte sich ein anderer Autor finden würde ich mich aber freuen.

_________________
Das c-plusplus.de-Magazin sucht Mitmacher --- Die Artikel --- meine Homepage
-1 Hack
Unregistrierter




Beitrag -1 Hack Unregistrierter 14:52:36 27.08.2008   Titel:              Zitieren

Ich finde deinen -1 Hack für DB_NULL nicht gerade toll. Ich kann zwar verstehen, dass man mit der MFC zwangsweise in den Frickelmode schaltet, aber ich will trotzdem eine Alternative aufzeigen: boost::optional, das ist ein Wrapper um einen Typ welchen man überprüfen kann ob der gewrappte Typ sinnvoll initialisiert ist, d.h. man kann damit den NULL-Wert aus der DB sehr schön nachbilden im Programm :)
Dan Diggler
Unregistrierter




Beitrag Dan Diggler Unregistrierter 16:40:48 23.11.2010   Titel:              Zitieren

Könnte mir jemand sagen, wie ich in den ConnectionString eine vorher initialisierte Variable einfüge?


Beispiel:

C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=" + Datenbankname+ ";LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=" + Datenbankname+ ";LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=" + Datenbankname+ ";LANGUAGE=Deutsch");
}


Das klappt z.b. nicht. Auch mit & klappt es nicht. Danke schonmal im voraus!
GPC
Moderator

Benutzerprofil
Anmeldungsdatum: 11.07.2004
Beiträge: 6298
Beitrag GPC Moderator 19:24:14 23.11.2010   Titel:              Zitieren

Ich weiß nicht mehr ganz sicher, ob das hier klappt:
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=") + Datenbankname + _T(";LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=") + Datenbankname + _T(";LANGUAGE=Deutsch");
}
C/C++ Code:
CString CDemoSet::GetDefaultConnect()
{
    return _T("DSN=Datenquelle;UID=Username;PWD=passwort;APP=Microsoft\x00ae Visual Studio .NET;WSID=PC-NAME;DATABASE=") + Datenbankname + _T(";LANGUAGE=Deutsch");
}


Ansonsten: halte den Connectionstring als CString vor und füge den Datenbanknamen mit der CString::Format-Methode ein
Dan Diggler
Unregistrierter




Beitrag Dan Diggler Unregistrierter 14:53:19 24.11.2010   Titel:              Zitieren

Vielen Dank! Mit der CString::Format-Methode funktioniert es :)
C/C++ Forum :: Die Artikel ::  Datenbankzugriff mit CRecordset (und CDatabase) - Teil 1   Auf Beitrag antworten

Zeige alle Beiträge auf einer Seite




Nächstes Thema anzeigen
Vorheriges Thema anzeigen
Sie können keine Beiträge in dieses Forum schreiben.
Sie können auf Beiträge in diesem Forum antworten.
Sie können Ihre Beiträge in diesem Forum nicht bearbeiten.
Sie können Ihre Beiträge in diesem Forum nicht löschen.
Sie können an Umfragen in diesem Forum nicht mitmachen.

Powered by phpBB © 2001, 2002 phpBB Group :: FI Theme

c++.de ist Teilnehmer des Partnerprogramms von Amazon Europe S.à.r.l. und Partner des Werbeprogramms, das zur Bereitstellung eines Mediums für Websites konzipiert wurde, mittels dessen durch die Platzierung von Werbeanzeigen und Links zu amazon.de Werbekostenerstattung verdient werden kann.

Die Vervielfältigung der auf den Seiten www.c-plusplus.de, www.c-plusplus.info, www.c-sar.de, www.c-plusplus.net und www.baeckmann.de enthaltenen Informationen ohne eine schriftliche Genehmigung des Seitenbetreibers ist untersagt (vgl. §4 Urheberrechtsgesetz). Die Nutzung und Änderung der vorgestellten Strukturen und Verfahren in privaten und kommerziellen Softwareanwendungen ist ausdrücklich erlaubt, soweit keine Rechte Dritter verletzt werden. Der Seitenbetreiber übernimmt keine Gewähr für die Funktion einzelner Beiträge oder Programmfragmente, insbesondere übernimmt er keine Haftung für eventuelle aus dem Gebrauch entstehenden Folgeschäden.