Press "Enter" to skip to content

11 – Lavorare con i Dati – UsersDb testiamo i tre database

In questo articolo, testiamo come la nostra scarna User Interface può verificare il corretto funzionamento di tutti i tre tipi di database.

Per fare questo, la prima cosa da fare è modificare l’enumerazione DataSourceType per aggiungere le due nuove fonti dati.

namespace Dnw.UsersServices
{
	public enum DataSourceType
	{
		NotSet,
 		SqlServer,
		OleDb,
		SQLite
	}
}

Fatto questo, se non lo avessimo già fatto, copiamo i file dei due database rispettivamente su C:\TestAccessDb e C:\TestSqliteDb in modo da poterli usare per i test. Se non aveste già effettuato il restore del database SQL Server, potete fare anche quello.

Modifichiamo AppContext.cs

Andiamo ora a generare in modo temporaneo per i test le stringhe di connessione ai due nuovi database all’interno della classe AppContext:

private const string CNS_Access = "Provider=Microsoft.ACE.OLEDB.12.0;Data Source=C:\\TestAccessDb\\UsersDb.accdb;Persist Security Info=True;";
private const string CNS_SqLite = "Data Source=C:\\TestSqliteDb\\UsersDb.sqlite;Version=3;";

Per prima cosa aggiungiamo le costanti con le stringhe di connessione di test. In seguito implementeremo il modo per poterle personalizzare.

public DataSourceType DataSource
{
	get
	{
		return mDataSource;
	}
	set
	{
		mDataSource = value;
	}
}

Aggiungiamo una property per poter gestire quale datasource utilizzare.

public string CnString
{
	get
	{
		switch (DataSource)
		{
			case DataSourceType.SqlServer:
				return CNS_SqlServer;
 
			case DataSourceType.OleDb:
				return CNS_Access;
 
			case DataSourceType.SQLite:
				return CNS_SqLite;
 
			default:
				return null;
		}
	}
}

Modifichiamo la property della Connection string affinché restituisca la stringa di connessione corretta in base al tipo di datasource che selezioneremo.

Modifichiamo UsersWindow.xaml

<TextBlock
     Width="100"
     Text=" "/>
<StackPanel Orientation="Horizontal" FlowDirection="LeftToRight">
      <RadioButton 
         Margin="4,10,4,10"
         Padding="2,4,2,4"
         HorizontalAlignment="Left"
         VerticalAlignment="Center"
         VerticalContentAlignment="Center"
         IsChecked="{Binding UseSqlServer, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
               Use Sql Server
      </RadioButton>
      <RadioButton 
         Margin="4,10,4,10"
         Padding="2,4,2,4"
         HorizontalAlignment="Left"
         VerticalAlignment="Center"
         VerticalContentAlignment="Center"
         IsChecked="{Binding UseAccess, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                Use Access
      </RadioButton>
      <RadioButton 
         Margin="4,10,4,10"
         Padding="2,4,2,4"
         HorizontalAlignment="Left"
         VerticalAlignment="Center"
         VerticalContentAlignment="Center"
         IsChecked="{Binding UseSSqlite, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
                Use SQLite
      </RadioButton>
</StackPanel>

Per implementare il test dei 3 diversi data provider, inseriamo una serie di radio button che ci permetteranno di decidere la data source che vogliamo testare.

Ognuno dei RadioButton ha la property IsChecked collegata ad una property che andremo ad inserire nel view model della nostra window, che in questo caso è semplicemente la window stessa.

Modifichiamo UsersWindow.xaml.cs

public bool UseAccess
{
	get
	{
		return mUseAccess;
	}
	set
	{
		mUseAccess = value;
		if (value)
		{
			UpdateDataSource(DataSourceType.OleDb);
		}
		OnPropertyChanged(FLD_UseAccess);
	}
}

Generiamo la prima delle tre property boolean collegate ai radio button, come potete notare, quando la property viene posta a true chiamiamo un metodo che aggiorna il tipo di data source. I RadioButton, quando inclusi in uno stesso control container, come in questo caso nello StackPanel da noi inserito nella window, sono automaticamente mutuamente esclusivi, pertanto selezionando uno dei tre, gli altri 2 vengono automaticamente resettati.

Da qui la nostra chiamata al metodo di cambio datasource effettuata solo quando la property assume il valore true.

public bool UseSQLite
{
	get
	{
		return mUseSQLite;
	}
	set
	{
		mUseSQLite = value;
		if (value)
		{
			UpdateDataSource(DataSourceType.SQLite);
		}
		OnPropertyChanged(FLD_UseSQLite);
	}
}

La seconda property, per il database SQLite.

public bool UseSqlServer
{
	get
	{
		return mUseSqlServer;
	}
	set
	{
		mUseSqlServer = value;
		if (value)
		{
			UpdateDataSource(DataSourceType.SqlServer);
		}
		OnPropertyChanged(FLD_UseSqlServer);
	}
}

La terza property, per SQL Server.

private void UpdateDataSource(DataSourceType type)
{
	AppContext.Instance.DataSource = type;
}

Il nostro metodo che modifica la property all’interno della classe AppContext.

public UsersWindow()
{
	InitializeComponent();
	this.Icon = BitmapFrame.Create(new Uri("pack://application:,,,/icon_2016.ico", UriKind.RelativeOrAbsolute));
	DataContext = this;
	Users = new ObservableCollection<User>();
	UseSqlServer = true;
}

Nel costruttore della window, inizializziamo a true il valore di SQLServer come valore di default.

UsersDataManager udm = new UsersDataManager(AppContext.Instance.CnString, AppContext.Instance.DataSource);

Nel metodo del pulsante di test del data provider, modifichiamo solo una riga, l’inizializzazione del Data Manager sostituendo al valore precedente, SqlServer, il valore impostato nella classe AppContext.

Se ora lanciamo l’applicazione, vedremo come se clicchiamo il bottone Test Service Provider Class con SqlServer selezionato, funzionerà come prima, se invece selezioniamo Access o SQLite, otterremo questo messaggio:

11_usersdb_01_error_dp_invalid[4]

Questo perché la classe dei servizi non gestisce ancora i due nuovi data provider, vediamo quindi quali modifiche fare per gestire anche i due nuovi database:

Prima di iniziare a modificare UsersDataManager, referenziamo dal progetto della libreria dei servizi, le nuove librerie Data Provider:

11_usersdb_02_referencedp[4]

Modifiche a UsersDataManager.cs

Premetto che per questo progetto didattico, ho deciso di utilizzare il metodo più semplice possibile per gestire i database multipli, infatti, avrei potuto utilizzare interfacce e/o classi derivate per ottenere lo stesso effetto ma essendo un progetto destinato ai beginners preferisco qualcosa di visivamente molto semplice e facile da comprendere. In seguito, se vi sarà curiosità in merito potremo decidere di vedere come migliorare/ottimizzare la nostra gestione in modo da diminuire sensibilmente la quantità di codice da scrivere per gestire più datasource diversi.

private Data.OleDb.UsersDp mOleDbDp;
 
private Data.SQLite.UsersDp mSQLiteDp;

Per prima cosa, generiamo le classi data provider per gli altri 2 database nel nostro service manager.

public UsersDataManager(string cnString, DataSourceType dataType)
{
	DataType = dataType;
	CnString = cnString;
	switch (DataType)
	{
		case DataSourceType.SqlServer:
			mSqlDp = new Data.SqlServer.UsersDp(CnString);
			break;
		case DataSourceType.OleDb:
			mOleDbDp = new Data.OleDb.UsersDp(CnString);
			break;
		case DataSourceType.SQLite:
			mSQLiteDp = new Data.SQLite.UsersDp(CnString);
			break;
	}
}

Aggiungiamo i due nuovi data provider al costruttore.

public Result<List<int>> Delete(List<User> usersToDelete)
{
	switch (DataType)
	{
		case DataSourceType.SqlServer:
			return mSqlDp.Delete(usersToDelete);
		case DataSourceType.OleDb:
			return mOleDbDp.Delete(usersToDelete);
		case DataSourceType.SQLite:
			return mSQLiteDp.Delete(usersToDelete);
 
		default:
			Result<List<int>> ret = Result.Get<List<int>>();
			ret.SetError(ERR_NoDatasource);
			return (ret);
	}
}
public Result<List<User>> Insert(List<User> usersToInsert)
{
	switch (DataType)
	{
		case DataSourceType.SqlServer:
			return mSqlDp.Insert(usersToInsert);
		case DataSourceType.OleDb:
			return mOleDbDp.Insert(usersToInsert);
		case DataSourceType.SQLite:
			return mSQLiteDp.Insert(usersToInsert);
 
		default:
			Result<List<User>> ret = Result.Get<List<User>>();
			ret.SetError(ERR_NoDatasource);
			return (ret);
	}
}
public Result<List<User>> SelectAll()
{
	switch (DataType)
	{
		case DataSourceType.SqlServer:
			return mSqlDp.SelectAll();
		case DataSourceType.OleDb:
			return mOleDbDp.SelectAll();
		case DataSourceType.SQLite:
			return mSQLiteDp.SelectAll();
 
		default:
			Result<List<User>> ret = Result.Get<List<User>>();
			ret.SetError(ERR_NoDatasource);
			return (ret);
	}
}
public Result<List<User>> Update(List<User> usersToUpdate)
{
	switch (DataType)
	{
		case DataSourceType.SqlServer:
			return mSqlDp.Update(usersToUpdate);
		case DataSourceType.OleDb:
			return mOleDbDp.Update(usersToUpdate);
		case DataSourceType.SQLite:
			return mSQLiteDp.Update(usersToUpdate);
		default:
			Result<List<User>> ret = Result.Get<List<User>>();
			ret.SetError(ERR_NoDatasource);
			return (ret);
	}
}

Modifichiamo i 4 metodi di CRUD per gestire i nuovi database.

Iniziamo il test selezionando il database Access e premendo ancora una volta il tasto Test Service Provider Class.

Se siete degli studenti modello e avete implementato a mano le modifiche fatte oggi fino a qui, rispetto alla versione della scorsa puntata, riceverete immediatamente un bel messaggio di errore, dovuto al fatto che sfortunatamente il funzionamento di OleDb non è esattamente uguale al funzionamento di SQL Server, pertanto dobbiamo fare alcune piccole modifiche alla classe Data Provider nella libreria OleDb.

Prima modifica:

In tutte le stringhe SQL, sostituiamo:

[dbo].[TbUsers] con [TbUsers]

Infatti Access ed in generale i database diversi da SQL Server non gestiscono gli Schemi all’interno di un database.

La seconda modifica è un po’ più complicata ed è dovuta al fatto che Access gestisce in modo diverso la possibilità di restituire il valore dell’IDentity appena inserita. Infatti, non permette di eseguire le due istruzioni di Insert e Select contemporaneamente, bisogna eseguire due query separate ecco quindi la modifica:

private const string SQL_Insert = @"
	INSERT INTO [TbUsers]
				([WinUser]
				,[WinDomain]
				,[UserName]
				,[Computer]
				,[LoginType]
				,[Password])
			VALUES
				(@WinUser
				,@WinDomain
				,@UserName
				,@Computer
				,@LoginType
				,@Password );";
 
private const string SQL_GetIdentity = @"
	SELECT @@IDENTITY AS 'Identity';	";

Trasformiamo la query di Insert, suddividendola in due parti.

OleDbParameter[] para = new OleDbParameter[] {
		new OleDbParameter("@WinUser", usr.WinUser.XxTryParseToDBNull<string>())
	,new OleDbParameter("@WinDomain", usr.WinDomain.XxTryParseToDBNull<string>())
	,new OleDbParameter("@UserName", usr.UserName.XxTryParseToDBNull<string>())
	,new OleDbParameter("@Computer", usr.Computer.XxTryParseToDBNull<string>())
	,new OleDbParameter("@LoginType", usr.LoginType.XxTryParseToDBNull<TypeOfLogin>(TypeOfLogin.None))
	,new OleDbParameter("@Password", usr.Password.XxTryParseToDBNull<string>())
};
cmd.Parameters.Clear();
cmd.Parameters.AddRange(para);
cmd.ExecuteNonQuery();
cmd.Parameters.Clear();
cmd.CommandText = SQL_GetIdentity;
object obj = cmd.ExecuteNonQuery();
if (obj != null)
{
	int id = -1;
	int.TryParse(obj.ToString(), out id);
	usr.ID = id;
	ret.Data.Add(usr);
}

Modifichiamo il codice all’interno del metodo Insert esattamente la parte nel ciclo foreach come qui sopra illustrato, quindi eseguiamo prima la query di insert con un ExecuteNonQuery, poi eseguiamo la query GetIdentity dopo aver eliminato i parametri del command e con l’ExecuteScalar recuperiamo il valore dell’identity che aggiorneremo nella nostra classe User.

Terminate queste modifiche, anche il nostro Data provider OleDb funziona perfettamente, quindi il risultato dovrebbe essere visibile sulla Window di test.

Se passiamo a testare il Data Provider di SQLite, da cui abbiamo già sostituito

[dbo].[TbUsers] con [TbUsers]

Sembrerebbe funzionare tutto perfettamente, fatto salvo il seguente problema:

——————————————————————————–

The Added users are:


Windows Credentials: GDBNere Domain_999 Computer PC_999


User Data: 0 Giovanni Dalle Bande Nere Windows

 


Windows Credentials: BMoranni Domain_999 Computer PC_888


User Data: 0 Bartolomeo Moranni Manual

 


Windows Credentials: LFantoni Domain_999 Computer PC_777


User Data: 0 Luigino Fantoni Windows

 


——————————————————————————–

Sembra che i nuovi record inseriti non abbiano un ID valido quindi la nostra query Insert + Select @@IDentity sembra non funzionare correttamente.

Anche in questo caso è perché il driver di SQLite non funziona esattamente come SQL Server, pertanto dobbiamo modificare leggermente il codice SQL del metodo Insert:

private const string SQL_Insert = @"
	INSERT INTO [TbUsers]
				([WinUser]
				,[WinDomain]
				,[UserName]
				,[Computer]
				,[LoginType]
				,[Password])
			OUTPUT INSERTED.ID 
			VALUES
				(@WinUser
				,@WinDomain
				,@UserName
				,@Computer
				,@LoginType
				,@Password )";

Per SQLite, per fare in modo che ci venga ritornato il valore dell’ID appena inserito, dobbiamo inserire la clausola OUTPUT INSERTED.ID nella query ed il risultato è corretto.

——————————————————————————–

The Added users are:


Windows Credentials: GDBNere Domain_999 Computer PC_999


User Data: 1041 Giovanni Dalle Bande Nere Windows

 


Windows Credentials: BMoranni Domain_999 Computer PC_888


User Data: 1042 Bartolomeo Moranni Manual

 


Windows Credentials: LFantoni Domain_999 Computer PC_777


User Data: 1043 Luigino Fantoni Windows

 


——————————————————————————–

Il risultato infatti è quello visibile qui sopra (non guardate ai numeri 1041 ecc. perché dipendono da quante volte ho fatto i test).

A questo punto, abbiamo raggiunto il nostro obiettivo base ed abbiamo generato e testato gli strati di servizio della nostra applicazione Multi Tier

11_usersdb_03_workingschema[4]

Peranto il Data Tier, l’Entity Tier e il Service Tier della nostra applicazione sono completati. Ora rimane la parte più divertente, ovvero l’implementazione della User Interface.

Riepilogo

In questo articolo abbiamo approfondito i seguenti argomenti:

  • Modificare la User Interface per testare le due nuove classi Data Provider.
  • Testare la classe OleDb Data provider per Access, modificandola per supportare le piccole differenze rispetto a SQL Server.
  • Testare la classe SQLite Data provider, modificandola per supportare le piccole differenze rispetto a SQL Server.

Potete scaricare il codice del progetto al seguente link:

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