In questo nuovo articolo, per quanto distante dal precedente procediamo con le funzionalità dell’applicazione, sviluppando le funzionalità di Creazione di un nuovo Autore, Salvataggio delle modifiche effettuate ad un Autore esistente sulla datagrid, e la cancellazione di un autore con il controllo che permetta di farlo solo quando non avesse già pubblicato un libro.
La prima cosa che dobbiamo fare è creare all’interno della classe DpAuthors i metodi che mancano, ovvero il metodo Insert, il metodo Update ed infine il metodo Delete per realizzare il quale andremo a creare anche il metodo HasBooks per verificare che l’autore possa essere cancellato perché solo se un autore non ha alcun libro è possibile cancellarlo senza provocare un errore sulla tabella che collega i libri agli autori in una relazione N a N.
public DataResult Authors_Insert(AuthorItem author) { DataResult ret = new DataResult(); try { using (SqlConnection cn = new SqlConnection(CnString)) { cn.Open(); SqlTransaction tn = cn.BeginTransaction(); SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.Transaction = tn; cmd.CommandText = SQL_Authors_Insert; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@au_id", author.AuthorID); cmd.Parameters.AddWithValue("@au_lname", author.AuthorLastName); cmd.Parameters.AddWithValue("@au_fname", author.AuthorFirstName); cmd.Parameters.AddWithValue("@phone", author.Phone); cmd.Parameters.AddWithValue("@address", author.Address); cmd.Parameters.AddWithValue("@city", author.City); cmd.Parameters.AddWithValue("@state", author.State); cmd.Parameters.AddWithValue("@zip", author.Zip); cmd.Parameters.AddWithValue("@contract", author.Contract); try { cmd.ExecuteNonQuery(); ret.Ok = true; tn.Commit(); } catch (Exception iex) { ret.ErrorMessage = iex.ToString(); tn.Rollback(); } } } catch (Exception ex) { ret.ErrorMessage = ex.ToString(); } return ret; }
private const string SQL_Authors_Insert = @" INSERT INTO [authors] ([au_id] ,[au_lname] ,[au_fname] ,[phone] ,[address] ,[city] ,[state] ,[zip] ,[contract]) VALUES (@au_id ,@au_lname ,@au_fname ,@phone ,@address ,@city ,@state ,@zip ,@contract) ";
Qui sopra abbiamo il codice per l’inserimento di un nuovo autore, vediamo quindi una lista di cose da tener presente quando si implementa un metodo che modifica dati sul database:
- Utilizzare la clausola using quando creiamo la SqlConnection, una connessione a database è un oggetto che consuma risorse finite, un RDBMS ha una limitata quantità di connessioni a cui può rispondere contemporaneamente, pertanto è indispensabile che tali oggetti esistano per il tempo più breve possibile. Usare la clausola using di C# fa in modo che la classe in essa istanziata sia rilasciata non appena lo statement using è chiuso, non solo, se la connessione è aperta come ho lasciato io nel codice, essa viene automaticamente chiusa. Un’altra cosa da ricordarsi quando si usano risorse che possono essere “aperte” e “chiuse” è tenerle aperte per il tempo più breve.
- Dopo avere aperto la Connessione, ho aperto una Transazione sulla connessione stessa. L’uso di una transazione (Transaction) sul database ci permette di evitare che in caso di errori il database divenga inconsistente. Non è il caso della nostra singola tabella, ma è importante quando scriviamo metodi che modificano più record su più tabelle, per evitare che un errore su una tabella secondaria, verificatosi dopo l’inserimento di un record non crei un insieme di record incoerenti (inconsistenti) perché manca una porzione dei dati. La Transazione, permette di effettuare un Rollback (Ritornare indietro) allo stato del database che precede l’apertura del database, così da evitare le inconsistenze. per prassi io inserisco sempre la transazione sulle modifiche, e a volte anche sulle selezioni, in modo che se necessario posso estrarre la porzione di codice dentro alla using per usarla sia in modo singolo che in una transazione più ampia.
- A questo punto, assegno al CommandText del SqlCommand, l’oggetto preposto all’esecuzione degli statement SQL, la stringa che ho creato e che contiene lo script SQL per inserire un record.
- Infine, genero i parametri necessari assegnandovi i valori trovati sul record che è stato passato dal ViewModel al Data provider per effettuare la modifica.
- Preparato tutto, utilizzo uno statement try – catch per eseguire il command ed inserire il record nella tabella. In questo modo, nel caso si verifichi un errore, eseguo il Rollback della transazione mentre se tutto va bene, eseguo il Commit, che conferma la modifica dei dati.
- Il metodo eseguito, restituisce un oggetto di tipo DataResult, è una classe che ho generato e inserito nella cartella Entities, che contiene tre property,
- OK un booleano ove mettere true se tutto è andato bene
- ErrorMessage, una stringa dove inserire l’errore se si verifica.
- HasError, una property booleana “derivata” che è true se c’è l’errore e rende semplice controllarlo.
E con questo, il metodo di inserimento degli autori è completo.
Passiamo ora al metodo di Aggiornamento, l’Update, dove permetteremo agli utenti di modificare tutto fatto salvo l’ID dell’autore, che ovviamente non deve mai essere modificata.
public DataResult Authors_Update(AuthorItem author) { DataResult ret = new DataResult(); try { using (SqlConnection cn = new SqlConnection(CnString)) { cn.Open(); SqlTransaction tn = cn.BeginTransaction(); SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.Transaction = tn; cmd.CommandText = SQL_Authors_Update; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@au_lname", author.AuthorLastName); cmd.Parameters.AddWithValue("@au_fname", author.AuthorFirstName); cmd.Parameters.AddWithValue("@phone", author.Phone); cmd.Parameters.AddWithValue("@address", author.Address); cmd.Parameters.AddWithValue("@city", author.City); cmd.Parameters.AddWithValue("@state", author.State); cmd.Parameters.AddWithValue("@zip", author.Zip); cmd.Parameters.AddWithValue("@contract", author.Contract); cmd.Parameters.AddWithValue("@au_id", author.AuthorID); try { cmd.ExecuteNonQuery(); ret.Ok = true; tn.Commit(); } catch (Exception iex) { ret.ErrorMessage = iex.ToString(); tn.Rollback(); } } } catch (Exception ex) { ret.ErrorMessage = ex.ToString(); } return ret; }
private const string SQL_Authors_Update = @" UPDATE [authors] SET [au_lname] = @au_lname ,[au_fname] = @au_fname ,[phone] = @phone ,[address] = @address ,[city] = @city ,[state] = @state ,[zip] = @zip ,[contract] = @contract WHERE [au_id] = @au_id ";
Anche il metodo di Aggiornamento è molto simile a quello di inserimento, e come potete vedere utilizziamo il valore dell’ID dell’autore come filtro per modificare solo il singolo record. Fate molta attenzione quando generate i metodi di Update e Delete, a inserire un filtro per evitare spiacevoli effetti.
Essendo praticamente identico all’inserimento, l’aggiornamento non ha bisogno di ulteriori commenti.
public DataResult Authors_HasBooks(SqlConnection cn, SqlTransaction tn, string authorID) { DataResult ret = new DataResult(); try { SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.Transaction = tn; cmd.CommandText = SQL_Authors_CountBooks; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@au_id", authorID); try { object obj = cmd.ExecuteScalar(); if (obj != null) { int num = (int)obj; if (num > 0) { ret.Ok = true; } } } catch (Exception iex) { ret.ErrorMessage = iex.ToString(); } } catch (Exception ex) { ret.ErrorMessage = ex.ToString(); } return ret; }
private const string SQL_Authors_CountBooks = @" SELECT count([title_id]) as NrBooks FROM [titleauthor] WHERE [au_id] = @au_id ";
Prima di creare la funzione di cancellazione, generiamo una funzione che ci permette di stabilire se l’autore ha già prodotto un libro, in modo da non cercare neppure di cancellare l’autore se ha dei libri onde evitare di creare un errore dovuto alla foreign key sul collegamento fra libri e autori.
Questa funzione, riceve come parametri sia la Connection che la Transaction da parte del metodo chiamante perché è collegato al metodo di cancellazione, anche se in una funzione di Select come quella chiamata da questo metodo non è necessaria la Transaction, essendo solo una lettura, il fatto che venga chiamata da una funzione di modifica dove useremo una transazione implica che anche questa chiamata si troverà dentro alla transazione. In questo metodo, passiamo il parametro dell’ID autore e contiamo se vi sono righe nella tabella “titleauthor” che collega i libri agli autori (un libro uno o più autori), con l’ID dell’autore che vogliamo cancellare.
public DataResult Authors_Delete(string authorID) { DataResult ret = new DataResult(); try { using (SqlConnection cn = new SqlConnection(CnString)) { cn.Open(); SqlTransaction tn = cn.BeginTransaction(); SqlCommand cmd = new SqlCommand(); cmd.Connection = cn; cmd.Transaction = tn; DataResult hasBooks = Authors_HasBooks(cn, tn, authorID); if (hasBooks.HasError) { tn.Rollback(); ret.ErrorMessage = hasBooks.ErrorMessage; return ret; } if (hasBooks.Ok) { tn.Rollback(); ret.ErrorMessage = string.Format("L'autore {0} ha almeno un libro e non può essere cancellato.", authorID); return ret; } cmd.CommandText = SQL_Authors_Delete; cmd.Parameters.Clear(); cmd.Parameters.AddWithValue("@au_id", authorID); try { cmd.ExecuteNonQuery(); ret.Ok = true; tn.Commit(); } catch (Exception iex) { ret.ErrorMessage = iex.ToString(); tn.Rollback(); } } } catch (Exception ex) { ret.ErrorMessage = ex.ToString(); } return ret; }
private const string SQL_Authors_Delete = @" DELETE FROM [authors] WHERE [au_id] = @au_id ";
Infine, la cancellazione, prima di cancellare verifichiamo se l’autore ha almeno un libro collegato, se non è così, procediamo alla cancellazione, altrimenti indichiamo un errore ed effettuiamo il Rollback, che viene effettuato anche nel caso si verifichi un errore.
Se invece l’autore non ha libri e non ci sono errori, procediamo alla cancellazione, passando il parametro con l’ID dell’autore al metodo di cancellazione.
Con questo abbiamo terminato di creare i metodi di modifica del Database, ora dobbiamo creare le chiamate a questi metodi dalla User Interface.