Press "Enter" to skip to content

3 – Lavorare con i dati – ADO.Net i fondamentali: INSERT

Nel precedente articolo dedicato ai database, abbiamo imparato come effettuare una query per leggere dati da 3 diversi database relazionali, Access, SQLite e SQL Server. In questo articolo, modificheremo la User Interface per renderla un po’ meno grezza e per aggiungere un controllo che ci permetta di dare all’applicazione un dato da inserire in uno dei database.

La nuova interfaccia utente sarà la seguente:

03_adobasic_mainwindow_01[4]

Abbiamo fatto le seguenti modifiche:

  1. Sostituito la semplice TextBox del risultato con una DataGrid, in grado di visualizzare la nostra tabella.
  2. Abbiamo aggiunto una TextBox che ci permette un input dei dati (per quanto ancora grezzo) permettendoci di aggiungere dati alla tabella.
  3. Abbiamo introdotto 3 radiobutton con cui selezionare il database su cui eseguire l’azione.
  4. Abbiamo trasformato i Button in modo che vi sia un solo Load button, che usa il radiobutton per sapere quale metodo chiamare
  5. Abbiamo aggiunto un Button per aggiungere il record, anche in questo caso baserà la scelta del database sull’opzione indicata dai radio button.

Per non complicarci la vita con un interfaccia con molte TextBox, visto che non è lo scopo dell’articolo, ho deciso che i dati si digiteranno in una singola textbox inserendoli nell’ordine delle colonne con i valori separati da punti e virgola.

Non dovrà essere indicato un ID in quanto tutti e tre i database usano un contatore automatico per aggiornare il valore dell’ID.

Le Modifiche a MainWindow.xaml

<Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

La prima modifica alla grid principale della finestra è stata effettuata per aggiungere due righe, una ospiterà la TextBox per inserire i dati, l’altra ospiterà uno StackPanel che conterrà i 3 RadioButton per la selezione del Database.

<DataGrid ItemsSource="{Binding Data, Mode=OneWay }">
    <DataGrid.Columns>
        <DataGridTextColumn Header="ID" Binding="{Binding ID}" />
        <DataGridTextColumn Header="Name" Binding="{Binding FirstName}" />
        <DataGridTextColumn Header="Family Name" Binding="{Binding FamilyName}" />
        <DataGridTextColumn Header="BirthDate" Binding="{Binding BirthDate}" />
        <DataGridTextColumn Header="Salary" Binding="{Binding Salary}" />
        <DataGridTextColumn Header="Children" Binding="{Binding ChildrenNumber}" />
        <DataGridTextColumn Header="Sex" Binding="{Binding Sex}" />
        <DataGridCheckBoxColumn Header="Married" Binding="{Binding Married}" />
    </DataGrid.Columns>
</DataGrid>

Al posto della precedente TextBox, a Riga 0 inseriamo una DataGrid, lo faccio per mostrarvi come una DataTable, pur essendo una classe molto anziana, visto che era presente anche nel Framework 1.0, può essere usata come datasource, o meglio in questo caso ItemsSource per una DataGrid WPF.

Abbiamo creato un Binding, sulla property Data che fornisce alla DataGrid la sua sorgente dati.
Abbiamo poi definito come sono fatti i campi della DataTable Definendo una serie di DataGridxxxxColumn, utilizzando due delle possibili varietà fornite, la Text Column per tutti i valori auto formattati, la CheckBox Column per il campo boolean Married.

Come vedete, in ogni colonna, abbiamo indicato l’intestazione Header e il Binding, ovvero la colonna collegata, in questo caso ci è bastato indicare il nome della colonna, il controllo Datagrid poi si arrangia.

<Grid 
     Grid.Row="1">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="Auto"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <TextBlock 
         Grid.Column="0"
         Margin="4,2,4,2"
         HorizontalAlignment="Right"
         VerticalAlignment="Center"
         Text="Input data"/>
    <TextBox
         Grid.Column="1"
         HorizontalAlignment="Stretch"
         VerticalAlignment="Center"
         HorizontalContentAlignment="Left"
         VerticalScrollBarVisibility="Auto"
         AcceptsReturn="True"
         AcceptsTab="True"
         TextWrapping="Wrap"
         MinHeight="48"
         Text="{Binding InputData, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
         ToolTip="Set the data for the record to add separating fields with a semicolon without indicating the ID that will be set automatically."
         />
</Grid>

La Grid annidata che contiene la TextBox per l’input dei dati, abbiamo inserito una descrizione, usando una TextBlock, e la TextBox, collegata in Binding ad una variabile che abbiamo chiamato InputData, una property di tipo stringa che definiremo poi nel codice.

Il Tooltip da all’utente un idea di come inserire i dati nella casella di testo.

<StackPanel
    Grid.Row="2"
    Orientation="Horizontal"
    VerticalAlignment="Center"
    Margin="4">
    <RadioButton 
        Margin="4,2,4,2"
        VerticalContentAlignment="Center"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        IsChecked="{Binding UseAccess, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBlock 
            Margin="4,2,4,2"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Text="Access"/>
    </RadioButton>
    <RadioButton 
        Margin="4,2,4,2"
        VerticalContentAlignment="Center"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        IsChecked="{Binding UseSqlServer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBlock 
            Margin="4,2,4,2"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Text="Sql Server"/>
            </RadioButton>
    <RadioButton 
        Margin="4,2,4,2"
        VerticalContentAlignment="Center"
        HorizontalAlignment="Left"
        VerticalAlignment="Center"
        IsChecked="{Binding UseSQLite, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
        <TextBlock 
            Margin="4,2,4,2"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Text="SQLite"/>
    </RadioButton>
</StackPanel>

I 3 RadioButton sono inseriti nella riga successiva, dentro ad uno StackPanel, questo fa in modo che automaticamente, WPF renda i 3 RadioButton mutualmente esclusivi, quindi settando uno dei tre a True, gli altri si resettano.

<StackPanel
    Grid.Row="3"
    Orientation="Horizontal"
    FlowDirection="RightToLeft">
    <Button
        Margin="4,2,4,2"
        Padding="10,4,10,4"
        Click="LoadData_Click"
        ToolTip="Loads the data from the selected database">
        <TextBlock 
            Margin="4,2,4,2"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Text="Load Data"/>
    </Button>
    <Button
        Margin="4,2,4,2"
        Padding="10,4,10,4"
        Click="Add_Click"
        ToolTip="Adds a record reading the input text data to the currently selected database">
        <TextBlock 
            Margin="4,2,4,2"
            HorizontalAlignment="Left"
            VerticalAlignment="Center"
            Text="Add Record"/>
    </Button>
</StackPanel>

Nello StackPanel ove precedentemente erano i 3 Button che caricavano i dati dai 3 diversi database, ora abbiamo 2 soli Button, uno per caricare i dati, l’altro per aggiungere i record al database selezionato tramite i radiobutton.

Le Modifiche a MainWindow.xaml.cs

Prima di tutto abbiamo eliminato la Property ResulText con i suoi 2 accessori, la variabile a livello di classe e il nome del campo.

Abbiamo Aggiunto quattro property:

public string InputData
{
	get
	{
		return mInputData;
	}
	set
	{
		mInputData = value;
		OnPropertyChanged(FLD_InputData);
	}
}

La property string collegata alla TextBox di input dati.

public bool UseAccess
{
	get
	{
		return mUseAccess;
	}
	set
	{
		mUseAccess = value;
		OnPropertyChanged(FLD_UseAccess);
	}
}

La property bool per indicare se utilizzare il database Access.

public bool UseSQLite
{
	get
	{
		return mUseSQLite;
	}
	set
	{
		mUseSQLite = value;
		OnPropertyChanged(FLD_UseSQLite);
	}
}

La property bool per indicare se utilizzare il database SQLite.

public bool UseSqlServer
{
	get
	{
		return mUseSqlServer;
	}
	set
	{
		mUseSqlServer = value;
		OnPropertyChanged(FLD_UseSqlServer);
	}
}

La property bool per indicare se utilizzare il database Sql Server.

I tre Event Handler per i bottoni che avevamo impostato nell’articolo precedente, sono stati modificati, trasformandoli in 3 semplici metodi ed abbiamo implementato un nuovo event handler per il bottone singolo:

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

Questo metodo chiama un altro metodo, se mi chiedete perché, è perché lo stesso metodo sarà chiamato anche dopo l’aggiunta del record al database per aggiornare la DataGrid contenente i dati.

private void LoadData()
{
	if (UseSqlServer)
	{
		LoadFromSqlServer();
	}
	else if (UseSQLite)
	{
		LoadFromSqLite();
	}
	else
	{
		LoadFromAccess();
	}
}

Il metodo LoadData, che non fa altro che controllare i 3 flag boolean e chiamare il metodo opportuno. I tre metodi sono esattamente quelli dell’articolo precedente, hanno solo cambiato nome e sono stati eliminati i parametri dell’event handler che non utilizziamo.

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

E vediamo ora il codice del nuovo Button, per aggiungere il record. Anche in questo caso chiamiamo un metodo.

private void AddRecord()
{
	string[] data = InputData.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
	if (data.Length < 7)
	{
		MessageBox.Show(@"
You need to set 7 fields, separated by semicolon:
FirstName;FamilyName;BirthDate;Salary;ChildrenNumber;Sex;Married
In Example:
John; Smith; 28/06/1960;32800;1;M;true
no data added.");
		return;
	}
	string firstName = data[0];
	string familyName = data[1];
	DateTime birthDate = new DateTime(1900, 1, 1);
	if (!DateTime.TryParse(data[2], out birthDate))
	{
		MessageBox.Show("BirthDate is not a valid date, no data added");
		return;
	}
	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)
	{
		AddToSqlServer(firstName, familyName, birthDate, salary, childrenNumber, sex, married);
	}
	else if (UseSQLite)
	{
		AddToSQLite(firstName, familyName, birthDate, salary, childrenNumber, sex, married);
	}
	else
	{
		AddToAccess(firstName, familyName, birthDate, salary, childrenNumber, sex, married);
	}
}

Questo metodo effettua un controllo di base sul tipo di dati che si aspetta, poi effettua un Parse dei dati in formato stringa per i dati di tipo Data e di tipo Numerico e Boolean ed eventualmente da dei messaggi di errore e non inserisce alcun dato se i dati non sono validi.

private void AddToAccess(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 = @"
			INSERT INTO [TbTest]
				([FirstName]
				,[FamilyName]
				,[BirthDate]
				,[Salary]
				,[ChildrenNumber]
				,[Sex]
				,[Married])
			VALUES
				(@FirstName
				,@FamilyName
				,@BirthDate
				,@Salary
				,@ChildrenNumber
				,@Sex
				,@Married)"
			;
			cmd.Connection = cn;
			OleDbParameter[] para = new OleDbParameter[] {
	 			 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);
	}
}

Il metodo che aggiunge un dato al database Access, anche in questo caso come per la Query di selezione lanciata nell’articolo precedente, per prima cosa creiamo una Connection, precisamente una OleDbConnection, utilizzando la Connection String già usata in precedenza. (Per chi non avesse letto l’articolo precedente, per cortesia fatelo perché nell’articolo è indicato anche come effettuare il setup dei database perché tutto funzioni).

Mettimao il Comando SQL di inserimento dati all’interno dell’OleDbCommand nella property CommandText.
Creiamo un Array di OleDbParameter, in cui inseriamo nell’ordine corretto e con i nomi corretti, i sette parametri con i valori da assegnare ai campi della nostra tabella sul database.
Aggiungiamo i parametri alla collezione Parameters del nostro Command dopo aver (per sicurezza) effettuato un Clear della collezione.
Apriamo la connection.
Eseguiamo questo comando utilizzando l’ExecuteNonQuery, un metodo fatto per eseguire tutti i comandi SQL che non effettuano una Query e non restituiscono dati al chiamante.
Chiudiamo la Connection

Infine ricarichiamo i dati dal database per visualizzare la tabella aggiornata.

private void AddToSQLite(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 = @"
			INSERT INTO [TbTest]
				([FirstName]
				,[FamilyName]
				,[BirthDate]
				,[Salary]
				,[ChildrenNumber]
				,[Sex]
				,[Married])
			VALUES
				(@FirstName
				,@FamilyName
				,@BirthDate
				,@Salary
				,@ChildrenNumber
				,@Sex
				,@Married)"
								;
			cmd.Connection = cn;
			SQLiteParameter[] para = new SQLiteParameter[] {
					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);
	}
}

Il Metodo che aggiunge i dati a SQLite è identico, fatto salvo che cambiano il tipo di Connection, Command e Parameter e che assegnamo la Password alla connection prima di collegarci al database.

private void AddToSqlServer(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 = @"
			INSERT INTO [TbTest]
				([FirstName]
				,[FamilyName]
				,[BirthDate]
				,[Salary]
				,[ChildrenNumber]
				,[Sex]
				,[Married])
			VALUES
				(@FirstName
				,@FamilyName
				,@BirthDate
				,@Salary
				,@ChildrenNumber
				,@Sex
				,@Married)"
			;
			cmd.Connection = cn;
			SqlParameter[] para = new SqlParameter[] {
					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);
	}
}

Anche il metodo che aggiunge il dato a SQL Server è identico a quello usato per Access, salvo per i diversi Connection, Command e Parameter.

Il risultato:

03_adobasic_LoadAccess[5]

03_adobasic_AddToAccess[5]

Gli esempi di aggiunta dati ad Access.

03_adobasic_LoadSqlServer[5]

03_adobasic_AddToSqlServer[5]

L’esempio di aggiunta a Sql Server.

03_adobasic_LoadSQLite[5]

03_adobasic_AddToSQLite[5]

L’esempio di aggiunta dati a SQLite.

Riepilogo

Come penso possiate desumere da quello che abbiamo fatto, inserire dati in un database non è difficile, la parte più difficile è sicuramente la User Interface. Ma vediamo cosa abbiamo spiegato:

  • Come utilizzare un controllo DataGrid di WPF
  • Come formattare le colonne di una DataTable sulla Datagrid per poterle vedere correttamente.
  • Come creare un sistema di opzioni esclusive usando uno StackPanel, una serie di RadioButton e una serie di variabili boolean in Binding con i RadioButton.
  • Come utilizzare le opzioni esclusive per chiamare un metodo collegato ad uno specifico database.
  • Come creare un metodo che effettua l’inserimento di un record all’interno di uno dei nostri 3 database.

Un consiglio per voi, se testate il programma e poi volete rimettere i 3 database a posto come erano prima delle modifiche, basta ricopiare sulle cartelle impostate nell’articolo precedente il database Access e il database SQLite, ed effettuare un Restore del database TestSqlServerDb dal file .bak che si trova nel progetto.

Potete scaricare il progetto di esempio dal link qui indicato:

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