[Parte 1Parte 2Parte 3Parte 4Parte 5]


Accesso ai dati in VB
Di:Enrico Granata



Jet è Orientato agli Oggetti

Il motore Jet è fortemente OO(Object Oriented) nel senso che la sua struttura è orientata agli oggetti, il che ci obbligherà a memorizzare qualche informazione sugli oggetti che compongono Jet

In seguito poi saremo addirittura facilitati da questa scelta di Microsoft

Il primo oggetto che rappresenta l'istanza di Jet in esecuzione è anche uno di quelli meno usati e prende il nome di DbEngine

Questo oggetto ha sulle spalle tutta la gerarchia di Jet Engine, ma è anche uno di quegli oggetti che lavorano nell'ombra, tanto è che nonostante le due versioni del codice riportato sopra siano equivalenti di solito vedrete sempre la seconda o al limite l'indicazione del solo WorkSpace

Ma anche lui ha diritto al suo momento di gloria, che arriva sempre nei momenti difficili, come quando usate RepairDatabase()

DBEngine.RepairDatabase "MioDb.Mdb"

Questo metodo tenta di ripristinare un Database danneggiato di nome MioDb.Mdb

Questo metodo si usa però solo di rado, un metodo più usato è CompactDatabase()

DBEngine.CompactDatabase "MioDb.Mdb", "MioNuovoDb.Mdb"

Questo metodo compatta il file "MioDb.Mdb" e lo copia in "MioNuovoDb.Mdb", per ottenere "MioDb.Mdb" compattato è necessario usare anche

Kill "MioDb.Mdb"

FileCopy "MioNuovoDb.Mdb","MioDb.Mdb"

Compattare un Database permette di eliminare fisicamente i record eliminati che vengono marcati da Jet con un segnaposto, ma questo è un metodo di Recordset

Stringhe di connessione ODBC

"ODBC;

UID=utente; PWD=password;

DSN=nomefontedati

LOGINTIMEOUT=secondi"

che potrebbe generare qualcosa come

"ODBC;

UID=Enrico; PWD=JetEngine;

DSN=MioDatabase;

LOGINTIMEOUT=50"

è la sintassi di una stringa di connessione ODBC che di solito si usa nel parametro fonte quando il database che intendiamo aprire è un DSN ODBC e non un file .MDB Jet Native

MakeReplica(), copie di questo DB

MyDb.MakeReplica("A:\back10marzo1998.mdb")

potrebbe essere usato per creare la copia di Backup del 10 marzo 1998 del Database MyDb, l'utilità è chiara, poter creare copie identiche di un Database senza bisogno di FileCopy e quindi della determinazione del nome di file che in applicazioni come Data Manager, ove il DB non è determinato dal programma, può richiedere la dichiarazioni di ulteriori variabili

L'uso di MakeReplica() richiede però

MyDb.Replicable=True

che rende il Database replicabile, ma attenti, non provate dopo a scrivere

MyDb.Replicable=False

perchè questo genererebbe un errore di Run Time, una volta che Replicable è True non può più diventare False

Una volta settato Replicable è possibile usare MakeReplica() per copiare un DB come abbiamo fatto prima

Stavolta decido io, Query SQL

In Database esistono due metodi degni di nota: Execute ed ExecuteSQL, entrambi eseguono query SQL, ma solo query che non ritornano record, ad esempio DELETE o UPDATE

Un esempio di Excute e di ExecuteSQL è questo

MyDb.Execute "UPDATE Impiegati SET Stipendio=4000000 WHERE Anni>=20 AND Contributi>=58000000"

Dim Lines As Long

Lines=MyDb.ExecuteSQL("DELETE FROM Impiegati WHERE Anni=36")

Le due query hanno ovviamente un significato diverso, ma sono anche eseguite da metodi diversi

Iniziamo comunque a dire che la prima query assegna il valore 4.000.000 al campo Stipendio della tabella Impiegati se gli Anni sono 20 o più e i contributi sono maggiori o uguali a 58.000.000

La seconda invece elimina dalla stessa tabella tutti i record dove Anni vale 36

La cosa che però ci interessa maggiormente notare è che Execute() non ritorna nulla, mentre ExecuteSQL() ritorna un Long che indica il numero di Record influenzati dalla Query, cioè nel nostro caso il numero di record cancellati

Dim MyRecordSet As RecordSet

Set MyRecordSet=MyDb.OpenRecordSet("SELECT * FROM Editori WHERE ScriveLibri=1")

Questo codice non fa altro che creare un Recordset che contiene tutti i record di Editori dove ScriveLibri è uguale ad 1

Facile, vero?

Ricordate però una cosa, questo RecordSet non è fisicamente presente nel Database, cioè una volta scritta questa linea di codice

Set MyRecordSet=Nothing

la tabella non esiste più

Iteriamo in quella tabella...

Adesso ci serve una tabella chiamati QueryEditori che creeremo con le seguenti istruzioni

Dim Table As TableDef

Set Table=MyDb.CreateTableDef("QueryEditori")

...'Codice per creare i campi

MyDb.TableDefs.Append Table

Questo codice è tutto quello che serve per creare un TableDef cioè un modello di Tabella e aggiungerlo al Database

Ma tra questo e la tabella QueryEditori finale c'è ancora molto lavoro da fare, cioè aggiungere i record

Dim MyField As Field

...'Impostazione del campo

Table.Fields.Append MyField

Come al solito il meccanismo degli insiemi ci ha ridotto di molto il lavoro, ma qualcosa da fare c'è ancora, MyField è ancora un oggetto vuoto, bisogna dargli un nome e un tipo di dati, dovete però sapere che la creazione dei record è un compito così ripetitivo e noioso che ci conviene creare una routine AggiungiCampo() che faccia il lavoro al posto nostro

Public Sub AggiungiCampo(Tabella As TableDef, Campo As Field, Nome _

As String, TipoDati As Integer)

Dim MyField As Field

'Impostazione del campo

Tabella.Fields.Append MyField

End Sub

Adesso passiamo al codice mancante, Impostazione del campo

Public Sub AggiungiCampo(Tabella As TableDef, Nome _

As String, TipoDati As Integer, Optional Dimensione)

Dim MyField As Field

If IsMissing(Dimensione) Then

Set MyField=Tabella.CreateField(Nome, TipoDati)

Else

Set MyField=Tabella.CreateField(Nome, TipoDati, Dimensione)

End If

Tabella.Fields.Append MyField

End Sub

Se il fatto che Dimensione sia Optional vi stupisce sappiate che è giusto che sia così, poichè alcuni campi ignorano l'argomento durante la creazione e pertanto inserirlo sarebbe solo uno spreco di Byte nello Stack

Adesso che abbiamo la nostra brava AggiungiCampo() possiamo anche usufruirne

AggiungiCampo(Table, "Nome", dbText)

AggiungiCampo(Table, "AnnoNascita", dbDate)

AggiungiCampo(Table, "LibriPubblicati", dbMemo)

AggiungiCampo(Table, "FatturatoUltimoAnno", dbCurrency)

AggiungiCampo(Table, "ScriveLibri", dbInteger)

MyDb.TableDefs.Append Table

Dim QueryEditori As RecordSet

Dim Editori As RecordSet

Set QueryEditori=MyDb.OpenRecordSet("QueryEditori")

Set MyRecordSet=MyDb.OpenRecordSet("SELECT * FROM Editori WHERE ScriveLibri=1")

Da questo codice otteniamo le due tabelle aperte e pronte per le operazioni

A questo punto siamo pronti per iniziare

BeginTrans

Do Until Editori.Eof

QueryEditori.AddNew

QueryEditori("Nome")=Editori("Nome")

QueryEditori("AnnoNascita")=Editori("AnnoNascita")

QueryEditori("LibriPubblicati")=Editori("LibriPubblicati")

QueryEditori("FatturatoUltimoAnno")=Editori("FatturatoUltimoAnno")

QueryEditori("ScriveLibri")=Editori("ScriveLibri")

QueryEditori.Update

Loop

CommitTrans

Cosa significa tutto questo?

Semplice, che finchè Editori non arriva all'ultimo record ogni suo campo deve essere trasferito a QueryEditori, BeginTrans e CommitTrans indicano una transazione ed è buona norma inserirle prima di ogni modifica al DB, BeginTrans prima e CommitTrans dopo

Esiste un metodo generico per copiare due tabelle con record uguali

BeginTrans

Do Until Editori.Eof

QueryEditori.AddNew

Dim FieldCounter As Integer

For FieldCounter=0 To Editori.Fields.Count-1

QueryEditori(FieldCounter)=Editori(FieldCounter)

QueryEditori(FieldCounter)=Editori(FieldCounter)

QueryEditori(FieldCounter)=Editori(FieldCounter)

QueryEditori(FieldCounter)=Editori(FieldCounter)

QueryEditori(FieldCounter)=Editori(FieldCounter)

QueryEditori.Update

Next FieldCounter

Loop

CommitTrans

Questo codice si basa sul metodo AddNew e Update per aggiungere un record ed entrare subito in modalità Editing, Update per salvare i nuovi dati, ma nel caso il record esista già cosa fare?

Esiste il metodo Edit che entra in modalità Editing per il record corrente, ma che richiede sempre Update per aggiornare i dati

Ad esempio poniamo che l'Editore "Diemme" sia diventato "Dielle", come registrare la modifica?

Dim Editori As RecordSet

Set Editori=MyDb.OpenRecordSet("Editori")

Do Until Editori.Eof

If Editori("Nome")="Diemme" Then

BeginTrans

Editori.Edit

Editori("Nome")="Dielle"

Editori.Update

CommitTrans

End If

Loop

Editori.Close

Transazioni e CancelUpdate()

Questo codice cerca l'editore Diemme in tutta la tabella e quando lo trova lo sostituisce con Dielle, tutto questo sempre incluso in una Transazione, ma a proposito di transazioni, poniamo che ad un certo punto vogliamo annullare delle modifiche

Come fare?

Dim Editori As RecordSet

Set Editori=MyDb.OpenRecordSet("Editori")

Do Until Editori.Eof

If Editori("Nome")="Diemme" Then

BeginTrans

Editori.Edit

Editori("Nome")="Dielle"

If Annullo Then

RollBack

Else

Editori.Update

CommitTrans

End If

End If

Loop

Editori.Close

Dove Annullo è una qualsiasi condizione che vogliamo esaminare, ovvio che nel caso stessimo eseguendo Editori.Update quando è mancata la corrente RollBack non può più fare niente, in qual caso solo RepairDatabase() o una copia di backup potranno qualcosa

Bisogna poi sapere che anche CancelUpdate() permette di annullare modifiche in un RecordSet, a meno che non si sia usato Update

Dim Editori As RecordSet

Set Editori=MyDb.OpenRecordSet("Editori")

Do Until Editori.Eof

If Editori("Nome")="Diemme" Then

BeginTrans

Editori.Edit

Editori("Nome")="Dielle"

If Annullo Then

Editori.CancelUpdate

Else

Editori.Update

CommitTrans

End If

End If

Loop

Editori.Close

Altre piccole routine di utilità

Un metodo interessante di Recordset è Clone(), per creare copie esatte di Recordsets

Questo metodo non è però utile per il Backup dei dati poichè crea un Recordset e non un TableDef

Dim MyRecordSet As RecordSet

Dim ClonedSet As RecordSet

Set MyRecordSet=MyDb.OpenRecordSet("QueryEditori")

Set ClonedSet=MyRecordSet.Clone

Il quale crea in ClonedSet una copia di QueryEditori

.Eseguire ricerche in un RecordSet è un'operazione che può essere eseguita in vari modi, sia aprendo un RecordSet con una query SQL, sia con i metodi Find

MyRecordSet.FindFirst "Nome=""Enrico"""

è funzionalmente equivalente a

Set MyRecordSet=MyDb.OpenRecordSet("SELECT * FROM Persone WHERE Nome=""ENRICO""")

Il primo pezzo di codice trova il primo elemento della tabella dove il campo Nome è Enrico

Il secondo apre una tabella come QueryEditori, cioè basata su query SQL

QueryDef

Lle Query eseguite tramite i RecordSet hanno il problema della non persistenza dei dati, le QueryDef rispondono a questa esigenza con uno strumento facile e che sopratutto, a differenza delle query basate su RecordSet, occupa poco spazio su disco, poichè un QueryDef contiene solo la definizione della Query, non i risultati, ovvero nel caso la tabella Anagrafe occupasse su disco 5.487.152 bytes la query

SELECT * FROM Anagrafe

ne occuperebbe invece solo 22 poichè i risultati verrebbero caricati a Run-Time

Adesso vediamo come creare la nostra QueryDef

Set MyQueryDef= MyDb.CreateQueryDef("MiaQueryDef", "SELECT * FROM Impiegati WHERE Anni>33")

crea ed aggiunge(implicitamente) la query MiaQueryDef a MyDb.QueryDefs, questa query seleziona tutti i record di Impiegati dove Anni è maggiore di 33

Per eseguire la query aprire un RecordSet passando il nome dell'oggetto QueryDef

Set MyRecordSet=MyDb.OpenRecordSet("MiaQueryDef")

che equivale a

Set MyRecordSet=MyDb.OpenRecordSet("SELECT * FROM Impiegati WHERE Anni>33")

Esiste una proprietà che vorrei illustrare perchè ci porta nel merito di uno di quegli argomenti che di solito non sono molto noti al programmatore normale:Le proprietà personalizzate

La proprietà si chiama LogMessages che indica se ODBC può restituire messaggi in tabelle denominate nomeutente-numeroprogressivo all'esecuzione di un QueryDef, pertanto una proprietà non indispensabile anche perchè in questo corso useremo poco ODBC, ma valida come scusa per illustrare gli oggetti Property

Dim MyProperty As Property

Set MyProperty = MyQueryDef.CreateProperty("LogMessages", dbBoolean, True)

MyQueryDef.Properties.Append MyProperty

Questo fatto è molto importante a parte perchè consente di selezionare delle caratteristiche di Jet in modo esplicito come nel caso dell'archiviazione dei messaggi ODBC, ma anche perchè consente all'utente di creare sue proprietà per un oggetto, ad esempio questa proprietà che avvisa l'utente successivo se è stato tentato di violare il DB

Private Sub Form_Load()

Set MyProperty=MyDb.CreateProperty("Violato", dbBoolean, False)

MyDb.Properties.Append MyProperty

End Sub

Private Sub Login_Click()

Dim Name As String

Name=InputBox("Dimmi il tuo nome")

If Name<>"Enrico" Then

MyDb.Violato=True

MsgBox "Il Database è Sicuro, Vai via!", 16, "Protezione"

Exit Sub

ElseIf Name="Enrico" Then

If MyDb.Violato Then

MsgBox "Qualcuno ha tentato di violare il DataBase", 48, "Avviso"

MyDb.Violato=False

End If

End If

End Sub

Questo codice sfrutta le proprietà personalizzate per verificare se qualcuno con nome diverso da Enrico ha tentato l'accesso, se ciò avviene imposta su True la proprietà Violato e quando Enrico esegue il Login lo avvisa dell'accaduto

Quando la sicurezza si rivela importante...

La sicurezza di Jet come ogni suo elemento è basata sugli oggetti, se ricordate all'inizio parlammo dell'oggetto WorkSpace come area di transazioni protetta per un singolo utente, bene ora questo concetto sarà ripreso ed esteso, l'oggetto WorkSpace contiene i metodi CreateUser() e CreateGroup() per creare un oggetto User e Group

Set MeMedesimo = MyWorkSpace.CreateUser("Enrico", "MIO1PID", "Password")

In questo modo Enrico diventa utente di MyWorkSpace

Esiste poi l'oggetto Group

Set MioGruppo = MyWorkSpace.CreateGroup("Vari", "MIO2PID")

che crea il gruppo Vari per MyWorkSpace

Adesso vi chiederete come aggiungere MeMedesimo e MioGruppo a MyWorkSpace

MyWorkSpace.Users.Append MeMedesimo

MyWorkSpace.Groups.Append MioGruppo

che aggiungono ad Users ed a Groups

Creare poi delle impostazioni di sicurezza è effettivamente una delle operazioni più difficili di cui il modulo Jet disponga, esistono infatti degli oggetti Container e Document con le proprietà

ove Permissions ed UserName sono collegate poichè Permissions agisce sull'utente UserName, la proprietà Owner indica invece il proprietario dell'oggetto

Vi faccio comunque notare che per usare questi esempi sarà necessario disporre del file SYSTEM.MDW distribuito con Access per Windows 95 detto anche "Database di Sistema" poichè contiene tutti i dati di protezione ed è indispensabile per attivare la protezione, cioè la sicurezza multiutente

Dicendo che lo abbiate questo è il codice che imposta il vostro primo privilegio

MyDb.Containers("Databases").UserName="MeMedesimo"

MyDb.Containers("Databases").Permissions=dbSecReadSec 'Leggi le impostazioni di sicurezza

Con questo codice l'utente MeMedesimo avrà il permesso di leggere le impostazioni di sicurezza, ma non di modificarle

Se poi vogliamo invece attribuire allo stesso MeMedesimo permessi di lettura di una tabella di nome MyTable

MyDb.Containsers("Tables").Documents("MyTables").UserName="MeMedesimo"

MyDb.Containsers("Tables").Documents("MyTables").Permissions=dbSecRetreiveData

L'organizzazione di sicurezza è divisa in Containers e Documents, ove Containers(stringa1).Documents(stringa2) identifica l'oggetto di nome stringa2 del Container stringa1

Praticamente questo è tutto ciò che c'è da dire sulla sicurezza di Jet, sembra difficile, ma consultando la guida di VB che è un riferimento completo all'argomento e che contiene anche tutte le costanti dbSec sarà più facile

Query con parametri

Queste sono query particolari in cui alcuni valori non sono noti a priori, ma lo saranno in un secondo tempo, ad esempio in un ipotetico programma per gestire un database di una Città si potrebbe voler rimuovere tutti i record dove AnnoMorte è < 1990, ma il programma dispone solo della funzione "Rimuovi record per Anno di Morte", cioè della routine RemoveForDeathYear()

Public Sub RemoveForDeathYear()

End Sub

Voi potreste dire che non c'è bisogno delle query con parametri, basta usare l'operatore & come nel seguente esempio

Dim DeathYear As String

DeathYear=InputBox("Anno di morte")

Dim SQLQuery As String

SQLQuery="DELETE FROM Anagrafe WHERE AnnoMorte<" & DeathYear

Ma perchè fare così quando esiste una query che fa esattamente la stessa cosa ed anche in modo più rapido?

PARAMETERS DeathYear Text; DELETE FROM Anagrafe WHERE AnnoMorte<DeathYear

L'unica cosa che questa query ha di insolito è la sezione prima del ; cioè "PARAMETERS DeathYear Text" che non fa altro che definire un parametro DeathYear che poi verrà modificato in fase di esecuzione

Prima di definire RemoveForDeathYear() dobbiamo però definire la CreateSQLQueryDefs() che nel nostro caso creerà la query con parametri

Public Sub CreateSQLQueryDefs()

Dim Query As QueryDef

Set Query=MyDb.CreateQueryDef("RemoveDeath", "PARAMETERS DeathYear Text; DELETE FROM Anagrafe WHERE AnnoMorte<DeathYear")

End Sub

Tutto è pronto per la RemoveForDeathYear() la quale grazie ai QueryDef sarà molto ristretta

Diciamo che possiamo usare in questo caso QueryDef.ExecuteSQL per ottenere il numero di righe eliminate

Public Sub RemoveForDeathYear()

Dim DeathYear As String

DeathYear=InputBox("Anno di morte")

Dim SQLQuery As QueryDef

Set SQLQuery=MyDb.QueryDefs("RemoveDeath")

SQLQuery.Parameters("DeathYear")=DeathYear

Dim NLines As Long

NLines=SQLQuery.ExecuteSQL

MsgBox NLines & " linee influenzate dall'esecuzione della Query", 64, "Informazione"

End Sub

La routine non fa altro che completare la QueryDef RemoveDeath e poi esegue con ExecuteSQL che come dicemmo nei mesi passati ritorna un Long che corrisponde al numero di Linee sulle quali la Query ha operato

Alla fine mostriamo in una MsgBox il Long ritornato e anche l'argomento

A questo punto il corso è finito, pertanto vi ringrazio per l'attenzione con la quale mi avete seguito e mi auguro di esservi stato di aiuto nell'uso di Jet Engine