L'oggetto command è utilizzato per effettuare operazioni sul database. Per collegarsi al database, ha bisogno di un oggetto connection che a sua volta andrà istanziato e/o valorizzato con una connection string valida.
Per utilizzare un command nel nostro codice è utile inserire un riferimento al namespace System.Data e un riferimento anche al dataprovider utilizzato a seconda del database prescelto. Se ad esempio utilizziamo Sql-Server il namespace da usare è System.Data.SqlClient, mentre se utilizziamo Access ci serviremo di tutti gli oggetti offerti dal namespace System.Data.OleDbClient.
Da notare come ogni namespace relativo ad un diverso dataprovider ci offre una serie di oggetti praticamente uguali (es. SqlClient.SqlCommand, OleDbClient.OleDbCommand, OracleClient.OracleCommand ecc.) oltre che nel nome anche nell'utilizzo. Di seguito useremo oggetti provenienti dal namespace SqlClient ma potrete facilmente adattarli anche agli altri dataprovider. Per importare i namespace di SqlServer possiamo scrivere in cima prima della dichiarazione della nostra classe:
VB.Net
Imports System.Data
Imports System.Data.SqlClient
C#
using System.Data;
using System.Data.SqlClient;
In questo modo possiamo fare riferimento direttamente agli oggetti all'interno del namespace SqlClient senza dover richiamare l'intero percorso System.Data.SqlClient.
Come detto l'oggetto command ci permette di interagire con il database. Per farlo, si utilizzano le sue funzioni ExecuteScalar, ExecuteNonQuery, ExecuteDatareader. In seguito approfondiremo una ad una queste funzioni e capiremo quando scegliere una piuttosto che l'altra. Da tenere presente che l'oggetto command ci permette di utilizzare nelle nostre query i parametri (per query si intende un codice che contiene i comandi sql per effettuare operazioni sul database la cui trattazione esula da questo articolo). L'argomento verrà approfondito nel seguito di questo articolo. Ma andiamo per ordine.
Per istanziare un oggetto command con l'oggetto connection e una query sql:
VB.Net
Dim m_conn As New SqlConnection("connectionstring")
Dim m_cmd As New SqlCommand("INSERT INTO Clienti (Nome, Cognome) VALUES ('Mario', ‘'Rossi')", m_conn)
C#
SqlConnection m_conn = new SqlConnection("connectionstring")
SqlCommand m_cmd = new SqlCommand("INSERT INTO Clienti (Nome, Cognome) VALUES ('Mario', ‘'Rossi')", m_conn);
Quando istanziamo un oggetto command, possiamo scegliere se passargli subito
la query sql insieme ad un oggetto connection come nell'esempio appena fatto oppure istanziarlo prima per poi assegnare in un secondo momento la query e la connection come nell'esempio seguente:
VB.Net
Dim m_cmd As NewSqlCommand
m_cmd.Connection = m_conn
m_cmd.CommandText ="INSERT INTO Clienti (Nome, Cognome) VALUES ('Mario',
‘'Rossi')"
C#
SqlCommand m_cmd = new SqlCommand();
m_cmd.Connection = m_conn;
m_cmd.CommandText ="INSERT INTO Clienti (Nome, Cognome) VALUES ('Mario', 'Rossi')"
Da notare che se disponiamo già di un oggetto connection, potremo istanziare un oggetto command direttamente da questo. Esempio:
VB.Net
Dim m_cmd As SqlClient.SqlCommand = m_conn.CreateCommand
C#
SqlCommand m_cmd = m_conn.CreateCommand();
L'oggetto command ci offre come già anticipato una serie di funzioni utili per interagire con il database. Prima di vedere più in dettaglio le funzioni ExecuteNonQuery, ExecuteScalar e ExecuteReader è utile accennare alla proprietà CommandType. Questa proprietà ci consente di scegliere da tre possibilità.
CommandType.Text
Questa è l'impostazione di default e ci permette di valorizzare la proprietà commandtext con una normale query sql. Nel prossimo esempio, prepareremo un command con una query sql di eliminazione:
VB.Net
m_cmd.CommandType = CommandType.Text
m_cmd.CommandText = "DELETE FROM Clienti WHERE IDCliente = 1”
C#
m_cmd.CommandType = CommandType.Text;
m_cmd.CommandText ="DELETE FROM Clienti WHERE IDCliente = 1” ;
CommandType.StoredProcedure
Questa impostazione ci permette di utilizzare una stored procedure. Le stored
procedures sono procedure costituite da uno o più comandi sql e sono memorizzate sul server database. Dopo aver selezionato questa impostazione, possiamo specificare il nome della stored procedure nella proprietà commandtext:
VB.Net
m_cmd.CommandType = CommandType.StoredProcedure
m_cmd.CommandText = "_eliminaCliente"
C#
m_cmd.CommandType = CommandType.StoredProcedure;
m_cmd.CommandText = "_eliminaCliente";
CommandType.TableDirect
Utilizzando questa impostazione, possiamo impostare nella proprietà commandtext il nome di una tabella del database e l'esecuzione del command ci restituirà tutte le righe presenti nella tabella specificata. Attenzione, TableDirect è supportato solamente dal dataprovider OleDb.
VB.Net
m_cmd.CommandType = CommandType.TableDirect
m_cmd.CommandText = "Clienti"
C#
m_cmd.CommandType = CommandType.TableDirect;
m_cmd.CommandText = "Clienti";
è possibile specificare più tabelle e in questo caso il risultato sarà un join tra le tabelle stesse. Per specificare più tabelle si utilizza la virgola come separatore senza aggiungere spazi.
Parametri (Parameters)
È importante sottolineare l'importanza dell'utilizzo dei parametri. È
consigliabile l'utilizzo dei parametri nelle nostre query sia quando si utilizza
una stored procedure sia quando passiamo al command direttamente la query sql.
L'utilizzo dei parametri porta vari benefici in termini di sicurezza e di diminuzione della possibilità di errori. I parametri ci mettono al sicuro rispetto al tipo di dati passati in modo da limitare i rischi di sqlinjection se i dati provengono dall'input di un utente. In più, ci facilitano la gestione dei tipi di dato come il giorno e l'ora o il decimal che possono sempre provocare problemi con le impostazioni del database. Facciamo qualche esempio.
Se dobbiamo inserire un record in una ipotetica tabella clienti di SqlServer possiamo scrivere una query sql come nell'esempio seguente:
INSERT INTO Clienti (Nome, Cognome, DataDiNascita) VALUES ('"& txtNome.Text & ”"', ‘'" & txtCognome.Text & ”"', '"& txtDataDiNascita.Text & "')
Per utilizzare i parametri, possiamo modificare il testo della query inserendo gli identificatori dei parametri in questo modo:
INSERT INTO Clienti (Nome, Cognome, DataDiNascita) VALUES (@nome, @cognome, @datadinascita)
Per poi aggiungere i parametri al nostro oggetto command:
VB.Net
m_cmd.Parameters.Add("@nome", sqldbtype.varchar).Value = txtNome.Text
m_cmd.Parameters.Add("@cognome", sqldbtype.varchar).Value = txtCognome.Text
m_cmd.Parameters.Add("@datadinascita", sqldbtype.date).Value = Date.Parse(txtDataDiNascita.Text)
Negli esempi appena fatti, abbiamo preso come esempio il dataprovider di SqlServer (System.Data.SqlClient ). Nel caso si utilizzi un provider dei dati diverso, è necessario adattare ad esso la sintassi usata per identificare i parametri. Ad esempio OleDb usa il segno "?" mentre il dataprovider specifico di Oracle richiede l'utilizzo del segno ":". Per verificare la sintassi corretta fare riferimento alla documentazione specifica del dataprovider rintracciabile nella documentazione in linea di Visual Studio.
Ora approfondiamo l'utilizzo delle funzioni che ci permettono di eseguire le operazioni sul database.
ExecuteNonQuery
Viene utilizzata questa funzione per le operazioni che non hanno un valore
di ritorno come nel caso delle operazioni di inserimento, modifica o eliminazione di records. l'esecuzione di questa funzione restituisce un valore che nel caso di operazioni di INSERT UPDATE o DELETE corrisponde al numero di righe condizionate (eliminate cancellato o inserite) dalla query. Se l'operazione provoca un errore verrà sollevata una eccezione.
Nel prossimo esempio vedremo da vicino una semplice procedura per l'inserimento di un record nella tabella Clienti. Come noterete, si fa uso dei parametri per leggere il contenuto di 3 textbox in cui l'utente dell'applicazione dovrebbe aver inserito dei dati. Da notare poi l'utilizzo del blocco try finally che ci permette in caso di errore (es. la data di nascita non corretta) di essere sicuri di chiudere la connessione prima di uscire dalla procedura:
VB.Net
Public sub InserisciCliente()
dim m_conn as SqlConnection
dim m_cmd as SqlCommand
dim m_sql as string
m_sql = "INSERT INTO Clienti (Nome, Cognome, DataDiNascita) VALUES (@nome,
@cognome, @datadinascita)"
Try
m_conn = new SqlConnection("Data Source=nomeServer;Initial catalog=nomeDataBase;user id=sa;password=")
m_cmd = new SqlCommand
m_cmd.connection = m_conn
m_cmd.commandtype = commandtext
m_cmd.commandtext = m_sql
m_cmd.Parameters.Add("@nome", sqldbtype.varchar).Value = txtNome.Text
m_cmd.Parameters.Add("@cognome", sqldbtype.varchar).Value = txtCognome.Text
m_cmd.Parameters.Add("@datadinascita", sqldbtype.date).Value = Date.Parse(txtDataDiNascita.Text)
m_conn.Open
m_cmd.ExecuteNonQuery
Finally
m_conn.Close
End Try
End sub
C#
public void InserisciCliente()
{
SqlConnection m_conn = new SqlConnection("Data Source=nomeServer;Initial catalog=nomeDataBase;user id=sa;password=");
try
{
SqlCommand m_cmd = m_conn.CreateCommand();
m_cmd.CommandType = CommandType.Text;
m_cmd.CommandText = "INSERT INTO Clienti (Nome, Cognome, DataDiNascita) VALUES (@nome, @cognome, @datadinascita)";
m_cmd.Parameters.Add("@nome", System.Data.SqlDbType.NVarChar).Value = txtNome.Text;
m_cmd.Parameters.Add("@cognome", System.Data.SqlDbType.NVarChar).Value = txtCognome.Text;
m_cmd.Parameters.Add("@datadinascita", System.Data.SqlDbType.DateTime).Value = DateTime.Parse(txtDataDiNascita.Text);
m_conn.Open();
m_cmd.ExecuteNonQuery();
}
finally
{
m_conn.Close();
}
La procedura è migliorabile. Ad esempio implementando una gestione specifica degli errori dal database o dando la possibilità di passare i valori sotto forma di parametri della funzione invece che leggerli direttamente dai controlli sulla pagina aspx in modo da poter spostare la funzione stessa in una libreria per l'accesso ai dati e servire così a più pagine diverse.
ExecuteScalar
Questa funzione ci ritorna il valore contenuto nella prima colonna della nostra query. Se ad esempio valorizziamo il CommandText del nostro SqlCommand così:
VB.Net
m_cmd.CommandText = "SELECT Cognome, Nome FROM Clienti WHERE IDCliente =
1"
Dim m_risultato as String = m_cmd.ExecuteScalar
dentro la variabile m_risultato troveremo il contenuto del campo Cognome della riga con IDCliente = 1 appunto perchè è la prima colonna indicata nella query sql. L'uso di questa funzione può quindi tornare utile in tutti i casi in cui ci serve un unico risultato (il primo) restituito dalla query di selezione come nei casi in cui vogliamo selezionare il numero di record con la clausola COUNT (es. SELECT COUNT(*) FROM Clienti).
ExecuteReader
L'esecuzione di questa funzione ci restituisce un oggetto datareader. L'oggetto datareader possiamo poi usarlo per leggere velocemente il suo contenuto e ad esempio riempire una combobox.
L'oggetto datareader può contenere uno o più set di risultati provenienti dal database. L'utilizzo di questo oggetto si caratterizza per la velocità di lettura ma comporta alcune limitazioni e in particolare va detto che è forward-only cioè può essere letto dall'inizio alla fine e non da quindi la possibilità allo sviluppatore di scrivere codice per fermarsi ad un record specifico e/o tornare indietro. Da tenere poi presente che il datareader è legato alla sua connessione al database. Non può quindi essere restituito da una funzione per essere usato da un'altra classe indipendentemente dalla connessione legata all'oggetto command che lo ha originato.
Normalmente dopo aver istanziato un oggetto connection e un oggetto command
i passaggi per utilizzare un datareader sono: si apre la connessione, si ottiene
il datareader, si effettua un ciclo sul datareader per leggere i dati, si chiude
il datareader, si chiude la connessione.
Nell'esempio seguente, dalla toolbox di visual studio trasciniamo sulla pagina aspx una dropdownlist. Per popolarla con i valori contenuti nella tabella Clienti del database, scriviamo il seguente codice.
VB.Net
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
'Inserire qui il codice utente necessario per inizializzare la pagina
If Not Page.IsPostBack Then
RiempiDropDownList()
End If
End Sub
Public Sub RiempiDropDownList()
Dim m_conn As SqlClient.SqlConnection
Dim m_cmd As SqlCommand
Dim m_reader As SqlDataReader
Try
m_conn = New SqlConnection("user id=sa;data source=nomeserver;initial catalog=nomedatabase")
m_cmd = m_conn.CreateCommand
m_cmd.CommandText = "SELECT * FROM Clienti"
m_conn.Open()
m_reader = m_cmd.ExecuteReader
While m_reader.Read
Dim item As New ListItem
item.Value = m_reader.GetInt32(0).ToString
item.Text = m_reader.GetString(1)
DropDownList1.Items.Add(item)
End While
m_reader.Close()
Finally
m_conn.Close()
End Try
End Sub
C#
private void Page_Load(object sender, System.EventArgs e)
{
// Inserire qui il codice utente necessario per inizializzare la pagina.
if (!this.IsPostBack)
{
RiempiDropDownList();
}
}
public void RiempiDropDownList()
{
SqlConnection m_conn = new SqlConnection("server user id=sa;data source=nomeserver;initial catalog=nomedatabase"); SqlCommand m_cmd;
SqlDataReader m_reader;
try
{
m_cmd = m_conn.CreateCommand();
m_cmd.CommandText = "SELECT * FROM Clienti";
m_cmd.CommandType= CommandType.Text;
m_conn.Open();
m_reader = m_cmd.ExecuteReader();
while (m_reader.Read())
{
System.Web.UI.WebControls.ListItem item = new ListItem();
item.Value = m_reader.GetInt32(0).ToString();
item.Text= m_reader.GetString(1);
DropDownList1.Items.Add(item);
}
}
finally
{
m_conn.Close();
}
}
In conclusione, è utile ricordare che il command può essere usato all'interno di un ciclo per effettuare operazioni su più records. Non dimentichiamoci infatti che anche il dataadapter utilizza dei normali oggetti command (dataAdapter.InsertCommand, dataAdapter.UpdateCommand, dataAdapter.DeleteCommand) per eseguire le operazioni sul database. Per questo, possiamo considerare il datadapter come un oggetto che risparmia al programmatore molto codice ma se il programmatore ha bisogno di un maggiore controllo sulle modifiche può anche scegliere di scrivere lui la procedura utilizzando dei commands senza bisogno del dataadapter.