ADO è uno strumento molto potente per quanto riguarda l'accesso ad un DB, e consente di gestire il trattamento dei dati da/per il DB in maniera piuttosto agevole ed intuitiva con relativamente pochi oggetti e con semplici metodi.
Un aspetto meno conosciuto del modello ad oggetti ADO è la possibilità di "esplorare" il DB anche dal punto di vista strutturale con il metodo OpenSchema. Con questo metodo possiamo andare a sbirciare nell'elenco delle tabelle disponibili in un DB e dei relativi campi.
Non solo questo, in realtà, poichè il metodo che andrò ad illustrare - seppure in minima parte - permette di esplorare molto più delle semplici tabelle e dei campi che le compongono (oltre ad eventuali indici, tipi di campo, chiavi primarie, etc.) in quanto consente di accedere all'intero catalogo delle query o delle procedure e ad altre peculiari caratteristiche riscontrabili normalmente nella struttura di un database.
A questo punto potrebbe anche sollevarsi la questione del "perchè" dovremmo occuparci di andare ad esplorare la struttura di un DB, quando quello che normalmente basta è trattarlo come un cassetto nel quale riporre dati ritirandoli fuori al momento giusto! In realtà (anche se non è una pratica che consiglierei) potrebbe capitare che la struttura di un DB non sia così rigida e che si renda necessario, in corso di utilizzo, l'aggiunta o l'eliminazione dinamica di tabelle o campi. Per non rinunciare alla flessibilità della nostra applicazione non rimane altra strada che far sapere all'applicazione stessa come è fatto il DB.
Per semplicità ci rivolgeremo ad un database Access e, per meglio comprendere il funzionamento del metodo in argomento, realizzeremo una semplice applicazione che, volendo, potrà essere migliorata ed ampliata per dotarla di ulteriori caratteristiche, che le permettano di andare ancora più in profondità nell'esplorazione di un DB.
Questo piccolo tool sarà formato da un singolo Form all'interno del quale troveranno posto alcuni controlli come un TextBox, due ListView, un controllo CommonDialog, un paio di pulsanti e qualche label.
Per usare la ListView e il CommonDialog dovremo referenziare questi controlli nella lista dei Componenti, accessibile dal menu Progetto -> Componenti.

Sarà inoltre necessario referenziare la libreria ADO più recente sul PC. Normalmente dovrebbe essere la Microsoft ActiveX Data Object 2.8 Library

Preparato lo scenario, possiamo procedere alla progettazione del Form.

Iniziamo a scrivere del codice e, nella sezione dichiarazioni del nostro Form, dichiariamo l'oggetto relativo alla connessione che servirà per accedere al DB.
Dim cn As ADODB.Connection
Sfruttando l'evento Form_Load() possiamo occuparci di preparare gli oggetti che raccoglieranno i dati, ovvero le ListView. Aggiungeremo delle colonne in relazione ai dati da visualizzare e contestualmente prepareremo anche il CommonDialog per permetterci di specificare il Database .mdb che esploreremo.
Private Sub Form_Load() With lwTables .View = lvwReport .ColumnHeaders.Add , , "Nome Tabella" .ColumnHeaders.Add , , "Tipo Tabella" End With With lwFields .View = lvwReport .ColumnHeaders.Add , , "Nome Campo" .ColumnHeaders.Add , , "Tipo Campo" .ColumnHeaders.Add , , "Lunghezza" End With With cDialog .CancelError = True .DefaultExt = "*.mdb" .DialogTitle = "Seleziona un Database Access" .Filter = "Database Access (*.mdb)|*.mdb" .Flags = cdlOFNExplorer Or cdlOFNFileMustExist End With End Sub
Passiamo a scrivere il codice relativo alla pressione sul tasto di caricamento del DB.
La sequenza delle operazioni è piuttosto logica:
- viene aperto il CommonDialog in modalità di selezione Files
- una volta ottenuto il nome del file lo si usa per aprire la Connessione dichiarata in precedenza
- vengono resettate le ListView che accoglieranno i dati
- si invoca la routine RetrieveTables (che vedremo dettagliatamente in seguito) che serve per popolare la ListView con le tabelle presenti nel DB.
Private Sub cmdLoadDB_Click() On Error GoTo No_File cDialog.ShowOpen Set cn = New ADODB.Connection cn.Open "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & cDialog.FileName txtDBPath.Text = cDialog.FileName lwTables.ListItems.Clear lwFields.ListItems.Clear RetrieveTables No_File: End Sub
La Routine RetrieveTables è uno dei tasselli importanti di questa piccola applicazione.
E' qui che viene utilizzato il metodo OpenSchema per accedere alla struttura delle Tabelle del nostro DB. Come dicevo, attraverso OpenSchema è possibile esplorare diversi aspetti della struttura del database e tutto dipende dalla costante passata come argomento al metodo (Schema As SchemaEnum) e dai criteri imposti (Restrictions), che sono opzionali e variano a seconda della costante utilizzata e in base alla necessità di filtrare, in relazione alla nostra esigenza, la lista degli oggetti che vogliamo ottenere.
La sintassi del metodo è la seguente:
OpenSchema (Schema As SchemaEnum, [Restrictions], [SchemaID])
Il metodo restituisce un oggetto di Recordset che conterrà tanti record per quante tabelle verranno trovate.
Per ottenere la lista di tutte le tabelle comprese quelle nascoste "di sistema" utili alle impostazioni del DB è necessario specificare esclusivamente la costante adSchemaTables.
Sub RetrieveTables() Dim itmX As MSComctlLib.ListItem Dim rsTables As ADODB.Recordset Set rsTables = cn.OpenSchema(adSchemaTables) If Not (rsTables.BOF And rsTables.EOF) Then While Not rsTables.EOF Set itmX = lwTables.ListItems.Add(, , rsTables.Fields("TABLE_NAME").Value) itmX.ListSubItems.Add , , rsTables.Fields("TABLE_TYPE").Value rsTables.MoveNext Wend End If rsTables.Close Set rsTables = Nothing Set itmX = Nothing End Sub
Come si può vedere nella precedente routine sono stati utilizzati esclusivamente 2 campi del recordset contenente la lista delle tabelle, ovvero TABLE_NAME e TABLE_TYPE, ma è possibile sapere molto di più su una tabella, utilizzando il valore contenuto negli altri campi:
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME TABLE_TYPE TABLE_GUID DESCRIPTION TABLE_PROPID DATE_CREATED DATE_MODIFIED
Occupiamoci adesso di gestire la seconda ListView che ospiterà la lista dei campi e alcuni dei dati ad essi relativi.
Useremo l'evento ItemClick() per ottenere dall'Item cliccato il nome della tabella da passare al metodo OpenSchema come parametro "Restrictions".
Private Sub lwTables_ItemClick(ByVal Item As MSComctlLib.ListItem) lwFields.ListItems.Clear RetrieveFields (Item.Text) End Sub
Restrictions in realtà può essere un Array contenente diversi parametri dipendenti strettamente dal tipo di oggetto richiesto.
Una lista completa dei parametri relativi ad ogni costante che è possibile passare al metodo OpenSchema la trovate qui:
W3Schools - OpenSchema
Per quello che ci riguarda, ovvero tirar fuori la lista dei campi relativi ad una tabella ci basta questo:
| Constant | Value | Description | Constraint Columns |
| adSchemaColumns | 4 | Returns the columns of tables defined in the catalog |
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME |
Come dicevo in precedenza Restrictions è facoltativo per cui, se non viene usato, OpenSchema restituirà l'elenco completo di tutti i campi del DB a prescindere dalla tabella in cui essi si trovino.
Ovviamente non è il nostro intento, e considerato che abbiamo a disposizione il nome della tabella della quale intendiamo sapere i campi, è opportuno preparare un Array da passare ad OpenSchema per ottenere quelli presenti nella tabella di nostro interesse.
L'array è presente nella routine seguente e si chiama Criteri. Esso viene valorizzato con 3 parametri dei quali i primi 2 come Empty (ovvero l'elenco dei campi non verrà filtrato nè per TABLE_CATALOG nè per TABLE_SCHEMA) ma contiene, nel terzo parametro, il nome della tabella passato attraverso l'argomento FromTable della Routine. Non è necessario indicare il 4° parametro che verrà valutato anch'esso come Empty:
Sub RetrieveFields(FromTable As String) Dim itmX As MSComctlLib.ListItem Dim rsFields As ADODB.Recordset Dim Criteri As Variant Criteri = Array(Empty, Empty, FromTable) Set rsFields = cn.OpenSchema(adSchemaColumns, Criteri) If Not (rsFields.BOF And rsFields.EOF) Then While Not rsFields.EOF Set itmX = lwFields.ListItems.Add(, , rsFields.Fields("COLUMN_NAME").Value) itmX.ListSubItems.Add , , ConvertTypeCode(rsFields.Fields("DATA_TYPE").Value) itmX.ListSubItems.Add , , rsFields.Fields("CHARACTER_MAXIMUM_LENGTH").Value & "" rsFields.MoveNext Wend End If rsFields.Close Set rsFields = Nothing Set itmX = Nothing End Sub
Analogamente a quanto avviene per la ListView delle tabelle anche per i campi abbiamo ottenuto, oltre ai loro nomi, delle informazioni aggiuntive.
La prima è DATA_TYPE che altro non è che la tipizzazione del campo del DB. Il valore restituito è una costante numerica che effettivamente, a meno di non avere la memoria di Salomone, dice davvero poco sull'effettiva natura del dato presente nel campo. Per ottenere una risposta maggiormente indicativa possiamo realizzare una semplice Function che restituisce una stringa con il nome della costante passata come argomento.
Function ConvertTypeCode(c As Integer) As String Select Case c Case adDBTimeStamp ConvertTypeCode = "adDBTimeStamp" Case adArray ConvertTypeCode = "adArray" Case adBigInt ConvertTypeCode = "adBigInt" Case adBinary ConvertTypeCode = "adBinary" Case adSingle ConvertTypeCode = "adSingle" Case adNumeric ConvertTypeCode = "adNumeric" Case adLongVarWChar ConvertTypeCode = "adLongVarWChar" Case adLongVarChar ConvertTypeCode = "adLongVarChar" Case adLongVarBinary ConvertTypeCode = "adLongVarBinary" Case adIUnknown ConvertTypeCode = "adIUnknown" Case adInteger ConvertTypeCode = "adInteger" Case adIDispatch ConvertTypeCode = "adIDispatch" Case adGUID ConvertTypeCode = "adGUID" Case adError ConvertTypeCode = "adError" Case adEmpty ConvertTypeCode = "adEmpty" Case adDouble ConvertTypeCode = "adDouble" Case adDecimal ConvertTypeCode = "adDecimal" Case adDBTimeStamp ConvertTypeCode = "adDBTimeStamp" Case adDBTime ConvertTypeCode = "adDBTime" Case adDBDate ConvertTypeCode = "adDBDate" Case adDate ConvertTypeCode = "adDate" Case adCurrency ConvertTypeCode = "adCurrency" Case adChar ConvertTypeCode = "adChar" Case adBSTR ConvertTypeCode = "adBSTR" Case adByRef ConvertTypeCode = "adByRef" Case adBoolean ConvertTypeCode = "adBoolean" Case adSmallInt ConvertTypeCode = "adSmallInt" Case adTinyInt ConvertTypeCode = "adTinyInt" Case adUnsignedBigInt ConvertTypeCode = "adUnsignedBigInt" Case adUnsignedInt ConvertTypeCode = "adUnsignedInt" Case adUnsignedSmallInt ConvertTypeCode = "adUnsignedSmallInt" Case adUnsignedTinyInt ConvertTypeCode = "adUnsignedTinyInt" Case adBoolean ConvertTypeCode = "adBoolean" Case adUserDefined ConvertTypeCode = "adUserDefined" Case adVarBinary ConvertTypeCode = "adVarBinary" Case adVarChar ConvertTypeCode = "adVarChar" Case adVariant ConvertTypeCode = "adVariant" Case adVector ConvertTypeCode = "adVector" Case adVarWChar ConvertTypeCode = "adVarWChar" Case adWChar ConvertTypeCode = "adWChar" End Select End Function
Al posto dei nomi delle costanti è ovviamente possibile farsi restituire dalla Function qualsiasi testo ci aiuti a capire meglio il tipo di contenuto del Campo.
La seconda informazione che intendiamo rilevare dal Campo è la lunghezza massima consentita (valevole per i campi di tipo testo), per cui possiamo analizzare il valore contenuto in CHARACTER_MAXIMUM_LENGTH
Anche in questo caso, come per le Tabelle è possibile sapere molte più informazioni sullo stato di un campo, e basta utilizzare altri nomi di campo, tra quelli qui sotto elencati, disponibili nel Recordset restituito da OpenSchema:
TABLE_CATALOG TABLE_SCHEMA TABLE_NAME COLUMN_NAME COLUMN_GUID COLUMN_PROPID ORDINAL_POSITION COLUMN_HASDEFAULT COLUMN_DEFAULT COLUMN_FLAGS IS_NULLABLE DATA_TYPE TYPE_GUID CHARACTER_MAXIMUM_LENGTH CHARACTER_OCTET_LENGTH NUMERIC_PRECISION NUMERIC_SCALE DATETIME_PRECISION CHARACTER_SET_CATALOG CHARACTER_SET_SCHEMA CHARACTER_SET_NAME COLLATION_CATALOG COLLATION_SCHEMA COLLATION_NAME DOMAIN_CATALOG DOMAIN_SCHEMA DOMAIN_NAME DESCRIPTION
Siamo giunti alla conclusione e la nostra piccola applicazione dovrebbe essere funzionante, eseguendo le operazioni per le quali l'abbiamo creata. Per scoprire se è vero non ci resta che lanciarla con F5 e caricare un DB premendo sul pulsante con i tre puntini [...]

Il risultato non è male e abbiamo appena scalfito la superficie del vasto mondo di OpenSchema, ma la metodologia di base qui illustrata è valevole per l'esplorazione degli ulteriori e talvolta più complessi aspetti di un Database. Una delle caratteristiche che si possono implementare in maniera quasi indolore, ad esempio, è la rilevazione delle Query presenti nel DB (delle quali è possibile sapere anche la frase SQL che le definisce) o ancora le chiavi primarie relative ad una determinata Tabella oppure i suoi Indici... le possibilità sono moltissime.
In area Download -> Progetti potete scaricare il progetto di esempio realizzato per illustrare il metodo OpenSchema.
























