Server room

Adatbázis kapcsolatok kezelése (2. rész)

Az előző bejegyzésben egy univerzálisan használható, adatbázis kapcsolatot kezelő osztályról volt szó. A gondolatmenetet folytatva és kiegészítve egy újabb, az adatbázis adatainak lekérdezését támogató osztályt készítünk.

A Recordset

A recordset objektum az adatbázis rekordjainak egy csoportját tartalmazza, ez lehet egy tábla az adatbázisból, vagy lehet egy (akár komplex) SQL lekérdezés eredménye. Ezek kezeléséhez szintén hozzunk létre egy osztályt AdoRecordset néven. Ez az osztály lesz felelős

  • a recordset kapcsolat létrehozásáért,
  • a lekérdezés végrehajtásáért,
  • a kapcsolat automatikus lezárásáért és
  • a hibák riportálásáért.

Mezők

Négy változónk lesz:

  • Connection – egy aktív adatbázis kapcsolat szükséges a lekérdezéshez;
  • Recordset – ez az ADO ‘Recordset’ típusú objektuma;
  • Error – boolean típusú változó, jelzi, ha hiba történt végrehajtáskor;
  • LastError – hiba esetén ez tartalmazza szövegként a hiba leírását.

A mezőket itt is privátként definiáljuk és kívülről tulajdonságokon keresztül érhetők el. A kódrészlet:

Private Type TAdoRecordset
     Connection As ADODB.Connection
     Recordset  As ADODB.Recordset
     LastError  As String
     Error      As Boolean
 End Type
 Private this As TAdoRecordset
 
 Public Property Get Self() As AdoRecordset
     Set Self = Me
 End Property
 
 Public Property Get Connection() As ADODB.Connection
     Set Connection = this.Connection
 End Property
 Public Property Set Connection(ByRef obj As ADODB.Connection)
     Set this.Connection = obj
 End Property

 Public Property Get Recordset() As ADODB.Recordset
     Set Recordset = this.Recordset
 End Property
 Public Property Set Recordset(ByVal obj As ADODB.Recordset)
     Set this.Recordset = obj
 End Property

 Public Property Get LastError() As String
     LastError = this.LastError
 End Property
 Public Property Let LastError(ByVal Value As String)
     this.LastError = Value
 End Property

 Public Property Get Error() As Boolean
     Error = this.Error
 End Property
 Public Property Let Error(ByVal Value As Boolean)
     this.Error = Value
 End Property

Eljárások

Egyelőre négy eljárást hozunk létre:

  • CreateFromSQL – ez egy parametrizált konstruktor eljárás lesz (magyarázatot lásd később), ezzel tudunk egy tetszőleges, SQL nyelven írt lekérdezést futtatni;
  • CreateFromTable – szintén egy parametrizált konstruktor eljárás, ezzel tudunk egy táblát (egy az egyben) Recordset-ként megnyitni;
  • CreateRecordset – ez nyitja meg ténylegesen a Recordset kapcsolatot a konstruktor eljárásoktól kapott paraméterek alapján, illetve ez az eljárás tartalmazni fog egy, a korábbihoz hasonló hibakezelő részt;
  • CloseAdoRecordset – ezzel tudjuk a recordset kapcsolatot lezárni, valamint a Connection objektumra való hivatkozást megszüntetni.

Ezeken kívül szükségünk lesz az osztály destruktor (Class_Terminate) eljárására.

Először nézzük a Recordset kapcsolat létrehozásáért felelős eljárást:

Private Sub CreateRecordset(ByRef rs As ADODB.Recordset, _
                            ByVal CommandType As ADODB.CommandTypeEnum, _ 
                            ByVal Source As String, _
                            ByRef Error_ As Boolean, _
                            ByRef LastError_ As String)
    
    On Error GoTo ADOError
    Error_ = False

    'kapcsolat létrehozása
    With rs
        .Source = Source
        Select Case CommandType
            Case ADODB.adCmdText
                .CursorType = adOpenStatic
                .CursorLocation = adUseClient
                .LockType = adLockOptimistic
            Case ADODB.adCmdTable
                .CursorType = adOpenStatic
                .CursorLocation = adUseClient
                .LockType = adLockOptimistic
        End Select
        .Open Options:=CommandType
    End With
    
    'kapcsolat ellenőrzése
    If Not rs.State = adStateOpen Then
        Error_ = True
        LastError_ = "A Recordset létrehozása nem sikerült"
    Else
        Debug.Print "A Recordset létrejött" ' teszteléshez
    End If

CleanExit:
    Exit Sub
ADOError:
    Error_ = True
    Dim strErr As String
 
    ' VB által generált hibaüzenet
    strErr = Application.WorksheetFunction.Concat( _
                 strErr, vbCrLf, _
                 "VB Error # ", Str(Err.Number), vbCrLf, _ 
                 "   Generated by ", Err.Source, vbCrLf, _
                 "   Description  ", Err.Description _ )
    
    ' ADO API által generált hibaüzenetek
    Dim AdoErrors As ADODB.Errors
    Set AdoErrors = this.Connection.Errors
    Dim ADOError As ADODB.Error
    For Each ADOError In AdoErrors
        With ADOError
            strErr = Application.WorksheetFunction.Concat( _
                strErr, vbCrLf, _
                "   ADO Error   #", .Number, vbCrLf, _
                "   Description  ", .Description, vbCrLf, _
                "   Source       ", .Source _
            )
        End With
    Next ADOError
    LastError_ = strErr
    Err.Clear
    Resume CleanExit
 End Sub

Az eljárás paraméterei:

  • rs: inicializált, de még üres (nem megnyitott) Recordset objektum;
  • CommandType: meghatározza, hogy milyen típusú lekérdezést futtatunk. Az eljárás által kezelt értékek lehetnek adCmdText ha SQL parancsot hajtunk végre vagy adCmdTable ha egy táblát olvasunk be egy az egyben a tábla neve alapján;
  • Source: ez a String típusú paraméter tartalmazza a lekérdezés típusa alapján vagy az SQL utasítást vagy a tábla nevét;
  • Error_ és LastError_: azonos tartalmú változók, mint amik az osztály mezői között is szerepelnek hasonló névvel.

Az eljárás első felében a kapott paraméterek alapján beállítjuk, majd megnyitjuk a Recordsetet. A Select Case blokkban lehetőségünk van a CommandType alapján különböző Cursor és Lock tulajdonságok beállítására (jelenleg azonos beállítások szerepelnek a kódban). Az eljárás második fele pedig a hibakezelő utasítássor, ami majdnemmegegyezik azzal, mint amit az AdoConnection osztályban használtunk.

Mielőtt a konstruktor eljárások részleteibe belemennénk, tegyünk egy kitérőt.
A VBA osztálymodul saját konstruktor eljárása (Class_Initialize) nem ad lehetőséget paraméterek megadására. Tehát például ez nem működik:
Private Sub Class_Initialize (ByVal ValamiParameter As String)
Egy kis trükközéssel azonban tudunk parametrizált konstruktor eljárást is készíteni. Az ilyen konstruktorral rendelkező osztály nagyon leegyszerűsített váza (mezők/tulajdonságok és egyéb eljárások nélkül) így néz ki:

Public Property Get Self() As OsztalyNeve
    Set Self = Me
End Property

Public Function Create (ByVal ValamiParameter As String) As OsztalyNeve
    With New OsztalyNeve
        '[...] műveletek a paraméterrel
        Set Create = .Self
    End With
End Function

Ahhoz, hogy a dolog működjön, még szükséges egy pár lépésből álló trükk:

  • (A munkafüzet mentése után) töröljük az osztályt:
    a Project Explorerben jobb klikk az osztályon > Remove OsztalyNeve….
    A felugró ablakban válasszuk ki, hogy Igen, ki akarjuk exportálni az osztályt;
  • A kiexportált .cls kiterjesztésű fájlt nyissuk meg egy szerkesztővel (pl. Notepad);
  • Az eleje felé keressük meg a következő sort:
    Attribute VB_PredeclaredId = False
    és írjuk át True-ra:
    Attribute VB_PredeclaredId = True
  • Mentsük el a fájlt
  • Importáljuk vissza az osztályt az munkafüzetbe:
    jobb klikk a Project Explorer-ben > Import File…

A kitérő után lássuk a konstruktor eljárásokat. Mivel parametrizált konstruktor eljárások lesznek, át kell először állítani az osztálymodul PredeclaredId értékét igazra, az előző bekezdésben leírtak alapján.
A két eljárás nagyon hasonló, néhány változóban lesz csak eltérés. Paraméterként mindkettőnél meg kell adni a hivatkozást egy élő ADODB kapcsolatra, valamint egy SQL utasítást vagy egy adatbázis tábla nevét.

A kettő nagyon hasonló, néhány változóban lesz csak eltérés. Paraméterként mindkettőnél meg kell adni a hivatkozást egy élő ADODB kapcsolatra, valamint egy SQL utasítást vagy egy adatbázis tábla nevét.

Public Function CreateFromSQL(ByRef Connection As ADODB.Connection, _
                              ByVal SQL_String As String)
    Dim Error_ As Boolean
    Dim LastError_ As String
    With New AdoRecordset
        If Not Connection Is Nothing Then
            If Connection.State = adStateOpen Then
                Set .Connection = Connection
                Set .Recordset = New ADODB.Recordset
                .Recordset.ActiveConnection = Connection
                If Not SQL_String = vbNullString Then
                    CreateRecordset .Recordset, SQL_String, _
                                    ADODB.adCmdText, Error_, LastError_
                    .Error = Error_
                    .LastError = LastError_
                Else
                    .Error = True: .LastError = "Nincs SQL utasítás megadva"
                End If
            Else
                .Error = True: .LastError = "A kapcsolat nincs megnyitva"
            End If
        Else
            .Error = True: .LastError = "Nincs kapcsolat"
        End If
        Set  CreateFromSQL = .Self
    End With
End Function

Public Function CreateFromTable(ByRef Connection As ADODB.Connection, _
                                ByVal TableName As String) As AdoRecordset
    Dim Error_ As Boolean
    Dim LastError_ As String
    With New AdoRecordset
        If Not Connection Is Nothing Then
            If Connection.State = adStateOpen Then
                Set .Connection = Connection
                Set .Recordset = New ADODB.Recordset
                .Recordset.ActiveConnection = Connection
                If Not TableName = vbNullString Then
                    CreateRecordset .Recordset, TableName, _
                                    ADODB.adCmdTable, Error_, LastError_
                    .Error = Error_
                    .LastError = LastError_
                Else
                    .Error = True: .LastError = "Nincs tábla név megadva"
                End If
            Else
                .Error = True: .LastError = "A kapcsolat nincs megnyitva"
            End If
        Else
            .Error = True: .LastError = "Nincs kapcsolat"
        End If
        Set  CreateFromTable = .Self
    End With
End Function 

A fenti eljárások hasonlóan épülnek fel: az elején végzünk néhány ellenőrzést: van-e élő adatbázis kapcsolat, van-e paraméterként SQL utasítás vagy tábla név megadva. Ezután a CreateRecordset eljárás meghívásával ténylegesen létrehozzuk az adatállományt. Ha menet közben bármilyen hiba felmerül, akkor az látszani fog az osztály Error és LastError mezők értékében.

A kapcsolat lezárását kezelő eljárás így néz ki:

 
Private Sub CloseAdoRecordset()
    If Not this.Recordset Is Nothing Then
        If this.Recordset.State = adStateOpen Then this.Recordset.Close
        Debug.Print "A Recordsetet lezártuk" ' teszteléshez
    Else
        Debug.Print "Nincs lezárandó recordset" ' teszteléshez
    End If
    Set this.Recordset = Nothing
End Sub

Az ezt meghívó destruktor eljárás pedig így:

Private Sub Class_Terminate()
    CloseAdoRecordset
End Sub

Az osztály készen van, ezt is teszteljük, méghozzá az 1. részben írt teszt eljárás tovább bővítésével.

 
Public Sub KapcsolatTeszt()
    Dim conn As AdoConnection
    Set conn = New AdoConnection
    If Not conn.Error Then
        Dim SQL_String As String
        SQL_String = "SELECT * FROM TablaNeve"
        Dim rs As AdoRecordset
        Set rs = AdoRecordset.CreateFromSQL(conn.Connection, SQL_String)
        If Not rs.Error Then
            Debug.Print "Nem történt hiba"
        Else
            Debug.Print rs.LastError
        End If
        Set rs = AdoRecordset.CreateFromTable(conn.Connection, "TablaNeve")
        If Not rs.Error Then
            Debug.Print "Nem történt hiba"
        Else
            Debug.Print rs.LastError
        End If
        Set rs = Nothing
    Else
        Debug.Print conn.LastError
    End If
    
    Set conn = Nothing
End Sub

Ha minden rendben ment, a következőket kell az azonnali ablakban látnunk:

A kapcsolat létrejött
A Recordset létrejött
Nem történt hiba
A Recordset létrejött
A Recordsetet lezártuk
Nem történt hiba
A Recordsetet lezártuk
A kapcsolatot lezártuk

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.