Nel post [MCAD.23] ho scritto alcune linee di codice relative a ADO.NET che hanno ricevuto qualche comment. Ho deciso quindi di riportare i consigli e i miglioramenti di Igor e di Daniele. Questi cambiamenti hanno a che fare sia con ADO.NET, sia con la buona programmazione C#, per cui ritengo questo post una sorta di add-on, un extra che chi vuole può saltare. Dunque, vediamo un po' di cosa si è parlato.
Enum SupportedTypeDB
Cominciamo con le cose più semplici. La mia applicazione Age gestisce un file app.config che inizialmente era scritto in questo modo:
<add key="TypeDB" value="SQLSERVER" />
<add key="DBConnection" value="Persist Security Info=True;Integrated Security=SSPI;database=age;server=SPOCK;Connect Timeout=30"/>
La key DBConnection non ci interessa: è semplicemente una stringa di connessione verso un database. La key TypeDB invece è anch'essa una stringa, che nel caso qui sopra vale "SQLSERVER". Il codice dell'applicazione, nonchè il form frmConnection che abbiamo creato in [MCAD.22] lavorava quindi con TypeDB come se fosse una stringa a tutti gli effetti (vedere ad esempio la property get/set della form che abbiamo creato). Igor mi ha giustamente consigliato di cambiare, ovvero di gestire TypeDB come un enum, cosa che ho prontamente fatto: ho aggiunto al progetto il file GeneralCode.cs, definendo un public enum:
public enum SupportedTypeDB
{
ACCESS = 0,
SQLSERVER = 1
}
In questo modo, la key TypeDB in app.config è stata modificata da "SQLSERVER" al valore "1".
L'hanlder mnuSetup_Click adesso legge TypeDB dal file app.config in questo modo:
SupportedTypeDB TypeDB = (SupportedTypeDB) Convert.ToInt32(ConfigurationSettings.AppSettings["TypeDB"]);
string DBConnection = ConfigurationSettings.AppSettings["DBConnection"];
Ovvero, viene fatto il cast al tipo di dato SupportedTypeDB, convertendo in int la string ritornata da ConfigurationSettings.AppSettings(key). In questo modo, il codice è più leggibile, perchè Intellisense ci viene incontro ogni volta che abbiamo a che fare con il tipo SupportedTypeDB.
SqlCommand.ExecuteReader Vs. SqlCommand.ExecuteNonQuery
La function CheckTable() utilizza in due punti diversi lo stesso SqlCommand:
- La prima volta per eseguire una SELECT sul server
- La seconda volta per eseguire una stored-procedure
Nel primo caso ho usato la chiamata cmd.ExecuteScalar(). Questo metodo ritorna il valore della prima colonna della prima riga del recordset ritornato dalla SELECT: nel mio codice ignoro questo valore, ma nel caso mi servisse, il metodo è quello corretto, direi.
Nel secondo caso invece ho utilizzato erroneamente il metodo cmd.ExecuteReader(). Assolutamente inutile. Perchè non ci ho pensato mentre scrivevo il codice? Il metodo ExecuteReader ritorna un SqlDataReader, che ci permette di leggere un recordset in forward-only. A me in questo caso non serve, per cui Daniele mi ha consigliato di eseguire il SqlCommand con il metodo ExecuteNonQuery():
try { cmd.ExecuteNonQuery(); }
Stringa di connessione al database
Nel file app.config la key DBConnection è la seguente:
Persist Security Info=True;Integrated Security=SSPI;database=age;
server=serverSQL;Connect Timeout=30
Da questa pagina su MSDN, vediamo velocemente i parametri che sono stati usati per costruire questa stringa. Io ho avuto diversi problemi prima di poter avere una string connection funzionante: l'apertura di una SqlConnection con una string connection errata mi dà un banale e triste errore System Error, senza essere più di tanto preciso.
Persist Security Info può valere true (yes) o false (no). Se vale false, vuol dire che la password non è inclusa nella stringa di connessione. Se vale true, vuol dire che la password è inserita direttamente nella stringa. In questo caso c'è un problema: il fatto di aver messo Integrated Security=SSPI, significa che l'accesso al server viene stabilito tramite le credenziali di sicurezza del nostro account di Windows. Per cui, lo username è, appunto, lo username di Windows. Questo è il motivo per cui la password e lo username non sono incluse nella string connection.
Se volessimo usare la sicurezza di SQL Server, dobbiamo creare un utente (ad esempio, 'age_user'), dare l'accesso per questo utente al database age. A questo punto possiamo usare una string connection così:
<add key="DBConnection" value="Persist Security Info=false;
Integrated Security=false;database=age;server=serverSQL;
Connect Timeout=30;User ID=age_user;Password=lallalerolallala"/>
Integrated Security = false dice a .NET che lo username e la password sono nella stringa di connessione. Ed infatti, ho dovuto aggiungere i parametri User ID e Password, che invece prima avevo omesso. Mi piace più questo ultimo approccio, sinceramente, perchè mi piace pensare agli utenti Windows e agli utenti SQL Server come a due blocchi ben distinti e separati. L'idea di dover creare un account nel dominio Windows per farlo accedere ad un db sotto SQL Server per me è solo una complicazione. Comunque sia, rimando la parola ai guru di sicurezza di UGIdotNET che, ne sono certo, avranno qualche precisazione da farmi. Rimando inoltre alla pagina su MSDN per tutti i dettagli del caso.