Utilisation de la réplication SQL dans le code .NET d'une application mobile - Implémentation & Conseils : PARTIE 2/3 (Synchronisation asynchrone)
Dans la première partie de cet article, nous avons vu de quelle manière s’utilisent les propriétés et les méthodes de la classe SqlCeReplication. Nous avons notamment écrit un simple code C# qui permet de répliquer une base SQL Server Compact à partir d’une base sur un serveur.
Cette seconde partie de l’article traite de l’exécution de la réplication SQL en mode asynchrone.
L’utilisation de la méthode Synchronize (dans la partie 1) nous a permis de répliquer les données en mode synchrone (c'est-à-dire que l’exécution de cette méthode est bloquante pour l’utilisateur).
Il est tout à fait possible d’exécuter la réplication SQL d’une manière asynchrone (interface graphique de l’utilisateur non bloqué durant l’exécution de la réplication).
Certes, le code .NET associé à la réplication en asynchrone est plus lourd et plus complexe à écrire, mais le développeur peut en tirer des avantages précieux surtout en terme de reporting. En effet, l’exécution de la réplication en asynchrone permet de récupérer des informations utiles comme le nom des tables en cours de synchronisation, ou encore de connaitre le pourcentage d’avancement de la réplication…
Méthode BeginSynchronize et delegates :
Pour répliquer de manière asynchrone, il est nécessaire de faire appel à la méthode BeginSynchronize au lieu de la méthode Synchronize.
La méthode BeginSynchronize peut prendre en argument plusieurs “delegates” qui vont permettre au développeur de récupérer différents évènements liés à la phase de réplication :
// (...)
repl.BeginSynchronize(new AsyncCallback(SyncCompletedCallback),
new OnStartTableUpload(TableUploadCallback),
new OnStartTableDownload(TableDownloadCallback),
new OnSynchronization(SynchronizingCallback), repl);
// (...)
-
La première delegate de type AsyncCallback notifie que la phase de réplication est achevée. C’est d’ailleurs dans la méthode associée à cette delegate (ici : SyncCompletedCallback) qu’il faudra nécessairement faire appel à la méthode EndSynchronize de l’objet SqlCeReplication.
-
La seconde, de type OnStartTableUpload, est levée à chaque fois qu’une table est en cours d’upload. Elle doit absolument recevoir une chaine de caractères en argument qui est destinée à contenir le nom de la table répliquée.
-
La troisième quant à elle, est levée quand une table est en cours de download. Elle doit absolument recevoir une chaine de caractères en argument qui est destinée à contenir le nom de la table répliquée.
-
Enfin, la dernière permet de notifier régulièrement le pourcentage d’avancement de la réplication.
Il est donc nécessaire d’écrire convenablement les 4 méthodes delegates associées, en respectant la bonne signature :
private void SyncCompletedCallback(IAsyncResult ar)
{
}
private void TableUploadCallback(IAsyncResult ar, string tableName)
{
}
private void TableDownloadCallback(IAsyncResult ar, string tableName)
{
}
private void SynchronizingCallback(IAsyncResult ar, int percent)
{
}
Obtention du statut de la réplication & Threads :
Désormais nous pouvons connaitre le pourcentage d’avancement ainsi que les tables en cours de réplication, durant le processus de synchronisation.
Nous pouvons ainsi rajouter un label sur la “form” Windows Mobile destiné à afficher ces différentes informations à l’utilisateur :
Comme dans tous les traitements asynchrones, la réplication SQL lancée via l’instruction BeginSynchronize se déroulera dans un thread détaché de l’interface graphique (= processus en arrière-plan). Aussi, il faudra faire appel aux méthodes Invoke pour que le thread de la réplication puisse accéder et mettre à jour les informations du label créé.
Dans un premier temps, nous créons donc un EventHandler updateStatusHandler que nous instancions dans le constructeur de la “form” en précisant la méthode qui va permettre de mettre à jour le statut à l’écran. La variable globale _currentStatus contiendra simplement le statut courant de la réplication, c'est-à-dire le texte à afficher dans le label :
private EventHandler updateStatusHandler;
private string _currentStatus = String.Empty;
public Form1()
{
InitializeComponent();
this.updateStatusHandler = new EventHandler(UpdateStatus);
}
private void UpdateStatus(object sender, System.EventArgs e)
{
this.lblStatus.Text = _currentStatus;
}
Ensuite, il ne reste plus qu’à compléter les différentes delegates présentées précédemment, c’est à dire de mettre à jour le contenu de la variable _currentStatus et de faire un “Invoke” de la delegate updateStatusHandler pour rafraichir les informations :
private void SyncCompletedCallback(IAsyncResult ar)
{
try
{
SqlCeReplication repl = (SqlCeReplication)ar.AsyncState;
repl.EndSynchronize(ar);
_currentStatus = "Synchro OK !";
this.Invoke(updateStatusHandler);
}
catch (SqlCeException ex)
{
MessageBox.Show(ex.Message);
}
}
private void TableUploadCallback(IAsyncResult ar, string tableName)
{
_currentStatus = "Upload table : " + tableName;
this.Invoke(updateStatusHandler);
}
private void TableDownloadCallback(IAsyncResult ar, string tableName)
{
_currentStatus = "Download table : " + tableName;
this.Invoke(updateStatusHandler);
}
private void SynchronizingCallback(IAsyncResult ar, int percent)
{
_currentStatus = "Synchro : " + percent.ToString() + "%";
this.Invoke(updateStatusHandler);
}
Au final, le code complet de la réplication est le suivant :
using System;
using System.Data.SqlServerCe;
using System.Windows.Forms;
namespace TestReplication2
{
public partial class Form1 : Form
{
private const string _dbName = "technomade_mob.sdf";
private EventHandler updateStatusHandler;
private string _currentStatus = String.Empty;
public Form1()
{
InitializeComponent();
this.updateStatusHandler = new EventHandler(UpdateStatus);
}
private void UpdateStatus(object sender, System.EventArgs e)
{
this.lblStatus.Text = _currentStatus;
}
private void btnSynchro_Click(object sender, EventArgs e)
{
SqlCeReplication repl = null;
try
{
repl = new SqlCeReplication();
repl.InternetUrl = "http://10.10.10.10/webSync/sqlcesa35.dll";
repl.InternetLogin = "login";
repl.InternetPassword = "password";
repl.Publisher = @"INSTANCE_SQL";
repl.PublisherDatabase = "BASE";
repl.PublisherSecurityMode = SecurityType.NTAuthentication;
repl.Publication = "Pub";
repl.Subscriber = "PDA1";
repl.HostName = "PDA2";
repl.SubscriberConnectionString = @"Data Source=" + _dbName
+ ";Max Database Size=1000;Default Lock Escalation =100;";
if (!System.IO.File.Exists(_dbName))
repl.AddSubscription(AddOption.CreateDatabase);
repl.BeginSynchronize(new AsyncCallback(SyncCompletedCallback),
new OnStartTableUpload(TableUploadCallback),
new OnStartTableDownload(TableDownloadCallback),
new OnSynchronization(SynchronizingCallback), repl);
}
catch (SqlCeException ex)
{
// Catch !
}
finally
{
if (repl != null)
repl.Dispose();
}
}
private void SyncCompletedCallback(IAsyncResult ar)
{
try
{
SqlCeReplication repl = (SqlCeReplication)ar.AsyncState;
repl.EndSynchronize(ar);
_currentStatus = "Synchro OK !";
this.Invoke(updateStatusHandler);
}
catch (SqlCeException ex)
{
MessageBox.Show(ex.Message);
}
}
private void TableUploadCallback(IAsyncResult ar, string tableName)
{
_currentStatus = "Upload table : " + tableName;
this.Invoke(updateStatusHandler);
}
private void TableDownloadCallback(IAsyncResult ar, string tableName)
{
_currentStatus = "Download table : " + tableName;
this.Invoke(updateStatusHandler);
}
private void SynchronizingCallback(IAsyncResult ar, int percent)
{
_currentStatus = "Synchro : " + percent.ToString() + "%";
this.Invoke(updateStatusHandler);
}
}
}
A noter qu’il aurait également été plus astucieux de créer une enumeration des différents statuts de la phase de réplication, au lieu d’utiliser une variable globale de type string.
A suivre…
Pi-R.
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :