Diciamoci la verità. Il lavoro quotidiano di un informatico è quello di trovare soluzioni a problemi che nella maggior parte delle volte sono veramente complicati.
Per poter affrontare queste problematiche, ognuno si crea una propria cassetta degli attrezzi da cui attingere (con un’abilità più da prestigiatore che da informatico) pezzi di codice, tools vari o quant’altro possa servire.
Col passare del tempo però, questa cassetta degli attrezzi rischia di diventare di dimensioni titaniche, troppo grande perché possa effettivamente essere di aiuto.
Per ovviare a questo problema, la prima cosa da fare è sicuramente quella scegliere con estrema attenzione quali strumenti conservare e quali no tra l’enormità di quelli che ci vengono messi a disposizione. Il secondo passo consiste nel manutenere gli strumenti che si è scelto, aggiornarli, e soprattutto non smettere mai di guardarsi attorno, perché tali strumenti rischiano di diventare obsoleti, o addirittura inappropriati allo scopo per cui furono originariamente progettati.
Partendo proprio da questo concetto, visto che spesso per mancanza di tempo o magari solo per pigrizia non si ha modo di guardarsi attorno, ho pensato di scrivere questo articolo su SQL Server Management Objects o più brevemente SMO. Con SMO si andrà ad aggiungere alla propria cassetta degli attrezzi uno strumento che pare essere davvero poco conosciuto seppur potentissimo, abbastanza semplice da usare anche se forse come strumento di lavoro è un tantino … ingombrante !
Ma perché ingombrante ? Vi spiegherò il perché tra breve. Ma prima di cominciarne l’esplorazione, è bene fare una importante premessa: cosa fa esattamente SMO e a cosa serve ?
SMO è una delle infinite libreria di classi che rientra nell’enorme patrimonio del framework .NET. E’ stato scritto per gestire SQL Server. Il suo scopo principale è solo quello. E’ una libreria che si presta perfettamente a compiti quali, ad esempio, elencare i database di una istanza, elencare le tabelle che vi sono contenute ed elencare le colonne di ogni singola tabella, oltre ovviamente a fornire svariate funzionalità di gestione per ognuno di questi oggetti. Consente inoltre di effettuare procedure di backup e di restore con le relative verifiche, ed altre operazioni di questo tipo, non sempre facilmente implementabili anche utilizzando una libreria potente come ADO.NET.
Il miglior esempio che si possa fare per rendersi immediatamente conto di cosa intendo per gestire, è immaginare una situazione in cui si deve poter amministrare SQL Server da una qualsiasi macchina della propria rete aziendale senza avere a disposizione Management Studio ! Per continuare ad avere la comodità di una interfaccia grafica, si decide di creare un tool ad hoc. Una volta completato il tool scritto con SMO, lo si può copiare sulla propria penna USB, e così sarà possibile accedere all’istanza di SQL Server di proprio interesse ed amministrarla, scrivendo davvero pochissime righe di codice !
Le risposte in verità sono più di una, ma la principale è che SMO è molto, ma molto pesante (ed ecco perché prima lo definivo ingombrante). SMO porta sulla vostra macchina, in un oggetto chiamato Server, tutta un’intera istanza di SQL Server ! Ora, non sappiamo quanto può essere grande una rappresentazione di una istanza, ma parlando di valori medi è sicuramente molto, ma molto più grande della rappresentazione di una tabella popolata con il risultato di una query, che poi è quello per cui è indicato ADO.NET.
In SMO, per fare una qualsiasi operazione, dobbiamo prima portarci in locale la copia dell’istanza di SQL Server che ci interessa, e poi, una volta connessi, possiamo cominciare a lavorare (per esempio lanciare una query per la modifica della struttura di una tabella). In ADO.NET, invece, effettuiamo solo la connessione al database e poi, tramite questa connessione, lanciamo la query. Quello che dobbiamo gestire, oltre all’oggetto connessione di per se, è il risultato della query (se c’è). Tale risultato potrà essere inserito, ad esempio, in un DataTable, ma certamente il DataTable risultante sarà più piccolo dell’intera istanza di SQL Server ! Immaginiamo poi solamente il volume di traffico che si verrebbe a generare se tutto questo “spreco” di memoria venisse moltiplicato per il numero di utenti collegati al database ! Appare quindi evidente che SMO non deve essere usato in ambienti in cui si prevedono molte connessioni, quale è appunto quello di un utilizzo “regolare” del database. Per questa ragione SMO viene confinato nell’area di gestione.
Certo, SMO ha degli strumenti per ottimizzare le informazioni, la memoria e il consumo sia di banda di rete che di CPU. Tra questi spicca sicuramente la tecnica chiamata Lazy Instantiation. Grazie a questa tecnica, quando un oggetto viene creato, le sue proprietà non vengono immediatamente lette dal server, eccezion fatta per quelle essenziali ai fini di scripting, risparmiandoci quindi notevole banda di rete. Tuttavia però, nel complesso il discorso non cambia di molto.
Inoltre c’è un secondo punto da sottolineare. SMO è progettato per lavorare solo con SQL Server (anche 2000). Se abbiamo necessità di gestire un database diverso da SQL Server, non possiamo usare SMO, ma dobbiamo usare ADO.NET, che è una libreria progettata per gestire basi di dati anche diverse tra loro.
Questa premessa sembrerebbe, a conti fatti, demolire SMO, ma in verità non è così. SMO, come vedremo tra poco, grazie alle librerie dedicate che lo compongono, consente di fare cose grandiose con pochissime righe di codice, il che, come è risaputo, tende a ridurre l’introduzione di errori, aumentando di riflesso la sicurezza.
Dato che SMO è composto da migliaia di classi. quello che cercherò di fare in questo articolo è di dare al lettore un’idea del funzionamento di SMO, e non di fornire padronanza della libreria in sé, lasciandogli quindi il piacere di continuare l’esplorazione delle classi alla ricerca di maggiori dettagli a seconda di quello che gli può servire.
Per cercare di raggiungere questo scopo, e al tempo stesso semplificare le cose, ho deciso di suddividere l’articolo in due macro aree. La prima riguarda le funzionalità di connessione e di interrogazione, mentre la seconda riguarda la gestione vera a propria.
Per mantenere il codice quanto più coinciso possibile, ho eliminato tutte quelle parti collegate alla buona norma di programmazione, come utilizzare le using e includere il codice in blocchi try-catch. Tuttavia vi esorto vivamente ad utilizzare queste tecniche, oltre che per una maggiore qualità del codice, anche perché quando si lavora con SMO, spesso di lavora con oggetti davvero voluminosi, che sicuramente non è bene lasciare in memoria, sparpagliati qua e là.
Infine, molte funzionalità sono state solo citate, ma non analizzate. Tuttavia, dove possibile, ho fornito alcuni spunti su cosa cercare per ottenere maggiori dettagli in merito.
Introduzione al codice
Nota
Se tra i riferimenti non ci sono i tre namespace indicati, vuol dire che probabilmente sulla vostra macchina non è installata nessuna versione di SQL Server. E’ sufficiente installarne una (anche la versione Express), oppure modificare l’installazione di Visual Studio aggiungendo la parte relativa a SQL Server che probabilmente non avete installato. Questo vi consentirà di aggiungere le librerie necessarie a SMO. Se SQL Server è già installato, potete puntare direttamente alle librerie SMO nella cartella C:\Programmi\Microsoft SQL Server\90\SDK\Assemblies. Se le librerie SMO non dovessero essere presenti, verificare di aver scaricato ed installato l’SDK per SQL Server.
Per comodità e compattezza di codice, importiamo due namespaces:
Cominciamo subito con la parte sequenzialmente più logica, relativa alla connessione a SQL Server e, come vedremo, non solo questo. Dato che questa sezione è probabilmente la più importante, sia perché tocca la sicurezza, sia perchè si candida ad essere l’area con le funzionalità che probabilmente si utilizzerà con più frequenza (perchè possono essere utili, ad esempio, per generare aree di interrogazione all’interno dei propri applicativi o di tool per la generazione di codice), ho deciso di dedicarle una particolare attenzione, e quindi la trattazione risulterà piuttosto lunga.
Il primo passo consiste sicuramente nell’effettuare la connessione al database. In SMO una connessione è rappresentata dalla classe ServerConnection facente parte del namespace Microsoft.SqlServer.Management.Common. Questa classe ha sei costruttori, ma per il momento ci occuperemo di solo due di essi. Il primo è il costuttore che non accetta parametri, il che indica la volontà di effettuare una connessione all’istanza di default di SQL Server sulla macchina locale.
L’altro costruttore è quello che accetta come unico parametro il nome del server su cui vogliamo puntare. Se sul server è presente solo una istanza, sarà sufficente il nome del server, altrimenti si dovrà concatenare al nome del server il carattere “\” seguito dal nome dell’istanza alla quale si vuole puntare.
Per esempio, per puntare al server SQLServ_001 su cui gira una sola istanza di SQL Server, sarà sufficiente passare al costruttore la stringa “SQLServ_001”. Se invece sullo stesso server ci sono due o più istanze, e dobbiamo puntare, ad esempio, all’istanza chiamata Ist01, dovremo passare al costruttore la stringa “SQLServ_001\ Ist01”
Ad aiutarci nel compito di generare la stringa da passare al costruttore appena esaminato, SMO ci mette a disposizione un utilissimo strumento, la classe SmoApplication, ed in particolar modo il suo metodo statico EnumAvailableSqlServers, per il quale conviene sicuramente aprire una parentesi.
Il metodo EnumAvailableSqlServers ha come scopo quello di rilevare tutti i server su cui è raggiungibile SQL Server, e per ognuno di essi le istanze rilevate. Questo metodo ha tre overload. Il primo, che non accetta parametri, fa esattamente quanto appena detto. Esegue una scansione di tutta la rete, per individuare i server su cui è raggiungibile SQL Server. Gli altri due servono a restringere il range di ricerca solo localmente o a puntare ad un computer specifico.
Per restringere il range di ricerca, si utilizza la versione che accetta il parametro booleano localOnly. Se lo impostiamo su True, effettueremo una ricerca solo localmente, mentre impostandolo su False, effettueremo una ricerca anche sulla rete. E’ ovvio che in quest’ultimo caso, la ricerca potrebbe durare anche doversi minuti.
Utilizzando invece la versione che accetta un parametro di tipo stringa, chiamato name, si indica il nome del computer su cui è instalalta l’istanza di SQL Server che vogliamo raggiungere, e la ricerca di SQL Server e delle sue istanze verrà eseguita solo su di esso.
Tutte le informazioni raccolte durante la ricerca, vengono rese disponibili tramite un datatable composto dalle seguenti colonne :
La maggior parte di queste colonne, sono autoesplicative. Ma quelle utili al nostro scopo sono le colonne Name, Server e Istance. La colonna server contiene (guarda un pò), il nome del server, mentre la colonna istance contiene … il nome dell’istanza ! Perché allora esiste una colonna name ? La colonna name contiene il nome del server ed il nome dell’istanza (se ce n’è più di una) già concatenate tra loro, quindi già pronte per poter essere utilizzate dal costruttore dell’oggetto ServerConnection. Nel caso che l’istanza sia una sola, la colonna name e la colonna server conterranno lo stesso valore, mentre la colonna istance sarà vuota.
Anche la colonna Version è molto utile in questa fase. Questa colonna infatti ci dice che versione di SQL Server è in esecuzione sul server rilevato. Se ci interessa la versione 2005, il valore restituito nella colonna dovrà cominciare per 9, mentre per il 2000 dovrà comincerà per 8. Quindi, se dobbiamo collegarci solo a delle istanza di SQL Server 2005, dovremo verificare che il valore contenuto nella colonna Version per il server di nostro insteresse cominci per 9.
A questo punto possiamo vedere un breve frammento di codice che interroga le istanze raggiungibili di SQL Server sulla rete locale e aggiunge i server (con le eventuali istanze) rilevati ad una ComboBox chiamata cmbRegServers :
Una volta istanziato l’oggetto ServerConnection, ed aver stabilito a chi vogliamo connetterci, dobbiamo decidere in che modo dobbiamo auenticarci, ossia se usare la modalità mista oppure la modalità SQL Server (il valore di default è la modalità mista, così come suggerito da Microsoft).
Cominciamo con la tipologia di autenticazione mista (ossia tramite un account di Windows) attivabile tramite la proprietà booleana LoginSecure (di default True). Se impostiamo questa proprietà su su False, stiamo richiedendo una autenticazione tramite un account di SQL Server, mentre se la si imposta (o la si lascia impostata) su True, si stà richiedendo una autenticazione tramite account di Windows.
In quest’ultimo caso, possiamo decidere se usare l’account corrente (quello con cui si è effettuato il login sulla macchina) oppure utilizzare le credenziali di un altro account. Se vogliamo utilizzare un altro account, imposteremo la proprietà ConnectAsUser su True, altrimenti possiamo lasciarla impostata sul suo valore di default, ossia False
Se la proprietà ConnectAsUser viene impostata su True, dovranno, ovviamente, essere fornite anche le credenziali dell’account che si vuole utilizzare. Queste credenziali vengono impostate tramite le proprietà ConnectAsUserName e ConnectAsUserPassword, che accettano entrambe una stringa.
Ecco un esempio riassuntivo di una richiesta di autenticazione tramite un account Windows, ma diverso da quello corrente (in questo esempio, come negli altri riguardanti le connessioni, si assume una connessione alla macchina locale) :
La seconda possibilità di autenticazione è tramite un account di SQL Server. Questa tipologia di autenticazione, come abbiamo visto, si richiede impostanto la proprietà LoginSecure su False, mentre per fornire le credenziali è sufficiente impostare le proprietà Login e Password, davvero molto semplice !
Ecco un esempio di richiesta di autenticazione tramite un account SQL Server del quale si forniscono le relative credenziali (la proprietà ConnectAsUserName ovviamente non occorre più) :
Ora che abbiamo istanziato ed inizializzato in nostro oggetto ServerConnection, possiamo finalmente instaurare la connessione fisica con SQL Server. Per poter aprire la connessione, si utilizza il metodo Connect, mentre per chiuderla si utilizza il metodo Disconnect (consentitemi di aggiungere “che fantasia !!”).
E’ bene sottolineare che spesso la chiusura esplicita di una connessione tramite il metodo Disconnect non serve. Questo perché una connessione viene aperta automaticamente quando richiesta, e allo stesso modo, se il metodo Disconnect non viene invocato, sarà lo stesso SMO a chiuderla automaticamente. Quindi, possiamo non preoccpuarci troppo se dimentichiamo di chiuderla, ma è bene ricordare che è sempre una buona pratica di programmazione chiudere e deallocare manualmente gli oggetti come ad esempo proprio le connessioni.
Tra i quattro costruttori non esaminati dell’oggetto ServerConnection, ne esistono due che consentono di fornire direttamente le credenziali, senza l’utilizzo delle proprietà. Uno dei due, tra l’altro, accetta la password in modo criptato (per maggiori dettagli fare riferimento all’oggetto System.Security.SecureString).
Sempre restando in tema di credenziali di accesso a SQL Server, una funzionalità che sicuramente può risultare molto utile, consente di modificare la password di accesso ad un account. La password può essere modificata contestualmente all’autenticazione grazie al metodo ChangePassword.
Eccone un breve esempio :
Dim _ServerConnection As New ServerConnection
Infine, vorrei concludere l’argomento connessione con un accenno sui pool delle connessioni. Il comportamento di default di SMO è quello di utilizza il pool delle connessioni. Dato che però l’argomento è davvero vasto, e lo spazio è tiranno, per chi desiderasse approfondire l’argomento, consiglio di cercare informazioni sulle proprietà NonPooledConnection, MinPoolSize, MaxPoolSize, e PooledConnectionLifetime dell’oggetto ServerConnection.
A questo punto, dopo aver esaminato come fare a collegarsi ad un server, è arrivato il momento di presentare la classe più importante di tutta la libreira SMO, la classe Server.
La classe Server rappresenta un’intera istanza di SQL Server, e ci consente, con estrema facilità, di interrogare, gestire ed effettuare una vastissdima gamma di operazioni.
La classe Server ha tre costruttori. Il primo accetta un oggetto ServerConnection, che abbiamo esaminato abbondantemente in precedenza. Il secondo accetta una stringa corrispondente al nome del server su cui gira l’istanza di SQL Server a cui vogliamo collegarci e la cui formattazione segue le stesse regole viste per lo stesso costruttore dell’oggetto ServerConnection ed infine il terzo, che non prevede parametri.
Quando si usa il seondo costruttore, ossia quello che accetta il nome del Server, in verità è come se si stesse usando il primo costruttore, ossia quello che accetta un oggetto ServerConnection, oggetto creato a sua volta utilizzando il costruttore che accetta il nome del server.
Lo stesso dicasi per il terzo e ultimo costruttore dell’oggetto Server, quello che non prevede parametri. Anche in questo caso, si stà creando un oggetto Server come se si stesse utilizzando il costruttore dell’oggetto ServerConnection tramire il suo costruttore senza parametri.
A prescindere da come si è creato l’oggetto Server, e a prescindere da come si è deciso di autenticarsi, ora si ha a disposizione, tramite l’oggetto Server stesso, una copia dell’istanza a cui ci si vuole collegare, ed è possibile quindi cominciare a lavorarci in modo più concreto.
Il modello di oggetti di SMO ha come scopo principale quello di consentire l’interrogazione di una istanza di SQL Server rappresentandola in modo gerarchico. Ed è per questa ragione che ho deciso di inserire qui questa breve introduzione. Fino ad ora non abbiamo interrogato una istanza di SQL Server, ma soltanto “trasferito” tale istanza nell’oggetto preposto a rappresentarla, ossia l’oggetto Server, e per far questo, abbiamo usato solo delle classi accessorie.
Ora che abbiamo una istanza di SQL Server, e che dobbiamo cominciare concretamente a lavoraci su, ecco che il modello di oggetti vero e proprio di SMO entra realmente “in funzione“, e questa breve spiegazione introduttiva certamente renderà più chiara la lettura del resto dell’articolo.
Abbiamo detto che SMO rappresenta gli oggetti di una istanza in modo gerarchico. Alla cima della piramide troviamo sempre l’oggetto Server. L’oggetto Server deriva, a sua volta, dall’oggetto SqlSmoObject. Per estendere la sua gerarchia, l’oggetto Server utilizza delle collections di oggetti figlio, tra cui spiccano sicuramente i database. Queste collections ereditano tutte dall’oggetto SmoCollectionBase.
Ogni oggetto figlio, contenuto nella collection dell’oggetto padre, deriva a sua volta dall’oggetto SqlSmoObject, quindi condivide buona parte degli attributi dell’oggetto padre. Anche l’oggetto figlio, a sua volta, contiene collection (sempre ereditate dall’oggetto SmoCollectionBase) per estendere a sua volta la gerarchia degli oggetti.
Facendo un esempio pratico, partiamo dall’oggetto Server, e quindi dalla cima della piramide. L’oggetto Server espone (tra le tante) una collection chiamata Databases contenente degli oggetti Database (i nomi degli oggetti sono assolutamente autoesplicativi, quindi non occorre vi dica cosa rappresentano !) . Ogni oggetto Database (che deriva a sua volta dall’oggetto SqlSmoObject) facente parte di questa collection, espone a sua volta varie collections, tra le quali la collection chiamata Tables, contenente oggetti Table. Ogni oggetto Table di questa collection espone a sua volta varie collections, tra cui la collection chiamata Columns contenente oggetti Column, e così via, sempre rispettando questa struttura gararchica, fino ad arrivare agli oggetti foglia.
Dato che tutti questi oggetti derivano dall’oggetto SqlSmoObject, e tutte le collections derivato dall’oggetto SmoCollectionBase, il risultato finale sarà una assoluta omogeneità di tutta la libreria, che ne rende estremamente semplice e intuitivo l’utilizzo.
Interrogazione dell’istanza
Nota
Da questo momento in poi, si presume che sia disponibile un oggetto Server chiamato _server, correttamente inizializzato.
Per cominciare, vediamo come elencare tutti i database presenti nell’istanza corrente indicando anche se si tratta di un database di sistema oppure no :
Come possiamo vedere da questo frammento di codice, stiamo utilizzando la collection Databases dell’oggetto _server, che elenca tutti i database presenti nell’istanza, ognuno dei quali è rappresentato da un oggetto Database. Usando un ciclo for each, leggiamo un’istanza di databse alla volta, e per ognuna di essa verifichiamo la proprietà IsSystemObject, che ci dice se si tratta di un database di sistema (True) oppure no (False). Questa proprietà è utilissima anche quando si vole lavorare solo con oggetti non di sistema, dato che la stessa identica prorpietà la troviamo anche nell’oggetto Table, che rappresenta una tabella.
Quindi, per ogni database, viene utilizzata la proprietà Name, che ci restituisce il nome del database. Prima del nome del database, viene concatenata una stringa che ci indica se si tratta di un database di sistema oppure no.
Ovviamente, trattandosi di collections, anziché ciclare tutti i database di una istanza, possiamo puntare direttamente al database che ci interessa per nome, o per indice. Vediamo come ottenere un’istanza di un database puntandovi via nome :
L’oggetto Database espone un numero enorme di proprietà, metodi e collections utilissimi sia nell’ottica gestionale che di interrogazionne. A titolo di esempio, visto che ci vorrebbe un libro solo per citarli, ne ho elencati qui alcuni che potrebbero essere particolarmente utili :
Proprietà
|
Valore restituito
(*)L’oggetto fa parte del namsespace Microsoft.SqlServer.Management.Smo
|
Descrizione
(Tutti gli oggetti elencati fanno parte del namsespace Microsoft.SqlServer.Management.Smo)
|
ActiveDirectory
|
DatabaseActiveDirectory (*)
|
Restituisce una collection di oggetti DatabaseActiveDirectory il quale consente la gestione delle impostazioni di Active Directory. |
Assemblies
|
SqlAssemblyCollection (*)
|
Restituisce una collection di oggetti SqlAssembly che rappresenta tutti gli assemblies definiti in una tabella.
|
CreateDate
|
Date
|
La data di creazione del database
|
DefaultSchema
|
String
|
Lo schema di default dell’utente
|
FileGroups
|
FileGroupCollection (*)
|
Restituisce una collection di oggetti FileGroup il quale rappresenta il filegroup definito nel database.
|
Roles
|
DatabaseRoleCollection (*)
|
Restituisce una collection di oggetti DatabaseRole il quale rappresenta una regola definita nel database.
|
Rules
|
RuleCollection (*)
|
Restituisce una collection di oggetti Rule il quale rappresenta tutti I ruoli definiti in una istanza di SQL Server.
|
Schemas
|
SchemaCollection (*)
|
Restituisce una collection di oggetti Schema il quale rappresenta uno schema del database referenziato.
|
StoredProcedures
|
StoredProcedureCollection (*)
|
Restituisce una collection di oggetti StoredProcedure il quale rappresenta una stored procedure dichiarata nella tabella.
|
Triggers
|
DatabaseDdlTriggerCollection (*)
|
Restituisce una collection di oggetti DatabaseDdlTrigger il quale rappresenta un trigger definito in una tabella (trigger DDL).
|
Urn
|
Urn (*)
|
L’oggetto Urn consente un accesso diretto via codice ad un oggetto specifico a mezzo del suo Uniform Resource Name (URN) che lo identifica univocamente tra gli oggetti di SQL Server. |
Ho volutamente lasciato a proprietà URN in coda, perché vorrei aprire una breve parentesi in merito. Oltre a quanto visto fino ad ora, SMO identifica gli oggetti anche attraverso un altro metodo, ossia utilizzando gli URN. Un URN indica la locazione di un oggetto nell’ambito del Server, ma, cosa più importante, è persistente. Da questo ne consegue che conoscendo l’URN di un oggetto, è possibile puntarvi direttamente senza dover prima attraversare tutta la gerarchia degli oggetti.
Per ottenere l’URN di un oggetto, si usa la proprietà Urn. Ad esempio, per conoscere l’URN di una tabella chiamata Tabella1 in un oggetto database chiamato Database1, si può usare il seguente codice (Database1 è un oggetto Database correttamente valorizzato) :
Se si è conservato l’URN della tabella, e vi si vuole accedere in un secondo momento, si utilizzerà il metodo statico GetSmoObject dell’oggetto Server, che accetta come unico parametro l’URN dell’oggetto cercato. Se l’oggetto esiste, ne verrà restituito un riferimento, altrimenti verrà restituito Norhing.
Gli URN sono un argomento molto complesso perché, tra l’altro, per poter essere utilizzati in modo profiquo, richiedono una buona conoscenza di Xpath. Per questa ragione, l’argomento non verrà ulteriormente approfondito in questa sede. Tuttavia, data la sua indubbia utilità, non sarebbe una cattiva idea decidere di approfondire l’argomento.
Da questo momento in poi procederemo più spediti, visto che abbiamo focalizzato i punti principali in merito all’utlilizzo di SMO e quindi possiamo concentrarsi solo sul codice finalizzato ad un determinato fine.
Questa collection si allinea perfettamente a tutte le collection, quindi è possibile scorrete tutte le tabelle di un oggeto database oppure puntare direttamente ad una di esse tramite indice o nome. Ecco un semplice esempio in merito. In questo codice istanziamo un oggetto Database puntandovi usando il suo nome, cicliamo le tabelle che vi sono contenute (scartando quelle di sistema) e con i nomi delle tabelle rilevate man mano popoliamo una listbox chiamata ListBoxTables. Infine associamo a due controlli PropertyGrid rispettivamente l’oggetto Database e la prima tabella tra quelle rilevate, in modo da poterne esaminare le rispettive proprietà più comodamente :
Nota
Il controllo PropertyGrid non è disponibile di default, e va aggiunto esplicitamente nella barra degli strumenti. Per far questo selezionare la voce “Scegli elementi …” dal menu contestuale della barra stessa, poi il tab “Componenti di .NET Framework” ed infine spuntare il controllo PropertyGrid.
Continuando ad addentrarci sempre più in profondità nella gerarchia degli oggetti, incontriamo l’oggetto colonna. Una colonna è rappresentata, dall’oggetto Column. E anche nel caso delle colonne, è possibile avvalersi di una collection, la collection Columns, che elenca tutte le colonne in una tabella rappresentata dall’oggetto Table.
Ecco quindi un esempio che istanzia un oggetto Table ed elenca le colonne che lo compongono aggiungendone i nomi e alcuni attributi ad una listbox chiamata ListBoxColums.
Anche in questo caso, per esaminare più comodamente le proprietà dell’oggetto Column, possiamo associare ogni singolo oggetto Column ad una PropertyGrid, ad esempio a seguito dell’evento SelectedIndexChanged della listbox ListBoxColums, come in questo esempio :
Ovviamente con questi oggetti e queste collection è possibile scrivere strumenti certamente più sofisticati e più utili di quanto proposto fin’ora, ma per me, vorrei ribadirlo ancora, l’importante era presentare il funzionamento generale di SMO e farvi comprendere le sue potenzialità.
In quest’ottica, direi che a questo punto si sia esaminata sufficentemente l’are di interrogazione e che quindi si possa passare alla seconda area della libreria SMO, dedicata alle funzionalità di gestione.
Gestione
Con questo argomento, andiamo ad esaminare l’area più gestionale di SMO. Per area gestionale si intende una raccolta di funzionalità dedite allo svolgimento di un compito, il quale può essere un backup piuttosto che un restore, la menutenzione delle tabelle (creazione, rimozione o altro), generazione degli script (che rientra anche in una forma di backup parallelo) e via dicendo. Per prima cosa andremo ad esaminare la gestione di un database e, più specificatamente, come crearlo e come eliminarlo.
Abbiamo già presentato l’oggetto Database in precedenza, quindi non ripeteremo ancora quei concetti qui. Esaminiamo invece una serie di metodi utili alle operazioni di gestione sui database cominciando dalla creazione di un database nuovo.
Per creare un database, è sufficiente chiamare il metdodo Create. Il metodo Create ha due overload, uno senza parametri, che crea il database direttamente nell’istanza indicata dall’oggetto Server, e il secondo che accetta un parametro booleano chiamato forAttach che indica se il database deve essere creato per essere poi collegato (True) oppure no (False).
Nell’esempio seguente, creeremo un nuovo database chiamato Test e subito dopo lo elimineremo :
Dim _database As Database = New Database(_server, "database_name")
In questo frammento di codice possiamo esaminare anche il secondo costruttore dell’oggetto Database (il primo non accetta parametri) che accetta (ovviamente) un oggetto Server e il nome del database. Immediatamente dopo la sua creazione, il database viene eliminato grazie al metodo Drop.
La creazione di un database di per se, non serve a gran che se non è possibile aggiungervi all’interno delle tabelle, e nelle tabelle le colonne con le relative chiavi, indici, costraints, e quant’altro serva ad un database operativo. Ecco quindi un esempio un po’ più completo per la creazione di un database chiamato DatabaseDiTest e l’aggiunta di una semplice tabella chiamata Test composta da tre colonne chiamte ID (che è una colonna identity), RagioneSociale e DataDiInserimento. Inoltre, sulla colonna ID, creeremo una chiave primaria:
Tra le funzionalità dei database piuttosto complesse da gestire senza strumenti visuali quali Menagement Studio, c’è sicuramente la generazione degli script delle tabelle. Questa procedura è molto utile anche nell’ambito dei backup, perché si potrebbe prevedere il salvataggio, oltre ai dati di per se, anche gli script di generazione dei database, perché non si sa mai ! Come è possibile immaginare, con SMO, questa operazioen viene estremamente semplificata.
Come molti altri argomenti, anche questo argomento è molto vasto, non per la generazione degli script di per se, ma più che altro perchè la creazione degli script racchiude in se molte opzioni e sfumature, tra cui includere negli script l’eventuale eliminazione di oggetti già esistenti, oppure la gestione delle dipendenze, e così via.
In questa parte dell’articolo, mi limiterò a presentare solo la parte più semplice (e direi sostanziale) della generazione di uno script per l’oggetto tabella. Se occorre una trattazione più dettagliata dell’argomento, consiglio di esaminare gli oggetti Scripter o ScriptingOptions (giusto per citarne alcuni), tramite i quali si può ottenere molta più granularità e molto più controllo (oltre che varietà) sulla generazione degli script in genere.
Per generare uno script di una tabella, si utilizza la collection Script dell’oggetto Table. Come il nome stesso ci suggerisce, questa collection contiene lo script tramite il quale è possibile rigenerare la tabella genitrice. Ma perchè una collection ? Perchè in verità l’oggetto Table espone lo script come una serie di comandi sqeuenziale dove quindi troveremo per primo, il comando SET ANSI_NULLS ON (questo è quanto successo secondo la configurazione di SQL Server sul mio PC, ma è una regola piuttosto generica), poi il comando SET QUOTED_IDENTIFIER ON ed infine lo script di generazione vero e proprio. Tutto questo ha il fine principale di offrire una maggiore granularità sulla generazione dello script stesso. Quindi per generare l’intero script, è necessario davvero poco codice :
Procediamo velocemente verso un nuovo argomento, il trasferimento di una o più tabella da un database ad un altro. In questo caso entra in gioco l’oggetto Transfer che, come il nome ci suggerisce, si occupa di gestire l’effettiva operazione di trasferimento. Ma prima di esamniare il codice, c’è una cosa molto importante da dire. Perché l’oggetto Transfer possa fare il suo lavoro, è necessario che sia installato DTS su entrambi i server, altrimenti verrà sollevata una eccezione. Esaminiamo ora il codice (dato il caso particolare, ho incluso il codice in un blocco try catch :
Se si desidera selezionare in modo più granulare gli oggetti da trasferire, dovremo aggiungere questi oggetti all’ArrayList ObjectList. Come esempio, nel frammento di codice seguente supporremo di voler copiare solo le tabelle selezionate da una listbox chiamata lbSourceTables che è stata popolata con tutte le tabelle del database sorgente :
Proseguiamo questa rapidissima carellata esaminando come effettuare una tra operazioni più importanti che un DBA deve pianificare e soprattuto far funzionare ! Il backup e il restore dei dati con relativa verifica.
Cominciamo dal backup. Il backup viene gestito dall’oggetto Backup. Il backup più semplice richiede che vengano specificate almeno quattro informazioni. Prima di tutto, bisogna specificare l’azione che si vuole intraprendere, tramite la proprietà Action. Questa proprietà accetta uno dei valori esposti dall’enumerazione BackupActionType, ed indica sostanzialmente di cosa si vuol fare il backup.
La terza informazione è il device su cui effettuare il backup. Il device viene impostato tramite il metodo AddDevice della lista di generic Devices dell’oggetto Backup. AddDevice accetta due parametri. Il primo parametro, di nome name è una stringa, ed indica appunto il nome della periferica oppure, nel caso di files, il percorso del file di backup stesso. Il secondo parametro, chiamato deviceType, è una enumerazione, ed indica la tipologia di device che si vuole utilizzare.
Con la quarta ed ultima informazione, si andrà ad indicare se si vuole un backup incrementale oppure no tramite la proprietà booleana Incremental.
‘ Creo l’oggetto Backup
Una volta effettuato il backup, è sempre buona usanza verificare che tale backup sia valido ! Per la verifica di un backup si utilizza l’oggetto Restore, che è lo stesso che verrà utilizato in seguito per effettuare il restore del backup appena effettuato, ma dato che quello che ci serve per ora è solo fare una verifica, affronteremo l’oggetto Restore dopo, con maggiore dettaglio.
Quello che ci serve al momento per la verifica è solo un device da cui leggere i dati, e il metodo SqlVerify (che richiede anch’esso un oggetto Server) esposto dallo stesso oggetto Restore. Se il metodo SqlVerify restituisce True la verifica è andata a buon fine, altrimenti qualcosa non ha funzionato.
Ecco un breve esempio :
L’ultimo passaggio, consiste nell’effettuare il restore. Come già accennato, il restore avviene a mezzo dell’oggetto Restore. Il restore di un backup è un’operazione quasi speculare al backup stesso, a parte per le opzioni che non sono rilevanti per un restore come ad esempio l’essere incrementale. Il restore vero e proprio viene lanciato a mezzo del metodo SqlRestore. Anche il metodo SqlRestore richiede un oggetto Server.
Ecco un breve frammento di codice di esempio :
Prima di concludere l’articolo, vorrei tornare velocemente a quanto da me detto nell’introduzione, e più precisamente al raffronto tra SMO e ADO.NET. Vorrei farvi vedere, giusto per un discorso di completezza, come eseguire un’operazione fatta da SMO con ADO.NET. Nell’esempio specifico elencheremo le informazioni di un database.
Nella versione 2.0 di ADO.NET (quindi se si fa riferimento alle versioni precedenti alla 2.0 il sistema più rapido per elencare le tabella di un database era effettuare una query filtrata su INFORMATION_SCHEMA.TABLES) viene messo a disposizione dell’oggetto SqlConnection il metodo GetSchema.
Il metodo GetSchema ha tre versioni. La prima, non accetta parametri. Se utilizzata restituisce un datatable contenente una serie di informazioni del database tra cui i nomi delle tabelle, delle viste e così via. Quindi, per poter trovare ciò che ci serve, dobbiamo filtrare questi risultati.
La seconda versione accetta un parametro collectionName di tipo stringa. Questo parametro ci consente di indicare esattamente che cosa vogliamo cercare. Quindi, se stiamo cercando solo le tabelle, valorizzeremo questo parametro con la stringa “Tables”. Per semplificare questa operazione, ADO.NET fornisce l’enumerazione SqlClientMetaDataCollectionNames, che di fatto ci evita l’oneroso compito di imparare a memoria tutte le stringhe associate ai singoli oggetti. Quindi, per elencare tutte le tabelle, ma utilizzando questa volta l’enumerazione, passeremo al metodo non la stringa “Tables”, ma bensì il valore SqlClientMetaDataCollectionNames.Tables.
Ecco di seguito un esempio :
Infine, andiamo ad esaminare la terza versione del metodo GetSchema. Questa versione accetta come la precedente un parametro collectionName di tipo stringa, ma in più accetta un secondo parametro restrictionValues che è un array di 4 stringhe. Tramite questo parametro, è possibile aggiungere un ulteriore granularità alle ricerche. I valori assumibili da questo secondo parametro possono combinarsi tra loro, ma nell’uso più frequente si passerà il nome del db, lo schema, il nome della tabella e un valore Nothing. Ecco un esempio inerente il caso appena descritto :
P.S. Un ringraziamento enorme a David Sceppa e al suo libro “Programmare ADO.NET 2.0” da cui ho attinto varie cosuccie per completare questo paragrafo
Gli esempi che ho voluto riportare qui sono davvero minimi, così come la trattazione dell’argomento stesso. Ma quello che volevo far risaltare è la differenza che esiste tra le due librerie. A parte il fatto che usare il metodo GetSchema con il parametro restrictionValues può portare molto facilmente a errori (si pensi al caso in cui si inizializzi l’array restrictionValues con valori passati come parametri), una delle cose che sicuramente balzerà agli occhi è che a prescindere dal risultato, ottenuto anche applicando molti filtri, non si avrà mai un’istanza dell’oggetto ricercato, e quindi, per poterlo gestire, si dovranno comunque eseguire delle query dirette sul database, con tutte le problematiche che questo approccio comporta, dalle autorizzazioni fino agli errori di sintassi. Ed inoltre, pare anche lampante come SMO offra in questo frangente una sintassi più chiara e semplice rispetto ad ADO.NET.
E’ ovvio che se ci dobbiamo limitare ad elencare le tabelle e le colonne di un database, e poi effettuare comunque solo operazioni di gestione dei DATI, ADO.NET è sicuramente più efficiente e performante rispetto a SMO. Ma se dobbiamo cominciare ad introdurre un discorso di gestione, quindi di padronanza degli oggetti che compongono SQL Serve, è lampante come SMO sia l’unico strumento adeguato a questo compito.
Non mi stancherò mai di ripeterlo. SMO è un vero e proprio universo, che richiederebbe un intero libro per essere usato in modo profiquo, quindi figuriamoci cosa è possibile trattare con solo un articolo. Tuttavia, spero di essere riuscito, con queste mie indicazioni, nell’intento di stuzzicare la vostra curiosità tanto da spingervi ad approfondire lo studio di una tra le librerie del Framework che sicuramente ha più da offrire, e che vi consentirà di lavorare meglio, più rapidamente, in modo più sicuro e, perché no, anche divertendovi.
Tra le tante cose che non ho potuto affrontare o approfondire, c’è tutta la parte di personalizzazione dell’ottimizzazione automatica di SMO nella creazione degli oggetti, un approfondimento sulla gestione delle Stored Procedure, dei Trigger e di altre componenti di SQL Server, così come varie ottimizzazioni tra cui il backup asincrono dei database.
Purtroppo lo spazio a mia disposizione non è infinito, e quindi ho cercato di ottimizzarlo il meglio possibile. Cercherò eventualmente di agiungere altre informazioni con altri articoli o tips, vedremo. Per ora ringrazio tutti coloro che hanno deciso di dare un’occhiata a questo mio primo articolo, sperando che sia stato di loro gradimento !
Per qualsiasi suggerimento, commento, correzione, annotaziome o quant’altro possa venirvi in mente, potete tranquillamente farlo contattandomi a questo indirizzo : gentile.renato@gmail.com. Ve ne sarò comunque grato !