Press "Enter" to skip to content

4 – Lavorare con i dati – ADO.Net i fondamentali: UPDATE e DELETE

In questo terzo articolo dedicato alle operazioni fondamentali di interazione con i Database, ci occuperemo delle altre due funzioni di base, ovvero la modifica dei dati e la cancellazione dei dati dalla tabella dei nostri tre database.

Come sono certa vi aspettate, anche in questo caso le operazioni saranno molto simili, varieranno solo le classi del provider dati utilizzato.

L’interfaccia dell’applicazione modificata sarà la seguente:

Adobasic_04_mainwindow_01

La finestra con un nuovo record pronto da aggiungere, notate che non viene indicato alcun ID.

Adobasic_04_mainwindow_02

Alla selezione di un record sulla tabella, adesso, nella TextBox Input data vengono riportati tutti i suoi dati e dietro al campo Married, per i record della nostra tabella è stato aggiunto l’ID.
Questo per dare un minimo di usabilità alla nostra pure essenziale interfaccia utente.

Le modifiche preliminari

Come abbiamo ottenuto tutto ciò? Ovviamente utilizzando un Evento messo a disposizione dalla DataGrid:

<DataGrid ItemsSource="{Binding Data, Mode=OneWay }" 
          SelectionChanged="DataGrid_SelectionChanged" 
          SelectionMode="Single"
          >

Il tag della DataGrid è stato modificato aggiungendo 2 attributi, SelectionChanged, che ospita l’event handler che useremo per rilevare i dati e SelectionMode che abbiamo impostato a Single, in modo che una sola riga per volta venga selezionata.

private void DataGrid_SelectionChanged(object sender, System.Windows.Controls.SelectionChangedEventArgs e)
{
    if (e.AddedItems != null && e.AddedItems.Count > 0)
    {
        DataRowView drv = e.AddedItems[0] as DataRowView;
        if (drv != null)
        {
            StringBuilder sb = new StringBuilder();
            string sep = "";
            for (int x = 1; x < Data.Columns.Count; x++)
            {
                sb.Append(sep);
                sb.Append(drv[x].ToString());
                if (sep == "")
                {
                    sep = ";";
                }
            }
            //Add the ID at the end for Update
            sb.Append(sep);
            sb.Append(drv[0]);
            InputData = sb.ToString();
        }
    }
}

Il codice dell’Event handler è molto semplice, ogni evento SelectionChanged dei controlli standard (Datagrid, Combobox, Listbox, Listview) nella property AddedItems degli Event Arguments, contiene una lista degli elementi aggiunti alla selezione, vi sono anche quelli eliminati, in un altro array, ma a noi interessa solo questo perché sappiamo che avendo scelto il SelectionMode Single, un solo elemento per volta sarà selezionato.

Quando l’ItemsSource di un controllo lista standard è una DataTable, gli Items non sono delle DataRow, ma delle DataRowView, perché il sistema automaticamente utilizza la sua vista di default che contiene tutte le righe. La DataView di default è una collection di DataRowView (se non sapete cosa sia andate a cercare DataTable su MSDN e leggete gli articoli che sicuramente spiegano molto meglio di quanto possa fare io come è fatta e come funziona).

Tutto ciò premesso, noi prendiamo l’elemento 0 dell’array degli elementi aggiunti e scorriamo tutti i suoi campi a partire dal secondo, ponendoli in uno StringBuilder, che è il mezzo più efficiente per costruire una stringa in .Net.
In fondo alla stringa, aggiungiamo l’elemento 0 che contiene l’ID.

Per quale motivo fare così? Perché in questo modo la funzione di insert funziona ancora perfettamente, e non dobbiamo modificarla e costringere gli utenti a digitare un campo ID fanfaluco davanti ai dati in inserimento.

Non importa che stiamo facendo un esercizio, cerchiamo comunque di pensare sempre a chi userà l’interfaccia, anche solo noi stessi per i nostri test.

Fatte queste modifiche, vedrete che selezionando una riga sulla DataGrid, il suo contenuto sarà copiato nella casella di input dati.

L’implementazione dei button Update e Delete

<Button
     Margin="4,2,4,2"
     Padding="10,4,10,4"
     Click="Update_Click"
     ToolTip="Updates the currently selected record on the database">
                <TextBlock 
         Margin="4,2,4,2"
         HorizontalAlignment="Left"
         VerticalAlignment="Center"
         Text="Update Record"/>
 
            </Button>
            <Button
     Margin="4,2,4,2"
     Padding="10,4,10,4"
     Click="Delete_Click"
     ToolTip="Deletes the currently selected record from the database">
                <TextBlock 
         Margin="4,2,4,2"
         HorizontalAlignment="Left"
         VerticalAlignment="Center"
         Text="Delete Record"/>
 
            </Button>

Abbiamo semplicemente aggiunto 2 Button al nostro StackPanel e aggiunto ad essi l’event handler per il Click del Button.

private void Delete_Click(object sender, RoutedEventArgs e)
{
    try
    {
        DeleteRecord();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
private void Update_Click(object sender, RoutedEventArgs e)
{
    try
    {
        UpdateRecord();
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

Il click dei due Button, semplicemente richiama un metodo. Vediamo come li abbiamo implementati.

La cancellazione del record

private void DeleteRecord()
{
    if (InputData == null)
    {
        MessageBox.Show("You must select the record to delete.", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
        return;
    }
    string[] data = InputData.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
    if (data.Length < 8)
    {
        MessageBox.Show("I need the record ID and it must be the 8th element of the semicolon separated list in the Input Data TextBox.");
        return;
    }
    int id = 0;
    int.TryParse(data[7], out id);
    if (id <= 0)
    {
        MessageBox.Show("The record ID must be a number Greater than Zero");
        return;
    }
    if (UseSqlServer)
    {
        DeleteFromSqlServer(id);
    }
    else if (UseSQLite)
    {
        DeleteFromSqLite(id);
    }
    else
    {
        DeleteFromAccess(id);
    }
}

Come potete osservare dal codice, prima di effettuare la cancellazione vera e propria, selezionando un metodo specifico in base al Database che è attualmente stato selezionato, facciamo alcuni controlli.

  1. Se non ci sono dati nella TextBox non è stato selezionato alcun record o semplicemente i dati sono stati cancellati e non possiamo procedere.
  2. Se non ci sono 8 dati separati da punto e virgola, non possiamo essere certi che sia stato selezionato un record e non possiamo procedere.
  3. Se il valore nell’ottavo dato non è un numero intero positivo, non lo possiamo accettare.

Se fossimo davvero nel mondo reale, dovremmo anche controllare che l’ID selezionato esista nel database, ma, siccome la cancellazione di un dato esistente non da errore o al limite ci darà un errore gestibile, eventualmente lo gestiremo in un applicazione seria, non in questo ambito.

Fatti tutti i controlli, chiamiamo il metodo relativo al database che abbiamo deciso di usare.

private void DeleteFromSqlServer(int id)
{
    try
    {
        using (SqlConnection cn = new SqlConnection(CNS_SqlServer))
        {
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = @"
            DELETE FROM [TbTest]
                WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            SqlParameter[] para = new SqlParameter[] {
                    new SqlParameter( "@ID", id)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
            //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

private void DeleteFromAccess(int id)
{
    try
    {
        using (OleDbConnection cn = new OleDbConnection(CNS_Access))
        {
            OleDbCommand cmd = new OleDbCommand();
            cmd.CommandText = @"
            DELETE FROM [TbTest]
                WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            OleDbParameter[] para = new OleDbParameter[] {
                    new OleDbParameter( "@ID", id)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
            //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}
 
private void DeleteFromSqLite(int id)
{
    try
    {
        using (SQLiteConnection cn = new SQLiteConnection(CNS_SqLite))
        {
            cn.SetPassword("TestPwd");
 
            SQLiteCommand cmd = new SQLiteCommand();
            cmd.CommandText = @"
            DELETE FROM [TbTest]
                WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            SQLiteParameter[] para = new SQLiteParameter[] {
                    new SQLiteParameter( "@ID", id)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
            //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

I tre metodi di cancellazione, sono quasi identici pertanto non li discuterò uno per uno, ma listerò cosa fanno e le eventuali differenze:

  1. Instanziano una Connection, usiamo la clausola using in modo tale che sia rilasciata immediatamente dopo che avremo fatto quel che ci serve.
  2. Creiamo un Command, (in SQLite, visto che abbiamo messo una password al database, prima indichiamo alla Connection la sua password)
  3. Predisponiamo il comando SQL di cancellazione di un singolo record.
  4. Creiamo il Parameter contenente l’ID (se vi chiedete perché ho fatto un array, è semplicemente per abitudine, potete anche semplicemente aggiungerlo con l’Add senza creare l’array).
  5. Apriamo la connessione.
  6. Eseguiamo il Command con ExecuteNonQuery, visto che non ci sono parametri di ritorno nel comando SQL, anche se credo (e dovreste provare a usare un ExecuteScalar per verificarlo) che automaticamente venga restituito il numero dei Record su cui l’operazione è stata eseguita, quindi nel nostro caso 1 se andata a buon fine, 0 se il record non fosse stato nel database.
  7. Chiudiamo la Connection.
  8. Ricarichiamo la tabella.

E con questo la cancellazione è fatta, scommetto che la modifica potreste farla da soli senza alcun aiuto.

La modifica del record

private void UpdateRecord()
{
    if (InputData == null)
    {
        MessageBox.Show("You must select the record to update and change the data in the Input TextBox respecting the semicolon separated fields and their order.", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
        return;
    }
    string[] data = InputData.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
    if (data.Length < 8)
    {
        MessageBox.Show("The ID of the record to update must be the 8th field in the semicolon separated list.");
        return;
    }
    int id = 0;
    int.TryParse(data[7], out id);
    if (id <= 0)
    {
        MessageBox.Show("The record ID must be a number Greater than Zero");
        return;
    }
    DateTime birthDate = new DateTime(1900, 1, 1);
    if (!DateTime.TryParse(data[2], out birthDate))
    {
        MessageBox.Show("BirthDate is not a valid date, no data updated");
        return;
    }
    string firstName = data[0];
    string familyName = data[1];
 
    double salary = 0;
    double.TryParse(data[3], out salary);
    int childrenNumber = 0;
    int.TryParse(data[4], out childrenNumber);
    string sex = data[5] == "M" || data[5] == "F" ? data[5] : "";
    bool married = false;
    bool.TryParse(data[6], out married);
 
    if (UseSqlServer)
    {
        UpdateSqlServer(id, firstName, familyName, birthDate, salary, childrenNumber, sex, married);
    }
    else if (UseSQLite)
    {
        UpdateSqLite(id, firstName, familyName, birthDate, salary, childrenNumber, sex, married);
    }
    else
    {
        UpdateAccess(id, firstName, familyName, birthDate, salary, childrenNumber, sex, married);
    }
}

Partiamo dal metodo di base, cosa facciamo prima di fare una modifica:

  1. Controlliamo che un record sia stato selezionato
  2. Verifichiamo che questo record abbia un ID come ottavo campo e che questo sia un numero intero maggiore di zero.
  3. Effettuiamo il parse di tutti i dati e verifichiamo che la data impostata sia corretta.
  4. In base al database selezionato, richiamiamo il metodo di aggiornamento corretto.
private void UpdateSqlServer(int id, string firstName, string familyName, DateTime birthDate, double salary, int childrenNumber, string sex, bool married)
{
    try
    {
        using (SqlConnection cn = new SqlConnection(CNS_SqlServer))
        {
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = @"
            UPDATE [TbTest]
                        SET [FirstName] = @FirstName
                        ,[FamilyName] = @FamilyName
                        ,[BirthDate] = @BirthDate
                        ,[Salary] = @Salary
                        ,[ChildrenNumber] = @ChildrenNumber
                        ,[Sex] = @Sex
                        ,[Married] = @Married
                WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            SqlParameter[] para = new SqlParameter[] {
                 new SqlParameter( "@ID", id)
                ,new SqlParameter( "@FirstName", firstName)
                ,new SqlParameter( "@FamilyName", familyName)
                ,new SqlParameter( "@BirthDate", birthDate)
                ,new SqlParameter( "@Salary", salary)
                ,new SqlParameter( "@ChildrenNumber", childrenNumber)
                ,new SqlParameter( "@Sex", sex)
                ,new SqlParameter( "@Married", married)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
            //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

private void UpdateAccess(int id, string firstName, string familyName, DateTime birthDate, double salary, int childrenNumber, string sex, bool married)
{
    try
    {
        using (OleDbConnection cn = new OleDbConnection(CNS_Access))
        {
            OleDbCommand cmd = new OleDbCommand();
            cmd.CommandText = @"
            UPDATE [TbTest]
                        SET [FirstName] = @FirstName
                        ,[FamilyName] = @FamilyName
                        ,[BirthDate] = @BirthDate
                        ,[Salary] = @Salary
                        ,[ChildrenNumber] = @ChildrenNumber
                        ,[Sex] = @Sex
                        ,[Married] = @Married
                WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            OleDbParameter[] para = new OleDbParameter[] {
                 new OleDbParameter( "@ID", id)
                ,new OleDbParameter( "@FirstName", firstName)
                ,new OleDbParameter( "@FamilyName", familyName)
                ,new OleDbParameter( "@BirthDate", birthDate)
                ,new OleDbParameter( "@Salary", salary)
                ,new OleDbParameter( "@ChildrenNumber", childrenNumber)
                ,new OleDbParameter( "@Sex", sex)
                ,new OleDbParameter( "@Married", married)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
                    //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}

private void UpdateSqLite(int id, string firstName, string familyName, DateTime birthDate, double salary, int childrenNumber, string sex, bool married)
{
    try
    {
        using (SQLiteConnection cn = new SQLiteConnection(CNS_SqLite))
        {
            cn.SetPassword("TestPwd");
            SQLiteCommand cmd = new SQLiteCommand();
            cmd.CommandText = @"
            UPDATE [TbTest]
                        SET [FirstName] = @FirstName
                        ,[FamilyName] = @FamilyName
                        ,[BirthDate] = @BirthDate
                        ,[Salary] = @Salary
                        ,[ChildrenNumber] = @ChildrenNumber
                        ,[Sex] = @Sex
                        ,[Married] = @Married
                        WHERE ID = @ID"
            ;
            cmd.Connection = cn;
            SQLiteParameter[] para = new SQLiteParameter[] {
                 new SQLiteParameter( "@ID", id)
                ,new SQLiteParameter( "@FirstName", firstName)
                ,new SQLiteParameter( "@FamilyName", familyName)
                ,new SQLiteParameter( "@BirthDate", birthDate)
                ,new SQLiteParameter( "@Salary", salary)
                ,new SQLiteParameter( "@ChildrenNumber", childrenNumber)
                ,new SQLiteParameter( "@Sex", sex)
                ,new SQLiteParameter( "@Married", married)
            };
            cmd.Parameters.Clear();
            cmd.Parameters.AddRange(para);
            cn.Open();
            cmd.ExecuteNonQuery();
            cn.Close();
            //Reload the grid with the updated table from database
            LoadData();
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    }
}    

Anche i tre metodi per la modifica sono identici salvo gli oggetti specifici del provider database, pertanto indicheremo cosa fanno e le eventuali differenze.

  1. Creiamo una Connection sempre usando la clausola using per fare in modo che sia rilasciata immediatamente dopo l’uso.
  2. Creiamo un Command (e in SQLite assegnamo la password di lettura del database)
  3. Assegnamo al Command il nostro comando UPDATE in SQL, che è identico per tutti e tre i database.
  4. Creiamo l’array dei Parameter, l’ordine non ha importanza.
  5. Aggiungiamo i Parameter al Command
  6. Eseguiamo il Command con ExecuteNonQuery, visto che non vi sono dati restituiti, anche in questo caso, vi invito a provare ExecuteScalar per vedere se viene restituito il numero di Record modificati.
  7. Ricarichiamo i dati sulla datagrid.

A questo punto, non ci resta che lanciare l’applicazione e fare un test provando tutte le 4 operazioni di CRUD sulla nostra tabella.

Dopo questa mini serie che spiega i comandi di base per i database, inizierò un progetto che per quanto semplice e fatto per gestire un database con una singola tabella come questo, mostrerà come si deve lavorare in una applicazione che deve funzionare nel mondo reale, dove dobbiamo separare in “strati” quello che compone l’applicazione in modo tale da dover lavorare di meno se per caso una volta finita l’applicazione il cliente arrivasse e dicesse: “No, non voglio usare SQL Server perché non mi piace, voglio usare Oracle, oppure DB2 oppure MySql.” Quanto spiegato sui provider dati in questi quattro articoli dovrebbe aiutarci a comprendere come agire per evitare di dover riscrivere tutta l’applicazione.

Riepilogo

Cosa abbiamo spiegato in questo articolo:

  • L’uso dell’evento SelectedChanged di una DataGrid
  • Come leggere i dati del record selezionato di una DataGrid popolata da una DataTable
  • Come implementare il comando DELETE di un record in una tabella database.
  • Come implementare il comando UPDATE di un record in una tabella database.
  • Come utilizzare i provider di 3 diversi database per eseguire i comandi implementati.

 

Potete scaricare il progetto di esempio dal link qui indicato:

Per qualsiasi domanda, approfondimento, curiosità, o per segnalare un errore usate pure il link alla form di contatto in cima alla pagina.