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