Nel post precedente, abbiamo creato un nuovo Windows
Form per regalare alla nostra applicazione qualche funzionalità in più in
materia di database, per esplorare per bene quello che ci offre ADO.NET. Il form
è quello riportato qui sotto, tutti i dettagli sulla sua implementazione sono
nel post [MCAD.22].
Quello che voglio fare oggi è la stessa cosa che ho anticipato la volta
scorsa. Appena l'utente clicca sul pulsante Ok, vuol dire che desidera da questo momento
utilizzare la nuova stringa di connessione specificata nella TextBox. Prima di
farlo, però, vorrei controllare che 1) la connessione funzioni realmente e
2) esista la tabella ListBirthdays. Vogliamo,
quindi, nell'ordine:
- testare la connessione al server, usando
SqlConnection
- se la connessione funziona, verificare la presenza della tabella
ListBirthdays
- se la tabella esiste, tutto ok, la form può essere
chiusa senza problemi
- se la tabella non esiste, occorre lanciare la stored-procedure
SettingUp che provvede a creare la tabella
ListBirthdays
- se tutto fila liscio, la form può essere chiusa
Come lo facciamo? Beh, innanzitutto creiamo una function
checkTable():
private bool CheckTable()
{ }
Questa function ritorna un bool (true = la tabella esiste oppure è stata creata, false = la connessione non funziona oppure qualche altra
condizione di errore). In questa release ho deciso di lavorare soltanto con
SQL Server, per cui useremo la classe SqlConnection
:
// estratto di codice
if (this.TypeDB == SupportedTypeDB.SQLSERVER)
{
// la funzione ritorna
// false: errore durante il controllo di esistenza della tabella
// true: la tabella esiste oppure è stata creata con successo
// mi connetto al db
SqlConnection conn = new SqlConnection(this.DBConnection);
// creo un Command per fare una semplice SELECT
SqlCommand cmd = new SqlCommand("SELECT id FROM ListBirthdays", conn);
// cmd.Connection = conn;
//cmd.CommandType = CommandType.TableDirect;
cmd.CommandType = CommandType.Text;
cmd.CommandTimeout = 15;
Come si vede, istanzio un oggetto conn di tipo
SqlConnection. Il costruttore prende come parametro la stringa di connessione
dalla property DBConnection (in alternativa, possiamo
direttamente leggere this.txtDBConnection.Text).
Successivamente, istanzio un SqlCommand cmd, facendo una
semplice SELECT sulla tabella ListBirthdays. La classe SqlCommand ha diverse
property: io mi sono divertito a dare un timeout per l'esecuzione del comando
(CommandTimeOut) e il tipo di comando, specificato dall'enum
CommandType.
Notare che al momento della creazione dell'oggetto SqlConnection, la
connessione non viene aperta: lo facciamo noi manualmente nelle righe di codice
successiva. Ovvero:
// apro la connessione
try { conn.Open(); }
catch { return(false); }
// tengo di eseguire il comando
try { cmd.ExecuteScalar(); }
catch { errore = true; }
Tento di aprire la connessione: ho incluso il tutto in un blocco
try...catch: se ci sono problemi, l'errore viene trappato, la
function ritorna false come
abbiamo detto prima. Se la connessione è ok, tento di eseguire il command che
abbiamo predisposto prima: un altro blocco try...catch. Qui ho
banalizzato un po' troppo: se ci sono problemi nell'eseguire il SqlCommand,
invece di ritornare false, decido di gestire
autonomamente la questione: parto dal presupposto che l'unico problema è che la
tabella non esiste, quindi lancio la stored-procedure
SettingUp. Quindi, vediamo il codice:
if (errore == true)
{
// in caso di errore, eseguo la sp SettingUp
cmd = new SqlCommand("SettingUp", conn);
cmd.CommandType = CommandType.StoredProcedure;
try { cmd.ExecuteReader(); }
catch { return(false); }
}
Preparo un SqlCommand nuovo, usando cmd. Questa volta si
tratta di una sp (notare la property CommandType) chiamata,
appunto, SettingUp. Ho messo un altro blocco
try...catch: se anche questa volta ho un errore di qualsiasi
tipo, ritorno false al chiamante. Le ultime linee di
codice chiudono la connessione, rilasciano le risorse. Poi, se tutto è andato
per il verso giusto, ritorno true.
// chiudo la connessione
conn.Close();
// rilascio le risorse
cmd.Dispose();
conn.Dispose();
Questo riportato sopra a "pezzi di codice" è la function
checkTable() che viene eseguita quando l'utente clicca sul
Button Ok del form frmConnection. Il suo
handler è il seguente:
private void btnOk_Click(object sender, System.EventArgs e)
{
if (!CheckTable())
MessageBox.Show(this, "Errore durante il controllo della struttura del database!",
"Errore!", MessageBoxButtons.OK, MessageBoxIcon.Error);
this.Close();
}
Semplicemente, se la function ritorna false, mostro una MessageBox all'utente
dicendo che non siamo riusciti a verificare la struttura del database (inteso
sia come connessione, sia come struttura: tabella e/o stored-procedure). La
stored-procedure SettingUp, di cui non abbiamo parlato, è
semplicemente un'istruzione CREATE TABLE ListBirthdays che genera i campi
id, name e birthday.
Comment e miglioramenti del codice
Nel post di ieri, ho ricevuto un comment da parte di Igor
(che non sono io!) che mi ha proposto due miglioramenti del codice che ho
scritto. Mi sono sembrate entrambe cose piuttosto interessanti, per cui ho
deciso in 2 secondi di integrarle.
La prima è l'utilizzo di un enum
SupportedTypeDB per gestire Access e SQL Server al posto
delle stringhe. Il codiceif (this.TypeDB == SupportedTypeDB.SQLSERVER) utilizza proprio questo enum invece di una banale string
"SQLSERVER". Il codice è quindi cambiato di conseguenza:
get/set della property, lettura da app.config, etc.
Come ho fatto a creare un enum globale? Ho fatto Add --> New Item
--> Code File: il file l'ho chiamato GeneralCode.cs e a questo punto
ho scritto il codice:
public enum SupportedTypeDB
{
ACCESS = 0,
SQLSERVER = 1
}