Oppure

Loading

Usare queries per manipolare il database è un mezzo molto efficacie, anche se il processo per creare una query sottoforma di stringa può risultare alquanto macchinoso in alcuni casi. A questo proposito, vorrei invitarvi a leggere le prime lezioni del tutorial che ho scritto riguardo a LINQ, il linguaggio di querying integrato disponibile dalla versione 2008 del linguaggio (framework v3.5).
In questo capitlo, invece, inizieremo a passare dalle relazioni, ossia dalle tabelle del database nel loro ambiente, agli oggetti, trasponendo, quindi, tutte le operazioni a costrutti che già conosciamo. Possiamo rappresentare un database e le sue tabelle in due modi:

  • Mediante l'approccio standard, con le classi DataSet e DataTable, che rappresentano esattamente il database, con tutte le sue proprietà e caratteristiche. Queste classi astraggono tutta la struttura relazione e la trasportano nel linguaggio ad oggetti;
  • Mediante l'approccio LINQ, con normali classi scritte dal programmatore, artificalmente collegate tramite attributi e metadati, alle relazioni presenti nel database;

Vedremo ora solo il primo approccio, poiché il secondo è trattato già nel tutorial menzionato prima.

DataSet

La classe DataSet ha lo scopo di rappresentare un database. Mediante un apposito oggetto, detto Adapter (che analizzeremo in seguito), è possibile travasare tutti i dati del database in un oggetto DataSet e quindi operare su questo senza bisogno di query. Una volta terminate le elaborazioni, si esegue il processo inverso aggiornando il database con le nuove modifiche apportate al DataSet. Questo è il principio di base con cui si affronta il passaggio dalle relazioni agli oggetti.
Questa classe espone una gran quantità di membri, tra cui menzioniamo i più importanti:

  • AcceptChanges() : conferma tutte le modifiche apportate al DataSet; questo metodo deve essere richiamato obbligatoriamente prima di iniziare la procedura di aggiornamento del database a cui è collegato;
  • CaseSensitive : indica se la comparazione di campi di tipo string avviene in modalità case-sensitive;
  • Clear() : elimina tutti i dati presenti nel DataSet;
  • Clone() : esegue una clonazione deep dell'oggetto DataSet e restituisce la nuova istanza;
  • DataSetName : nome del DataSet;
  • GetChanges() : restituisce una copia del DataSet in cui sono presenti tutti i dati modificati (come se si fosse richiamato AcceptChanges());
  • GetXml() : restituisce una stringa contenente la rappresentazione xml di tutti i dati presenti nel DataSet;
  • HasChanges : determina se il DataSet sia stato modificato dall'ultimo salvataggio o caricamento;
  • IsInitialized : indica se è inizializzato;
  • Merge(D As DataSet) : unisce D al DataSet corrente. Le tabelle e i dati di D vengono aggiunti all'oggetto corrente;
  • RejectChanges() : annulla tutte le modifiche apportate dall'ultimo salvataggio o caricamento;
  • ReadXml(R) : legge un file XML mediante l'oggetto R (di tipo XmlReader) e trasferisce i dati ivi contenuti nel DataSet;
  • Reset() : annulla ogni modifica apportata e riporta il DataSet allo stato iniziale (ossia come era dopo il caricamento);
  • Tables : restituisce una collezione di oggetti DataTable, che rappresentano le tabelle presenti nel DataSet (e quindi nel database);

 

DataTable

Se un DataSet rappresenta un database, allora un oggetto di tipo DataTable rappresenta un singola tabella, composta di righe e colonne. Nessun nuovo concetto da introdurre, quindi: si tratta solo di una classe che rappresenta ciò che abbiano visto fin ora per una relazione. I suoi membri sono pressoché simili a quelli di DataSet, con l'aggiunta delle proprietà Columns, Rows, PrimaryKey e del metodo AddRow (ho menzionato solo quelli di uso più frequente).

Caricamento e binding

Ora possiamo caricare i dati in un DataSet ed eseguire un binding verso un controllo. Abbiamo già il concetto di binding nei capitoli sulla reflection, in riferimento alla possibilità di legare un identificatore a un valore. In questo caso, pur essendo fondamentalmente la stessa cosa, il concetto è leggermente differente. Noi vogliamo legare un certo insieme di valori ad un controllo, in modo che esso li visualizzi senza dover scrivere un codice particolare per inserirli. Il controllo che, per eccellenza, rende graficamente nel modo migliore le tabelle è DataGridView. Assumendo, quindi, di avere nel form solo un controllo DataGridView1, possiamo scrivere questo codice:

Imports MySql.Data.MySqlClient
Class Form1
    Private Data As New DataSet

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")
        Dim Adapter As New MySqlDataAdapter

        Conn.Open()

        Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Customers;", Conn)
        Adapter.Fill(Data)

        Conn.Clone()

        DataGridView1.DataSource = Data.Tables(0)
    End Sub
    
End Class

DataGridView1 verrà riempito con tutti i dati presenti nella prima (e unica) tabella del dataset.

DataSet tipizzati

Nel prossimo esempio vedremo di accennare alla costruzione di una semplice applicazione per gestire brani musicali con un database, ed eventualmente introdurrò un po' di codice per la riproduzione audio. In questo esempio, però, non useremo un generico database, ma uno specifico database di cui conosciamo le proprietà e della cui esistenza siamo certi. Quindi faremo a meno di usare un generico DataSet, ma ne creeremo uno specifico per quel database, che sia più semplice da manipolare. Useremo, quindi, un DataSet tipizzato. Non dovremo scrivere alcuna riga di codice per far questo, poiché basterà "disegnare" la struttura del database con uno specifico strumento che il nostro IDE mette a disposizioni, e che si chiama Table Designer. Mediante quest'ultimo, possiamo delinare lo schema di un database e delle sue tabelle, e l'ambiente di sviluppo provvederà a scrivere un codice adeguato per creare un dataset tipizzato specifico per quel database. A livello pratico, un dataset tipizzato non è altro che una classe derivata da DataSet che definisce alcune proprietà e metodi atti a semplificare la scrittura di codice. Ad esempio, invece di referenziare la prima cella della prima riga di una tabella, ottenerne il valore e convertirlo in intero, la versione tipizzata del dataset espone direttamente una proprietà ID (ad esempio) che fa tutto questo. C'è solo un difetto nel codice autogenerato, ma lo illustrerò in seguito.
Prima di iniziare, bisogna creare effettivamente le tabelle che useremo nel database AppData. Per questo programma, ho ideato tre tabelle: authors, songs e albums, costruite come segue:

CREATE TABLE `albums` (                  
  `ID` int(11) NOT NULL AUTO_INCREMENT,  
  `Name` char(255) NOT NULL,             
  `Year` int(11) DEFAULT NULL,           
  `Description` text,                    
  `Image` text,                          
  PRIMARY KEY (`ID`)                     
);

CREATE TABLE `authors` (                 
  `ID` int(11) NOT NULL AUTO_INCREMENT,  
  `Name` char(255) NOT NULL,             
  `Nickname` char(255) DEFAULT NULL,     
  `Description` text,                    
  `Image` text,                          
  PRIMARY KEY (`ID`)                     
);

CREATE TABLE `songs` (                   
  `ID` int(11) NOT NULL AUTO_INCREMENT,  
  `Path` char(255) NOT NULL,             
  `Title` char(255) DEFAULT NULL,        
  `Author` int(11) DEFAULT NULL,         
  `Album` int(11) DEFAULT NULL,          
  PRIMARY KEY (`ID`)                     
);

[Gli accenti tonici sono stati aggiunti da SQLyog, e sono obbligatori solo se il nome della colonna o della tabella contiene degli spazi.]
Prima di procedere, potrebbe essere utile mostrare la toolbar di gestione delle basi di dati: per far questo, cliccate con il pulsante destro su uno spazio vuoto della toolbar e spuntate Data Design per far apparire le relative icone:

DataDesign.jpg
Per aggiungere un nuovo dataset tipizzato, invece, cliccate sempre col destro sul nome del progetto nel solution explorer, scegliete Add Item e quindi DataSet. Dovrebbe apparirvi un nuovo spazio vuoto simile a questo:

NewDataSet.jpg
Spostando il mouse sulla toolbox a fianco, potrete notare che è possibile aggiungere tabelle, relazioni, queries e alcune altre cose. Dato che dobbiamo ricreare la stessa struttura del database AppData, è necessario creare tre tabelle: Albums, Authors e Songs, ciascuna con le stesse colonne di quelle sopra menzionate. Trascinate un componente DataTable all'interno della schermata e rinominatelo in Songs, quindi fate click col destro sull'header della tabella e scegliete Add > Column:

DataTableAddColumn.jpg
Aggiungete quindi tante colonne quante sono quelle del codice SQL. Ora selezionate la prima colonna (ID) e portate in primo piano la finestra della proprietà (la stessa usata per i controlli). Essa visualizzerà alcune informazioni sulla colonna ID. Per rispettare il vincolo con il database reale, essa deve essere dichiarata di tipo intero, deve supportare l'autoincremento e non può essere NULL:

DataColumnProperties.jpg
Ora fate lo stesso con tutte le altre colonne, tenendo conto che char(255) e text sono entrambi contenibili dallo stesso tipo (String). Prima di passare alla compilazione delle altre tabelle, ricordatevi di impostare ID come chiave primaria: cliccate ancora sull'header della tabella, scegliendo Add > Key:

PrimaryKey.jpg

Bene. Come avrete sicuramente notate, i campi Author e Album di Songs non sono stringhe, bensì interi. Infatti, come abbiamo visto qualche capitolo fa, è possibile collegare logicamente due tabelle tramite una relazione (uno-a-uno, uno-a-molti o molti-a-molti). In questo caso, vogliamo collegare al campo Author di una canzone, la tupla che rappresenta quell'autore nella tabella Authors. Questa è una relazione uno-a-molti (in questo programma semplificato, assumiamo che tutti coloro che hanno partecipato alla realizzazione siano considerati "autori", senza le varie distinzioni tra autore dei testi, artist, compositori eccetera...). Mediante l'editor integrato nell'ambiente di sviluppo possiamo anche aggiungere questa realzione (che andrà a popolare la proprietà Relations del DataSet). Basta aggiungere un oggetto Relation e compilare i campi relativi:

DataRelation.jpg
Una volta completati tutti i passaggi, è possibile iniziare a scrivere qualche riga di codice (non dimenticatevi di riempire il database con qualche tupla di esempio prima di iniziare il debug).

Musica, maestro!

Ecco l'interfaccia del programma:

MusicDatabase.jpg
Oltre ai controlli che si vedono nell'immagine, ho aggiunto anche il dataset tipizzato creato prima dall'editor, AppDataSet. Dato che nella listbox sulla sinistra visualizzeremo i titoli delle canzoni, possiamo eseguire un binging di tali dati sulla listbox. Dopo averla selezionata, andate nella proprietà DataSource e scegliete la tabella Songs:

DataTableBinding.jpg
Dopodiché, impostate il campo DisplayMember su Title e ValueMember su ID: come avevo spiegato nel capitolo sulla listbox, queste proprietà ci permettono di modificare cosa viene visualizzato coerentemente con gli oggetti immagazzinati nella lista. Se avete fatto tutto questo, l'IDE creerà automaticamente un nuovo oggetto di tipo BindingSource (il SongsBindingSource dell'immagine precedente). Esso ha il compito di mediare tra la sorgente dati e l'interfaccia utente, e ci sarà utile in seguito per la ricerca.
Ecco il codice:

Public Class Form1

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim Conn As New MySqlConnection("Server=localhost; Database=appdata; Uid=root; Pwd=root;")
        Dim Adapter As New MySqlDataAdapter

        Conn.Open()

        'Carica le tabelle nel dataset. Le tabelle sono ora
        'accessibili mediante omonime proprietà dal
        'dataset tipizzato
        
        Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Songs;", Conn)
        Adapter.Fill(AppDataSet.Songs)

        Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Authors;", Conn)
        Adapter.Fill(AppDataSet.Authors)

        Adapter.SelectCommand = New MySqlCommand("SELECT * FROM Albums;", Conn)
        Adapter.Fill(AppDataSet.Albums)

        Conn.Clone()
    End Sub

    Private Sub lstSongs_SelectedIndexChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles lstSongs.SelectedIndexChanged
        If lstSongs.SelectedIndex < 0 Then
            Exit Sub
        End If

        'Trova la riga con ID specificato. Il tipo SongsRow è stato
        'definito dall'IDE durante la creazione del dataset tipizzato.
        Dim S As AppDataSet.SongsRow = AppDataSet.Songs.FindByID(lstSongs.SelectedValue)

        lblName.Text = S.Title

        tabAuthor.Tag = Nothing
        'Anche il metodo IsAuthorNull è stato definito dall'IDE.
        'In generale, per ogni proprietà per cui non è stata
        'specificata la clausola NOT NULL, l'IDE crea un metodo per 
        'verificare se quel dato attributo contiene un valore
        'nullo. Equivale a chiamare S.IsNull(3).
        If Not S.IsAuthorNull() Then
            'Ottiene un array che contiene tutte le righe della
            'tabella Authors che soddisfano la relazione definita
            'tra Songs e Authors. Dato che la chiave esterna della
            'tabella figlio era un ID, la realzione, pur essendo
            'teoricamente uno-a-molti, è in realtà
            'uno-a-uno. Perciò, se questo array ha almeno
            'un elemento, ne avrà solo uno.
            Dim Authors() As AppDataSet.AuthorsRow = S.GetAuthorsRows()
            'Come IsNull, questo metodo equivale a chiamare
            'S.GetChildRows("Songs_Authors")
            
            If Authors.Length > 0 Then
                Dim Author As AppDataSet.AuthorsRow = Authors(0)

                lblAuthorName.Text = Author.Name
                If Not Author.IsNicknameNull() Then
                    lblAuthorName.Text &= vbCrLf & Chr(34) & Author.Nickname & Chr(34)
                End If
                If Not Author.IsImageNull() Then
                    imgAuthor.Image = Image.FromFile(Author.Image)
                Else
                    imgAuthor.Image = Nothing
                End If

                If Not Author.IsDescriptionNull() Then
                    txtAuthorDescription.Text = Author.Description
                Else
                    txtAuthorDescription.Text = ""
                End If
                tabAuthor.Tag = Author.ID
            End If
        End If

        tabAlbum.Tag = Nothing
        If Not S.IsAlbumNull() Then
            Dim Albums() As AppDataSet.AlbumsRow = S.GetAlbumsRows()

            If Albums.Length > 0 Then
                Dim Album As AppDataSet.AlbumsRow = Albums(0)

                lblAlbumName.Text = Album.Name
                If Not Album.IsYearNull() Then
                    lblAlbumYear.Text = Album.Year
                Else
                    lblAlbumYear.Text = ""
                End If
                If Not Album.IsImageNull() Then
                    imgAlbum.Image = Image.FromFile(Album.Image)
                Else
                    imgAlbum.Image = Nothing
                End If
                If Not Album.IsDescriptionNull() Then
                    txtAlbumDescription.Text = Album.Description
                Else
                    txtAlbumDescription.Text = ""
                End If
                tabAlbum.Tag = Album.ID
            End If
        End If
    End Sub

    Private Sub btnSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSearch.Click
        If Not String.IsNullOrEmpty(txtSearch.Text) Then
            'La proprietà Filter di un binding source è come
            'una condizione SQL. In questo caso cerchiamo tutte le 
            'canzoni il cui titolo contenga la stringa specificata.
            SongsBindingSource.Filter = String.Format("title like '%{0}%'", txtSearch.Text)
        Else
            SongsBindingSource.Filter = ""
        End If
    End Sub

End Class


MusicDatabase2.jpg

A cura di: Il Totem