Press "Enter" to skip to content

Creare un’applicazione WPF per gestire Dati su un database SqlServer – parte 7 La classe DpPubs

In questa settima puntata, iniziamo a costruire la classe che interfaccia la nostra applicazione con il database.

Se avete letto altri articoli, soprattutto la serie dedicata all’applicazione 3 tier, sapete che per una vera applicazione è opportuno separare la User Interface dal Database in modo netto. Ecco perché pure semplificando al massimo la nostra applicazione, tutto ciò che si interfaccia al database lo mettiamo in una classe che fa solo quello come mestiere.

Nel momento in cui creerete una applicazione nel mondo reale, con un database complesso probabilmente vi serviranno più classi come questa, ed imparando a separare gli strati applicativi, sarà anche facile pensare a come suddividere in più progetti una applicazione, per creare una struttura robusta e manutenibile.

Iniziamo dunque a disegnare la nuova classe che chiameremo DpPubs.cs.

Per prima cosa, posizioniamoci sul Nodo del Progetto WpfPubs nel solution explorer e dal menu contestuale con il tasto destro selezioniamo Add -> New Folder per creare una nuova cartella che ospiterà le classi par la gestione del database.

Rinominiamo la cartella New Folder (Nuova catella) in Data.

Ora posizioniamoci sulla nuova cartella e dal menu contestuale con il Tasto Destro, selezioniamo Add -> New Item ed andiamo a creare la nuova classe Data Provider (ovvero fornitore di dati) che chiameremo DpPubs.cs. Data Provider è un nome che mi porto dietro dal 2005, quando ho iniziato a creare le mie prime applicazioni .Net, ha un nome che trovo concettualmente facile da visualizzare, e mi da modo di creare una buona metodologia di lavoro. E’ anche uno dei motivi per cui in determinati ambiti del programma uso un Prefisso (Dp) perché a colpo d’occhio entrando nell’applicazione so cosa sto guardando. Usare le cartelle di progetto (comodissime anche perché creano automaticamente i Namespace) è qualcosa che vi consiglio di utilizzare per aiutare voi stessi nello sviluppo e nella manutenzione dei programmi.

Creiamo quindi la nostra Classe utilizzando il template Class per creare DpPubs.cs

Vediamo quindi cosa metteremo nella nostra classe dopo averla creata.

 

namespace WpfPubs.Data
{
	public class DpPubs
	{
		public DpPubs(string cnString)
		{
			CnString = cnString;
		}
 
		/// <summary>
		/// Stringa di connessione al Database
		/// </summary>
		private string mCnString;
 
		/// <summary>
		/// Stringa di connessione al Database
		/// </summary>
		///<remarks>
		///</remarks>
		public string CnString
		{
			get
			{
				return mCnString;
			}
			private set
			{
				mCnString = value;
			}
		}
	}
}

Le operazioni che ho effettuato nella classe sono le seguenti:

  1. ho inserito il modificatore public per permettere la visibilità della classe agli altri oggetti che sono nel programma.
  2. Ho creato il costruttore della classe, ovvero il metodo public DpPubs, a cui ho aggiunto il parametro stringa che consentirà alle classi che utilizzeranno il data provider di passare la stringa di connessione al database.
  3. ho creato una property, CnString che sarà inizializzata con la stringa di connessione e la fornirà ai metodi che poi implementeremo per interfacciarci al database. Se notate, ho messo il modificatore private al Set della property, questo perché il solo luogo ove voglio sia modificata è nel costruttore. Qualcuno chiederà, perché fare una Property e non semplicemente una variabile privata. E’ vero si può fare anche così, ma a me non dispiace dare modo a chi usa la classe di vedere la stringa di connessione dall’esterno se gli serve.
  4. Nel costruttore, ora che ho la property, vado ad assegnargli il parametro passato con la stringa di connessione al database.   

Adesso che abbiamo definito la struttura della classe, andiamo ad implementare il primo metodo, la select della tabella Authors, che ci permetterà di iniziare a costruire l’interfaccia utente di gestione degli autori.

Ma prima facciamo un ulteriore modifica e impariamo l’uso delle partial class. In realtà è un concetto che conoscete già, infatti le Window e gli User Control WPF sono implicitamente delle partial classes, ovvero delle classi formate da più files (nel nostro programma ad esempio MyWindow.xaml e MyWindow.xaml.cs). Ma il concetto di Partial class è stato introdotto molto tempo fa e permette di creare delle classi corpose e suddividerle in modo arbitrario su più files.

I Data provider sono fra le classi che più si addicono alla suddivisione in più file. Nel nostro caso, DpPubs può essere suddivisa in una porzione comune (quella appena definita) e una ulteriore serie di file che contengono il necessario a gestire una singola tabella del database. In realtà io creo almeno 2 file per ogni tabella, il primo contenente le stringhe SQL per leggere, inserire, modificare, cancellare. Il secondo contenente i metodi che eseguono le chiamate a SQL Server.

Un altro tipo di classe che si presta alla suddivisione in più porzioni è la classe view model delle window molto corpose (ad esempio il view model di una window con molte azioni, che istanzia componenti, lavora con dati ed esegue grandi quantità di codice) questo tipo di classe può essere logicamente suddivisa nelle varie aree di azione della stessa.

Ma torniamo a noi, lasciamo nella classe DpPubs.cs solo la stringa di connessione ed il costruttore per ora e facciamo la seguente modifica:

public partial class DpPubs

Aggiungiamo il modificatore partial alla classe appena creata. Poi usiamo nuovamente il menu contestuale sulla cartella Data e aggiungiamo le due nuove classi parziali.

La nostra prima Partial class sarà quella in cui gestiremo il codice per i metodi che si interfacciano al DB per la tabella Authors. La regola quando si fanno delle partial classes è che ogni partial ha lo stesso nome della classe e poi un suffisso che dice cosa contiene, nel nostro caso:

DpPubs.Authors.cs

Contiene il codice di gestione della tabella Authors.

Ripetiamo l’operazione per la seconda partial class, e generiamo una classe dove metteremo tutte le stringhe SQL per gestire la tabella Authors.

DpPubs.Authors.sql.cs

Potreste obiettare che visto che sono solo 4 stringhe potevo metterle in cima alla classe precedente. Vero però quando inizierete a lavorare sul serio con i database vi renderete conto che spesso mentre lavorate sul codice vi servirà guardare le stringhe SQL (ad esempio scrivendo le insert e le update) e non sempre una split window sullo stesso file è comoda, mentre una classe separata può essere sdoccata da visual studio e per i fortunelli spostata su un secondo monitor (che è una cosa favolosa per chi sviluppa e che io consiglio a tutti)

Uno dei ragazzi che lavorano con me si è comperato un monitor USB che è costato un centinaio di euro e può essere anche portato in giro visto che è sottile, leggerissimo e alimentato dall’USB del notebook.

Pensate che negli anni ’90 quando sviluppavo programmi specifici presso i miei clienti io mi portavo in giro un Monitor CRT da 15 pollici e un Minitower. Sono stata la prima in azienda ad ottenere un PC portatile all’epoca, appena sono usciti sul mercato. [ Scusate l’amarcord…]

Tornando al dunque, ora che abbiamo creato le due classi, la prima cosa che dobbiamo fare è modificarle, perché ci danno un errore dicendo di essere duplicate:

Questo è quello che trovate dentro la classe in entrambi i file:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WpfPubs.Data
{
	class DpPubs
	{
	}
}

Modifichiamo entrambe le classi nel seguente modo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace WpfPubs.Data
{
	public partial class DpPubs
	{
	}
}

public ci serve per rendere la classe visibile al resto dell’applicazione, partial serve a dichiarare che la classe è una porzione della classe DpPubs.

ATTENZIONE!!!
Le classi partial hanno nomi file diversi, ma l’oggetto class è unico quindi il suo nome deve essere sempre lo stesso.

Ora andiamo ad inserire un commento che descrive le 3 porzioni della classe e poi creeremo le chiamate sql facendoci aiutare da Sql Management Studio.

Ricordo chi si sta avvicinando alla programmazione da poco che commentare le classi, i metodi, le property non è una perdita di tempo ma tempo risparmiato nel futuro quando dovrete andare a cercare nel vostro codice qualcosa che dovete modificare.
Le partial class sono un modo per organizzare il codice ai fini della manutenzione più semplice.

Ed ora, andiamo a collegarci a SQL Server tramite SQL Server Management Studio per generare le query per leggere e scrivere nella tabella Authors.

Io non ne ho accennato all’inizio di questa serie, ma ovviamente se volete creare questo programma assieme a me e provarlo sul vostro computer dovete installare SqlServer (potete scaricare la versione SqlExpress With Advanced Services in qualsiasi versione a partire dalla 2012). Anche la 2019 va benissimo oppure, se avete un abbonamento MSDN scaricate la versione Developer che è praticamente una Enterprise, ma può essere installata su un PC, tale versione possiede tutta una serie di strumenti interessanti per chi sviluppa applicazioni e per chi sviluppa database.

Oltre a SQL Server, dovete installare Sql Server Management Studio (SSMS) che trovate qui:

Download Sql Server Management Studio

Questo è il mio SQL Server, nella versione 2012 ma sul mio pc di sviluppo ho installate in parallelo 2012, 2016 e 2019 che possono convivere e funzionare senza problemi sulla stessa macchina. Ovviamente hanno nomi di istanza diversi (es. il mio 2019 si chiama localhost\k19) ma come sviluppatore posso lavorare con le versioni diverse di database dei miei clienti senza gravi problemi.

Se volete qualche suggerimento sull’installazione di SQL Server, questo è un vecchio post per l’installazione di SQL Server 2012 ma le edizioni successive non sono molto diverse.

Installare Sql Server 2012

Ho selezionato la tabella authors sul database Pubs ed ora usando il menu contestuale vediamo come SSMS può aiutarmi a comporre le query per gestirla.

Come vedete, sul menu contestuale della tabella authors c’è un opzione che si chiama Script Table as, questa opzione mi permette di produrre delle bozze delle funzioni principali che si eseguono su una tabella, così da poter preparare quanto ci servirà nel nostro programma.

Scaviamo nel sottomenu e generiamo la query di SELECT della tabella su una nuova finestra del query editor di SSMS.

Ci verrà prodotto il seguente script:

Oltre a togliere il superfluo, ho deciso anche di rinominare i campi originali della tabella con i nomi delle property della mia classe AuthorItem così che sia più semplice la sua gestione, ma non è obbligatorio.

Così come, nei vostri database, non è obbligatorio che tutti i campi siano minuscoli, anzi, i miei database solitamente hanno i campi identici ai nomi delle property delle classi.

Andiamo a porre la nostra query dentro alla classe Data Provider e poi creiamo il metodo per chiamarla:

Prima di fare le modifiche alla classe, ovviamente testiamo la query premendo il pulsante Execute, come vedete la lista appare e le colonne hanno i nomi da noi assegnati.

 

Nella partial class che abbiamo generato per occuparsi delle stringhe SQL, aggiungiamo una Costante privata (la query ci serve solo internamente alla classe) e creiamo una stringa multilinea per ospitarla. Usando il prefisso [@] sulla stringa.

Ora invece ci spostiamo sulla partial class che dedichiamo ai metodi che gestiscono Authors e scriviamo il necessario ad eseguire la query e scriverne il contenuto in una collection di oggetti AuthorItem.

Nell’immagine qui sopra il codice per eseguire la select sulla tabella e restituire i dati come una collection di AuthorItem. 

Per questo primo articolo abbiamo scritto il metodo senza alcun tipo di controllo di errore, pertanto se qualcosa non funziona in questa chiamata, sarà sollevata un eccezione che il codice della User interface dovrà intercettare e gestire al meglio. Ma non appena avremo una UI funzionante ci torneremo sopra e useremo un po’ di codice “serio” per gestire gli errori.

Nel prossimo articolo inizieremo a fare la Window Authors, ma per ora, per testare il nostro metodo select usiamo il pulsante della main window che chiamerà la finestra di gestione autori come test di chiamata a questo metodo, e metteremo il risultato su un file di testo che apriremo per vedere il risultato.

Spostiamoci quindi su MainWindow.xaml.cs e implementiamo il codice per testare la chiamata appena creata sul nostro Data Provider.

Prima operazione, controlliamo ed aggiungiamo i namespaces che contengono le classi che ci serviranno per implementare il test vediamo quali sono (in ordine alfabetico e non di utilizzo).

  • using Microsoft.Win32 = contiene la SaveFileDialog, finestra di sistema di windows per salvare file cercando la cartella e indicandone il nome, la useremo per salvare il contenuto dei dati restituiti in un file di testo per visualizzarli usando il notepad.
  • using System.Diagnostic = contiene la classe helper Process di cui useremo il metodo Process.Start per aprire il file di testo con il programma di default (Notepad di solito).
  • using System.IO = contiene varie classi molto utili per leggere e scrivere file su disco, noi utilizzeremo la classe Helper File per scrivere i dati degli autori sul file di testo.
  • using System.Text = contiene la classe StringBuilder, che è la classe più utile del framework per comporre stringhe e formare dei testi. La useremo per creare una stringa con il contenuto della lista degli autori.
  • using WpfPubs.Data = il namespace dove si trova la nostra classe DpPubs, graziosamente generato da visual studio quando abbiamo generato la classe sulla cartella Data.
  • using WpfPubs.Entities = il namespace dove si trova la classe AuthorItem, anche questo graziosamente generato da Visual Studio quando abbiamo generato la classe sulla cartella Entities.

Ora che abbiamo i namespaces, facendo un controllo ho trovato che manca un pezzettino di codice che ho aggiunto.

Nel metodo che apre la finestra di modifica della stringa di connessione a database, mancava il controllo del valore di ritorno del metodo chiamato e l’aggiornamento della stringa di connessione della MainWindow. Pertanto, ho inserito un controllo del valore  getConfig, che vale True se tutto è ok, o False altrimenti. Se è andato tutto bene, aggiorno la stringa di connessione, se no, do un messaggio di errore.

Vediamo ora il codice per testare la nostra funzione, che abbiamo messo sul click del pulsante Authors.

 

Prima operazione, istanziamo la classe DpPubs e chiamiamo il metodo Authors_Select. E adesso controlliamo cosa è stato restituito.

Prima di tutto verifichiamo se la collezione restituita non è nulla, se fosse nulla si sarebbe verificato un errore di qualche tipo. Se invece non è nulla proseguiamo.

Se invece i dati ci sono, usiamo la classe StringBuilder per creare una lista in formato testo, utilizzando il metodo ToString che avevamo implementato nella classe AuthorItem proprio allo scopo di poter vedere in modo potabile i dati contenuti in una istanza della classe AuthorItem.

Preparata la stringa, utilizziamo la window di sistema SaveFileDialog per chiedere all’utente in quale file vogliamo salvare i dati per visualizzarli. Una volta che l’utente ha selezionato il file, scriviamo il contenuto dello StringBuilder nel file di testo usando la classe Helper File ed il suo metodo File.WriteAllText. Poi usiamo la classe Helper Process per aprire il file utilizzando l’applicazione di default per i file di testo. Tramite il metodo Process.Start(nomedelfile) che fa esattamente quello che fa windows quando fate doppio click su un file sul disco.

Se l’utente chiudesse la finestra senza salvare o premesse Annulla, diamo un messaggio di errore e non facciamo nulla.

Se la chiamata alla funzione di select non ritorna alcun dato (ad esempio per un errore) diamo un messaggio per informare l’utente e verificare.

In realtà, siccome sono cattiva, quando faremo partire l’applicazione e premeremo il tasto Authors ecco cosa succede:

Ecco il messaggio restituito:

System.IndexOutOfRangeException: AuthorItem\r\n in System.Data.ProviderBase.FieldNameLookup.GetOrdinal(String fieldName)\r\n in System.Data.SqlClient.SqlDataReader.GetOrdinal(String name)\r\n in System.Data.SqlClient.SqlDataReader.get_Item(String name)\r\n in WpfPubs.Data.DpPubs.Authors_Select() in D:\\Dotnetwork\\Dev2020\\WpfPubs\\WpfPubs\\Data\\DpPubs.Authors.cs:riga 35\r\n in WpfPubs.Windows.MainWindow.Authors_Click(Object sender, RoutedEventArgs e) in D:\\Dotnetwork\\Dev2020\\WpfPubs\\WpfPubs\\Windows\\MainWindow.xaml.cs:riga 100

Cosa possiamo dedurre:

Si è verificato un errore nel file DpPubs.Authors.cs a riga 35 perché il programma ha cercato di accedere al campo AuthorItem all’interno della riga corrente del SqlDataReader e tale campo non esiste. Pertanto Authors_Click da errore e termina.

Che Errore ho inserito?

Ecco l’errore, ho scritto AuthorItem nel nome del campo invece che AuthorID, pertanto correggo la riga nel file DpPubs.Authors.cs con la seguente:

item.AuthorID = (string)reader["AuthorID"];

A questo punto, rilanciando la funzione di test otteniamo questo:

L’esecuzione del metodo di test apre la finestra di salvataggio file per chiederci dove salvare i dati letti. La finestra si è aperta sulla cartella documenti pertanto generiamo una sottocartella per i test della nostra applicazione.

Il codice salva i dati sul file Authors.txt e poi lo apre con il programma associato all’estensione .txt, nel mio caso Notepad++ ma normalmente sarà notepad.exe.

Ci fermiamo qui per ora, nel prossimo articolo iniziamo a creare la finestra per gestire gli Authors.