Technology Experience

Contenuti gestiti da Igor Damiani
posts - 949, comments - 2741, trackbacks - 15120

My Links

News

  • Questo blog si propone di raccogliere riflessioni, teoriche e pratiche, su tutto quello che riguarda il world-computing che mi sta attorno: programmazione in .NET, software attuale e futuro, notizie provenienti dal web, tecnologia in generale, open-source.

    L'idea è quella di lasciare una sorta di patrimonio personale, una raccolta di idee che un giorno potrebbe farmi sorridere, al pensiero di dov'ero e cosa stavo facendo.

    10/05/2005,
    Milano

Archives

Post Categories

Generale

[MCAD.26] Nuovi miglioramenti del codice sulla gestione di ADO.NET

A seguito del mio ultimo post di ieri, ho ricevuto diversi comment di Andrea e Lorenzo che mi hanno spinto a rivedere e ad ottimizzare il codice C# scritto, appunto, in quel post.

Motivo scatenante: l'utilizzo della classe SqlCommandBuilder. Come ho detto ieri, questa classe serve per facilitare e a velocizzare la creazione dei 3 Command necessari a ADO.NET per fare le sue operazioni sul database (i Command sono InsertCommand, DeleteCommand e UpdateCommand). Tranne in rarissimi casi, però, fretta e bene non vanno mai bene, ed effettivamente provando a visualizzare la CommandText dei 3 SqlCommand si vede che il codice SQL auto-generato potrebbe (leggere deve) essere migliorato, e non poco. Quindi, morale della favola: è giusto sapere che .NET ci mette a disposizione il SqlCommandBuilder, è giusto conoscerlo in vista dell'esame 70-316, ma quando poi si produce codice .NET serio, i 3 SqlCommand vanno creati a mano. Osservazione: la Study Guide di Lorenzo, nè le altre risorse on-line ci dicono che dobbiamo sapere il SqlCommandBuilder. Per superare l'esame dobbiamo saper conoscere le classi e dobbiamo sapere scrivere C#, per cui mi sono messo subito (già ieri sera) a correggere il codice e a migliorarlo al volo. Quindi, veniamo a noi.

Creazione di un SqlCommand con i suoi parametri
Nel form frmExportDB che abbiamo visto ieri, ho creato un metodo privato, che è il seguente:

private SqlCommand getInsertIntoCommand(SqlConnection conn)
{
    
// Definisco il SqlCommand
    
SqlCommand cmd = new SqlCommand("INSERT INTO ListBirthdays (name, birthday) VALUES (@name, @birthday)", conn);
    cmd.CommandType = CommandType.Text;
    
    
// Definisco i suoi due parametri
    
cmd.Parameters.Add(new SqlParameter("@name", SqlDbType.VarChar, 100, "name"));
    cmd.Parameters.Add(
new SqlParameter("@birthday", SqlDbType.SmallDateTime, 4, "birthday"));
    
    
return(cmd);
}

Il codice non è complicatissimo, ma una descrizione la voglio fare comunque. Nel codice sono state usate 2 classi diverse: SqlCommand e SqlParameter. La prima descrive il Command nella sua interezza, la classe descrive ogni singolo parametro che il Command ha bisogno per funzionare correttamente.
La property CommandText del SqlCommand cmd (che poi verrà ritornato al chiamante) è la seguente:

INSERT INTO ListBirthdays (name, birthday) VALUES (@name, @birthday)

Trattandosi di una tabella con due soli campi (ricordiamoci che id è un contatore univoco), la sua INSERT INTO ha bisogno di due soli parametri: il nome della persona e la sua data di compleanno. I parametri vengono indicati dalle stringhe @name e da @birthday. Quindi, non dobbiamo fare altro che creare due SqlParameter per indicare a .NET tutte le informazioni del caso. Il costruttore di questa classe è molto comodo, perchè permette di creare e definire un parametro molto velocemente. Nel codice sopra, ho semplicemente 2 linee di codice per definire 2 parametri nell'SQL. Il costruttore prevede il nome del parametro, il tipo di dati, la lunghezza in bytes e il nome della column source.

Notare l'uso dell'enum SqlDbType che mappa all'interno del framework .NET i tipi di dato di SQL Server (varchar, bit, text, float, image, e così via). La Source Column, di tipo string, contiene il nome della colonna (field, DataColumn) all'interno del DataSet: l'engine di ADO.NET sostituisce automaticamente @name con il campo del DataSet, permettendo quindi la creazione del nuovo record. Il SqlCommand cmd viene ritornato al chiamante (l'handler del Button Ok che abbiamo visto ieri).

Riempire una DataTable con tante DataRow di un'altra DataTable
Mi spiego meglio
Supponiamo (come è nel nostro caso) di avere due tabelle strutturalmente identiche (stessi nomi di campi, stessi tipi di dati, etc.). Supponiamo che contengano dati diversi e records. Cosa dobbiamo fare se vogliamo accodare records da una DataTable all'altra?

Nel codice che ho postato ieri, seguivo questa procedura:

  1. Chiamo il metodo NewRow() sulla DataTable di destinazione
  2. Valorizzo tutte le DataColumn della DataRow appena ritornata da NewRow()
  3. Aggiungo alla collection Rows della DataTable la DataRow
  4. Ricomincio fino a quando non ho finito tutte le DataRow che voglio trasferire

Ieri sera, quasi per caso, ho scoperto l'utilizzo del metodo ImportRow. Ne ho parlato anche in questo posto. Adesso l'handler del Button Ok è il seguente:

private void btnOk_Click(object sender, System.EventArgs e)
{
    
// SqlConnection per la connessione al db
    
SqlConnection conn = new SqlConnection(ConfigurationSettings.AppSettings["DBConnection"]);

    
// se la CheckBox è true, eseguo un SqlCommand
    // per svuotare la tabella di destinazione
    
if (this.chkDeleteData.Checked)
    {
        
string sql = "TRUNCATE TABLE ListBirthdays";
        SqlCommand cmd = 
new SqlCommand(sql, conn);
        cmd.CommandType = CommandType.Text;
        
// apro la connessione, eseguo il command e chiudo
        
conn.Open();
        cmd.ExecuteNonQuery();
        conn.Close();
    }

    
// Creo un SqlDataAdapter per creare il DataSet locale
    
SqlDataAdapter da = new SqlDataAdapter("SELECT * FROM ListBirthdays", conn);

    
// Nel DataSet creo la struttura e i dati della tabella
    // ListBirthdays del database: se ho fatto la DELETE, allora
    // ds.Tables[0].Rows = 0
    
DataSet ds = new DataSet();
    da.Fill(ds, "ListBirthdays");
    
    
foreach (DataRow dr_source in this.Tabella.Rows)
    {
        
// copio i dati dalla tabella Source
        // alla tabella Destination
        
ds.Tables[0].ImportRow(dr_source);

        
// faccio proseguire la barra
        
this.barBarra.Value++;
        
this.Refresh();
    }

    
// invio il DataSet a SQL Server
    // specifico solo l'InsertCommand
    
da.InsertCommand = this.getInsertIntoCommand(conn);

    
// Per cominciare una transazione sul server, la SqlConnection deve
    // essere aperta per forza
    
conn.Open();
    SqlTransaction tran = conn.BeginTransaction(IsolationLevel.ReadCommitted);;
    da.InsertCommand.Transaction = tran;
    
    
try
    
{
        
// tento l'update sul database
        // questa riga richiede un SqlCommandBuilder
        
da.Update(ds, "ListBirthdays");
        
// se tutto va bene, committo la transazione
        
tran.Commit();
    }
    
catch
    
{
        
// altrimenti, rollback
        
tran.Rollback();
    }

    tran.Dispose();
    da.Dispose();
    ds.Dispose();
    conn.Close();
    
this.Close();

    
// conferma all'utente di fine esportazione
    
MessageBox.Show(this, "L'esportazione dei compleanni è stata completata con successo!!",
        "Conferma!", MessageBoxButtons.OK, MessageBoxIcon.Information);
}

Gran parte del codice è rimasto invariato. Gli unici cambiamenti degni di nota sono:

  1. L'uso della classe SqlTransaction (blocco try...catch per fare il rollback)
  2. La chiamata alla function getInsertIntoCommand descritta prima per valorizzare la property InsertCommand del SqlDataAdapter
  3. Il miglioramento del ciclo foreach (metodo ImportRow)

Uso della classe SqlTransaction
SqlTransaction è la classe di ADO.NET che implementa la logica delle transazioni sui database. La classe SqlConnection ritorna un oggetto SqlTransaction dopo la chiamata al metodo BeginTransaction. Lavorando con l'oggetto SqlTransaction, possiamo chiamare i metodi Commit() o Rollback(). Nel codice sopra, creo un oggetto tran proprio per questo motivo.

La chiamata a da.Update(...) è stata inclusa in un blocco try...catch. Se tutto va a buon fine, viene chiamato il metodo Commit() e il database risulta aggiornato a tutti gli effetti. Se qualcosa va storto, viene chiamato il Rollback() per annullare tutto.

powered by IMHO 1.2

Print | posted on martedì 30 agosto 2005 15:02 | Filed Under [ MCAD ]

Feedback

Gravatar

# re: [MCAD.26] Nuovi miglioramenti del codice sulla gestione di ADO.NET

Per cancellare la tabella è meglio una TRUNCATE TABLE che non logga niente ed è molto più veloce... :-)
30/08/2005 15:26 | Lorenzo Barbieri
Gravatar

# re: [MCAD.26] Nuovi miglioramenti del codice sulla gestione di ADO.NET

toh, corretto al volo!
scrivo stored-procedure tutti i giorni, chissà perchè in C# mi scappa sempre la DELETE FROM!
:-)
per essere un dilettante, ne di cose, però!
ovviamente scherzo.... :-p
grazie!
30/08/2005 15:49 | Igor Damiani
Gravatar

# [MCAD.28] Creare un SqlCommand per eseguire una sp con parametri di output

05/09/2005 19:02 | Technology Experience
Comments have been closed on this topic.

Powered by:
Powered By Subtext Powered By ASP.NET