Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Réplication SQL avec SQL Server Compact : Mon premier livre blanc !

Mon premier livre blanc sur la réplication de fusion avec SQL Server Compact est aujourd’hui disponible !

Pour le télécharger :

http://www.wygwam.com/Documents/ReplicationFusionSQLServerCompact.pdf

 

Ce livre blanc m’a permis de réunir toutes mes connaissances et mon expérience sur le sujet. Il rassemble notamment tous les articles que j’ai publié, et bien plus…

Comme un tutoriel, il explique comment implémenter un système de synchronisation de données de A à Z, en utilisant la réplication de fusion SQL entre une base centrale SQL Server et des bases mobiles SQL Server Compact. Il décrit pas à pas les étapes de configuration nécessaires au bon fonctionnement d’un système de réplication.

Il se base également sur un exemple concret de projet mobile pour exposer les grands concepts de la construction d’un système de réplication adéquat, et optimisé.

Je remercie les personnes avec lesquelles j’ai eu la chance de travailler, et qui m’ont fortement conseillé sur le fond et la forme de cet article. Merci surtout à Grégory Renard [Rédo], et Marine Payen (Wygwam) de m’avoir accompagné pour la diffusion de cet article.

 

N’hésitez pas à me donner votre avis sur cet article.

De mon côté, je vais un peu laisser de côté ce sujet, pour m’attaquer plus sérieusement à d’autres technologies de synchronisation de données (pour la mobilité), par exemple le nouveau “Sync Framework”…

Pi-R.

Optimisation des performances de la réplication avec SQL Server Compact, problématique de la volumétrie : PARTIE 3/3 (Autres pistes)

Dans la seconde partie de cet article, nous avons montré comment optimiser les synchronisations des terminaux et notamment comment améliorer l’utilisation des “Precompute partitions” en retravaillant le système de filtres.

D’autres actions peuvent également être entreprises si l’on souhaite aller plus loin en matière d’optimisations. Cette ultime partie de mon article expose plusieurs pistes pour améliorer davantage les performances…

 

Optimiser les mises à jour ou les synchronisations ?

Nous pouvons donc distinguer 2 types d’actions :

  • Celles qui vont permettre d’optimiser les mises à jour volumineuses. Ces instructions devront être lancées avant les traitements de nuit par exemple dans le cadre de l’utilisation des “Precompute partitions”.
  • Celles qui vont permettre de préparer au mieux le système pour accélérer les phases de synchronisations des terminaux. Ces traitements devront être exécutés juste après les mises à jour en masse.

Certaines actions permettent de préparer au mieux les mises à jour en masse sur la base de données (s’effectuant par exemple lors des traitements de nuits). Les modifications sur la base, effectuées par ces actions, devront être rétablies pour que le système soit de nouveau prêt à prendre en charge les synchronisations de terminaux portables.

 

Les statistiques :

Pour optimiser au mieux le temps d’exécution des requêtes SQL sur la base centrale, il est possible d’activer la génération automatique de statistiques. Les statistiques SQL Server permettent au système de connaitre les meilleurs “plans d’exécution” pour l’exécution d’une requête particulière. Cette option s’active dans les propriétés de la base de données (page “Options”) :

image

Cette option n’altère pas les performances des synchronisations.

 

Les indexes :

Pour optimiser toujours plus le temps d’exécution des requêtes suite à l’activation des “Precompute partitions”, nous pouvons prêter plus attention aux indexes posés sur les différentes tables articles.

Pour que les performances des requêtes soient optimales lors de mises à jour en masse :

  • On peut placer des indexes de type “nonclustered index” sur tous les champs utilisés dans les jointures SQL du système de filtres.
  • On peut désactiver temporairement les indexes fonctionnels (c’est à dire ceux qui servent uniquement pour le fonctionnel de l’application mobile) avant d’exécuter des mises à jour en masse sur la base.

Les indexes que nous désactivons doivent évidemment être réactivés juste après les mises à jour.

Remarque :

Il est également important de reconstruire régulièrement les indexes pour garder des performances optimales. En effet, il ne faut pas oublier que certaines lourdes modifications de données sur la base peuvent considérablement fragmenter les indexes. Cette fragmentation aura des effets négatifs sur la réplication SQL mais également sur l’application mobile (Les requêtes SELECT jouées sur la base mobile répliquée seront alors plus lentes).

 

Le degré de parallélisme :

SQL Server détecte le degré de parallélisme optimal, qui correspond au nombre de processeurs employés pour exécuter une seule instruction. Lors de l’exécution de requêtes SQL, le serveur prend en compte les plans d'exécution parallèle. Ce parallélisme, c'est-à-dire l’utilisation d’un maximum de processeurs, joue un rôle très important pour les temps de réponses des requêtes SQL. Pour permettre au serveur de déterminer le degré maximal de parallélisme, il faut définir cette option à 0.

L’option “max degree of parallelism” avec sa valeur 0, permet de maximiser le parallélisme avant des mises à jour volumineuses.

Cette option doit être modifiée à l’aide de la procédure stockée sp_configure :

exec sp_configure 'show advanced options', 1
reconfigure with override
exec sp_configure 'max degree of parallelism', 0
reconfigure with override

Inversement, pour optimiser les synchronisations de données, il est préférable de minimiser le parallélisme.

Le parallélisme peut avoir cette fois des effets négatifs lors des phases de synchronisations. En effet, des verrous (locks) peuvent régulièrement être positionnés sur les tables systèmes à cause du multi threading.

En choisissant d’utiliser un seul processeur pour les phases de synchros, nous limitons ainsi le nombre de threads et donc le nombre de verrou (locks) qui seront posés sur les tables systèmes.

Pour supprimer la génération de plans parallèles, il est nécessaire d’attribuer la valeur 1 à l'option “max degree of parallelism”.

 

Période de rétention :

La période de rétention correspond à un délai durant lequel les métadonnées liées aux synchronisations sont stockées et historisées dans des tables systèmes.

Une fois la période de rétention dépassée, les données sont purgées et les abonnements liés à la publication deviennent obsolètes et doivent être réinitialisés (voir article ici).

Aussi, pour éviter d’effectuer une réinitialisation, chaque terminal doit répliquer au moins une fois pendant le délai de rétention. Si un PDA ne réplique pas au moins une fois durant cette période, la réinitialisation de son abonnement (base locale) sera inévitable.

La période de rétention doit être paramétrée de manière habile, selon les besoins métiers :

  • Si la période de rétention est trop courte, les terminaux sur le terrain risquent de devoir souvent réinitialiser leurs abonnements (très couteux).
  • Si la période de rétention est trop longue, la volumétrie des métadonnées stockées en tables systèmes deviendrait alors trop importante et risquerait de fortement réduire les performances.

La configuration de la période de rétention est possible sur la page “General” des propriétés de la publication :

imageLa période de rétention que l’on définit est très importante pour les performances du système. En effet, si la période de rétention est trop longue, la volumétrie des métadonnées stockées en tables systèmes deviendrait alors trop importante et risquerait de fortement réduire le temps d’exécution des mises à jour de données.

 

Profil de distribution :

La notion de “profil de distribution” représente un ensemble d’options utilisées par le distributor lors des phases de transferts de données vers les terminaux portables.

Lors de la configuration initiale du distributor, un profil par défaut lui est automatiquement attribué.

Il existe une astuce pour accélérer les phases de transferts dans le cas où de nombreuses lignes de données doivent fréquemment être distribuées vers les abonnés (= terminaux mobiles). En effet, il est possible de changer la taille des blocs de données lues et distribuées par le distributeur. Le fait d’augmenter la taille maximale des blocs (batch) peut accélérer les phases de synchronisations dans le sens où plus de données sont transférées en même temps.

Dans les propriétés du profil de distribution, nous pouvons ainsi modifier la valeur des options “DownloadReadChangesPerBatch” et “DownloadWriteChangesPerBatch” :

image

Le fait de définir la valeur 1000 au lieu de 100 pour ces 2 options aura un effet positif sur les temps de transferts lorsque le nombre de données à transférer sur un terminal sera de l’ordre de plusieurs centaines de lignes.

En effet, la distribution se basera alors sur des blocs de 1000 lignes au lieu de 100 pour transférer les données. Les phases de transfert des synchronisations seront alors accélérées dans le cas d’une grande volumétrie de données à descendre.

 

Pi-R.

Optimisation des performances de la réplication avec SQL Server Compact, problématique de la volumétrie : PARTIE 2/3 (Simplification d'un système de filtres)

Dans la première partie de cet article, nous sommes entrés dans le vif du sujet en abordant une problématique de performances et de volumétrie de données dans le cadre de gros projets mobiles.

Nous avons également vu l’utilité de l’option “Precompute partitions”, qui joue un rôle important pour limiter les phases de calculs au moment qu’un terminal se connecte au serveur pour synchroniser ses données.

L’activation de l’option “Precompute partitions” est très intéressante mais peut nécessiter de retravailler les systèmes de filtres complexes pour obtenir des temps de réponses convenables lorsque l’on joue une simple requête SQL sur la base…

 

Simplification d’un système de filtres :

Il existe plusieurs préconisations afin de simplifier un système de filtres complexe pour le rendre plus performant avec l’utilisation de l’option “Precompute partitions” :

  • Limitez le nombre d’articles filtrés. Les tables non-volumineuses peuvent très bien ne pas être filtrées. En résultera alors une augmentation du volume des bases répliquées mais qui devrait être toutefois acceptable.
  • Limitez le nombre de niveaux de filtres. Plus l’arborescence de filtres est complexe (en niveaux de profondeurs), plus les calculs seront difficiles lors de l’exécution d’une requête sur un article.

En résumé, implémenter une arborescence de filtres simple, qui comporte un nombre limité d’articles filtrés et moins de 2 niveaux de profondeurs, est conseillé pour utiliser convenablement l’option “Precompute partition”.

 

Exemple :

Le système de filtres exposé ci-dessous est complexe. Il est composé de 17 tables disposées en 7 niveaux de profondeurs dans l’arborescence. Ce système est mal adapté dans le cadre de l’utilisation de l’option “Precompute partitions” avec une base volumineuse et un grand nombre de partitions :

image

Ce système de filtres a été retravaillé pour donner l’arborescence ci-dessous :

image

Il s’agit d’une arborescence simplifiée, composée de 11 tables disposées en 2 niveaux de profondeurs seulement.

Pour aboutir à ce système simplifié et adéquat pour l’utilisation du “Precompute partitions”, nous avons dans un premier temps supprimé 6 tables non-volumineuses qu’il n’était pas nécessaire de filtrer. Ensuite, nous avons “aplati” l’arborescence de telle manière à obtenir un arbre composé de 2 niveaux.

Remarque :

Pour simplifier une arborescence de filtres, il est parfois nécessaire de rajouter des colonnes sur certaines tables pour pouvoir assurer les jointures SQL nécessaires.

Important :

Il est important de signaler que c’est sur les tables “nœuds” de l’arbre (La table 3 dans l’exemple ci-dessus) que les temps d’exécutions des requêtes seront les plus coûteux. Les exécutions de requêtes SQL sur l’ensemble des autres tables « feuilles » seront moins coûteuses.

A suivre…

 

Pi-R.

Optimisation des performances de la réplication avec SQL Server Compact, problématique de la volumétrie : PARTIE 1/3 (Problématique & Precompute partitions)

Dans mes précédents articles sur la réplication SQL, il m’est souvent arrivé d’exposer quelques conseils quant à la mise en place d’un système de synchronisation avec des terminaux mobiles.

Aujourd’hui, l’article que je vous propose va plus loin.

Composé en 3 parties, il aborde les problématiques de performances et de volumétrie de données dans le cadre de gros projets qui mettent en action un grand parc de terminaux portables.

Cet article me permet surtout de réunir, et d’exposer, toutes les astuces que je connais au travers de mes expériences professionnelles, pour construire un système de réplication fiable, résistant à la charge et aux lourdes mises à jour de données.

 

Problématique :

La problématique abordée dans cet article est donc celle des gros projets qui mettent en action un grand parc de terminaux portables. En effet, la configuration d’un système de réplication fiable se complique lorsque le nombre de terminaux portables abonnés devient important.

Mettre en place un système de réplication performant devient surtout un casse-tête lorsque la base centrale est volumineuse et lorsque les mises à jour sont fréquentes sur cette dernière.

Il faut d’abord savoir que lorsqu’un terminal se connecte au serveur, une phase de calculs suit directement l’initialisation de la synchronisation. Cette phase de calculs, prise en charge par SQL Server et par l’agent de réplication, est nécessaire pour déterminer précisément quelles sont les données modifiées à distribuer sur le terminal connecté.

Ces calculs sont d’autant plus longs et complexes quand la volumétrie de la base est importante et quand le nombre de partitions, c'est-à-dire le nombre de terminaux abonnés, est conséquent.

De la même façon, la volumétrie de données brassées par les mises à jour sur la base publiée peut également alourdir considérablement cette phase de calculs.

Il faut garder dans l’esprit que plus les données sont modifiées, plus le processus de réplication mettra du temps pour lister l’ensemble des lignes qui doivent être descendues sur les différents terminaux. Un système de filtres complexe peut également avoir des effets négatifs lors de la synchronisation d’un terminal.

La complexité de cette phase de calculs est donc relative à plusieurs points :

  • La volumétrie de la base publiée
  • Le nombre d’abonnements associés à une publication
  • Le système de filtres implémenté sur les articles de la réplication
  • La fréquence des mises à jour sur la base publiée et la volumétrie de données brassées durant ces mises à jour

Dans ce genre de configuration, la synchronisation d’un terminal peut rester bloquée durant plusieurs longues minutes sur cette phase de calculs avant que le transfert des données modifiées s’effectue réellement. Pire encore, il est même possible que les calculs n’aboutissent pas et que le système de réplication tombe en “TimeOut”. Si ce cas se produit, il sera alors impossible de continuer à synchroniser les PDA ! Il faudra alors augmenter le délai du “TimeOut” par défaut ou effectuer des manipulations risquées dans les tables systèmes pour continuer à utiliser le système sans détruire les publications (solutions non acceptables).

Ce genre de problème peut être détecté sur l’écran de monitoring des synchronisations. Il se matérialise souvent par des messages d’erreur comme “Timeout expired” ou encore “The merge process failed to enumerate changes in articles…”.

A noter que cette problématique doit surtout être prise au sérieux lorsque la base de données centrale subit des mises à jour en masse suite à l’exécution de traitements de nuit par exemple.

Néanmoins, il existe des solutions pour que ce genre de problème n’apparaisse pas !

En effet, il sera nécessaire d’attacher plus d’importance à la manière dont nous configurons les publications et le système de réplication en général. Cette optimisation passe dans un premier temps par l’activation de l’option “Precompute partitions”…

 

Activation de l’option “Precompute partitions” :

Pour activer l’option “Precompute partitions” sur une publication, il est nécessaire de passer la valeur de l’option à “true” dans les écrans de paramètres de la publication (page “Subscription Options”) :

image L’option “Precompute partitions” permet d’éviter les phases de calculs durant les processus de synchronisation. En effet, l’activation de cette option permet d’effectuer le filtrage et l’archivage des lignes directement lors des mises à jour sur les tables articles. L’activation de cette option permet donc de préparer au mieux les données à télécharger pour chacune des partitions, c’est à dire pour chacun des terminaux portables.

Concrètement, en activant cette option nous supprimons l’ensemble des calculs qui ont lieu lors de l’initialisation d’une synchronisation, mais nous rajoutons un traitement supplémentaire en amont, à chaque mise à jour de données.

Process :

  1. Une requête (INSERT, UPDATE ou DELETE) est jouée sur une table article
  2. Le système détermine alors quel terminal est affecté par cette modification (parcours du système de filtre et des différentes partitions)
  3. Et ainsi de suite pour l’ensemble des requêtes exécutées lorsque l’option “Precompute partitions” est activée…

Lorsque le terminal se connecte pour effectuer une synchronisation, toutes les données le concernant sont déjà prêtes à être transmises. Le transfert de données peut donc avoir lieu immédiatement.

Attention :

L’activation de cette option n’est donc pas sans impact. En effet, une fois activée, les modifications de données sur les articles par des requêtes INSERT, UPDATE ou DELETE génèrent un calcul de filtres et mettent donc plus de temps à s’effectuer. Les temps d’exécution de ces requêtes doivent rester acceptables dans le cadre de mises à jour en masse par des traitements de nuit.

Avec l’option “Precompute partitions”, les temps d’exécution des requêtes sont liés au nombre de partitions déclarées, mais également au système de filtres implémenté. En effet, ces temps deviennent vite inacceptables si le système de filtres mis en place est trop complexe…

… A suivre….

 

Pi-R.

Réplication SQL pour les projets mobiles : Gestion des conflits et résolveurs - PARTIE 2/2 (Rédaction d'un résolveur personnalisé en C#)

Introduction :

Dans la première partie de cet article, nous avons étudié les généralités de la gestion des conflits pour la réplication de fusion SQL. Nous allons voir ici un exemple de création d’un résolveur de conflits personnalisé en C# pour répondre à des besoins spécifiques.

La rédaction d’un résolveur en .NET est rendu possible grâce à une API Microsoft qui permet d'écrire une logique métier personnalisée pour gérer les différents événements qui se produisent au cours du processus de synchronisation de la réplication de fusion.

Cette API est la DLL “Microsoft.SqlServer.Replication.BusinessLogicSupport” qui se situe par défaut dans le répertoire “C:\Program Files\Microsoft SQL Server\100\COM”.

D’une manière globale, voici quelles sont les étapes de cette personnalisation :

  1. On écrit et on génère une assembly .NET (DLL) qui implémente et “override” les méthodes nécessaires de la classe “BusinessLogicSupport.
  2. On inscrit la DLL créée sur le serveur de distribution.
  3. On déploie la DLL créée sur le serveur où l’agent de fusion s’exécute.
  4. On édite les propriétés d’un article pour choisir le résolveur de conflits que l’on vient de créer.

A titre d’exemple, nous allons imaginer que le système de réplication que nous avons implémenté met en jeu une table “CLIENT” dans laquelle nous stockons toutes les données relatives aux clients telles que les noms, prénoms, adresses, numéros de téléphone, etc…

Une application mobile, liée à une base mobile répliquée, permet de modifier l’adresse du client via un écran spécifique.

L’article “CLIENT” de notre solution de réplication peut donc être soumis à des éventuels conflits lors d’une synchronisation, si l’adresse d’un client particulier est modifiée à la fois sur le serveur et sur le terminal mobile synchronisant.

Nous allons également imaginer une règle simple à mettre en place en cas de conflit sur l’adresse d’un client :

“Si l’adresse du client modifiée par l'abonné est correctement formatée (numéro + rue + ville), on doit la garder! Sinon on doit systématiquement conserver celle modifiée par le publisher”.

Nous allons voir maintenant, étape par étape, comment nous devons procéder pour créer un résolveur de conflits qui applique cette règle !

 

Création d’une DLL de résolveur :

Pour commencer à écrire notre résolveur, nous allons partir d’un nouveau projet de type « Class library » dans Visual Studio. De cette manière, nous pourrons facilement compiler une DLL et non un fichier exécutable.image Avant toutes choses, il va être également nécessaire d’ajouter une référence à la DLL “Microsoft.SqlServer.Replication.BusinessLogicSupport”. Parcourez alors  le système de fichier pour trouver cette DLL située par défaut dans le répertoire “C:\Program Files\Microsoft SQL Server\100\COM”.

image

Pour commencer, nous allons ajouter les “using” nécessaires :

using System.Data;
using Microsoft.SqlServer.Replication.BusinessLogicSupport;

Puis, dans un second temps, nous allons spécifier que notre classe “ResolverPersoCLIENT” hérite de la classe “BusinessLogicModule”. L’héritage de la classe “BusinessLogicModule”  rend obligatoire l’implémentation de la propriété override “HandledChangeStates” puisque cette dernière est notée “virtuelle” dans la classe mère “BusinessLogicModule”.

Voici à quoi doit donc ressembler le squelette de départ de notre résolveur :

public class ResolverPersoCLIENT : BusinessLogicModule
{
    public ResolverPersoCLIENT()
    {
     }

    public override ChangeStates HandledChangeStates
    {
     }
}

 

En héritant de la classe “BusinessLogicModule”, il est possible de “overrider” de nombreuses méthodes afin d’implémenter une logique métier personnalisée sur les différents événements qui se produisent au cours du processus de synchronisation de la réplication de fusion.

L’ensemble des méthodes qu’il est possible de définir et de personnaliser sont les suivantes :

  • CommitHandler : Pour définir des traitements particuliers lors de la validation de données lors des synchros.
  • DeleteErrorHandler : Pour définir des traitements particuliers lorsqu’une erreur se produit suite à une instruction DELETE.
  • DeleteHandler : Pour définir des traitements particuliers lorsque des instructions DELETE sont exécutées par le processus de réplication.
  • InsertErrorHandler : Pour définir des traitements particuliers lorsqu’une erreur se produit suite à une instruction INSERT.
  • InsertHandler : Pour définir des traitements particuliers lorsque des instructions INSERT sont exécutées par le processus de réplication.
  • UpdateConflictsHandler : Pour définir des traitements particuliers lorsque des instructions UPDATE génèrent des conflits.
  • UpdateDeleteConflictHandler : Pour définir des traitements particuliers lorsque des instructions UPDATE entrent en conflit avec des instructions DELETE.
  • UpdateErrorHandler : Pour définir des traitements particuliers lorsqu’une erreur se produit suite à une instruction UPDATE.
  • UpdateHandler : Pour définir des traitements particuliers lorsque des instructions UPDATE sont exécutées par le processus de réplication.

De cette manière, nous sommes capables de réécrire complètement les logiques de traitements sur chacun des évènements qui apparaissent lors des phases de synchronisations ! Pour définir un traitement particulier, il suffit “d’overrider” la méthode liée à l’évènement concerné et de personnaliser le traitement en y implémentant son propre code.

Evidemment, nous ne sommes pas obligés de réécrire et de personnaliser l’entièreté de ces méthodes.

Dans notre exemple, ce qui nous intéresse est de proposer une règle particulière de résolution de conflits pour les conflits de type “update”.

C’est pourquoi, nous allons commencer par déclarer quels sont les types de modifications que nous allons gérer dans la propriété override “HandledChangeStates”. Il est obligatoire de spécifier ce que nous allons personnaliser et traiter pour pouvoir capturer correctement les évènements choisis lors des phases de synchronisations.

Nous allons donc spécifier que nous nous intéressons spécifiquement aux évènements de conflits de mises à jour à l’aide de l’énumération “ChangeStates” qui contient l’ensemble des types d’évènements :

public override ChangeStates HandledChangeStates
{
      get
      {
            return ChangeStates.UpdateConflicts;
       }
}

Comme nous avons choisi d’intercepter et de personnaliser les modifications de type “conflits de mises à jour”, nous devons à présent “overrider” la méthode “UpdateConflictsHandler” qui va nous permettre de spécifier les règles particulières à appliquer lorsque ce type de conflit est levé.

La méthode override “UpdateConflictsHandler” doit respecter une signature particulière. Les arguments de la méthode correspondent, entre autres, aux éléments importants ci-dessous :

  • Un “dataset” qui correspond aux données modifiées par le publisher
  • Un “dataset” qui correspond aux données modifiées par le subscriber (abonné)
  • Une référence sur un “dataset” personnalisé qui va permettre de personnaliser l’ensemble des données, suite à un conflit particulier, avant de les renvoyer.
public override ActionOnUpdateConflict UpdateConflictsHandler(DataSet publisherDataSet,
            DataSet subscriberDataSet, ref DataSet customDataSet, 
            ref ConflictLogType conflictLogType, ref string customConflictMessage,
            ref int historyLogLevel, ref string historyLogMessage)
        {
            // ON DEFINIT ICI LES TRAITEMENTS PERSONNALISES !
        }

A présent, à l’intérieur de cette méthode, nous devons écrire la logique de traitement associée. Pour rappel, nous devons appliquer la règle suivante :

“Si l’adresse du client modifiée par l'abonné est correctement formatée (numéro + rue + ville), on doit la garder! Sinon on doit systématiquement conserver celle modifiée par le publisher”.

Le code ressemblera donc à ceci :

public override ActionOnUpdateConflict UpdateConflictsHandler(DataSet publisherDataSet,
            DataSet subscriberDataSet, ref DataSet customDataSet, 
            ref ConflictLogType conflictLogType, ref string customConflictMessage,
            ref int historyLogLevel, ref string historyLogMessage)
{
      // On récupère l'adresse du client modifiée par l'abonné:
      string adresseAbo = subscriberDataSet.Tables[0].Rows[0]["Adresse"].ToString();

      // Si le format de l'adresse, modifié par l'abonné est correct, on la garde, 
      // sinon, on prend systématiquement celle modifiée par le publisher !
      if (FormatCorrect(adresseAbo) == true)
      {
           // On copie les valeurs du dataset publisher dans un dataset personnalisable:
           customDataSet = publisherDataSet.Copy();
           // On prend en compte l'adresse modifiée par l'abonné:
           customDataSet.Tables[0].Rows[0]["Adresse"] = adresseAbo;
           // Renvoi d'un message personnalisé:
           customConflictMessage = "Conflit résolu et traité comme il se doit !";
           // On valide les modifications effectuées:
           return ActionOnUpdateConflict.AcceptCustomConflictData;
       }
       else
       {
           // On valide toutes les modifications du publisher:
           return ActionOnUpdateConflict.AcceptPublisherData;
       }
}

Explications :

  • Dans un premier temps, nous récupérons l’adresse conflictuelle à partir du dataset de l’abonné.
  • Ensuite, nous vérifions si son format est correct.
  • Si le format de l’adresse est correct, on spécifie explicitement que l’on garde les modifications effectuées par le publisher.
  • Sinon, on copie les données modifiées du publisher dans le dataset personnalisé et on modifie juste la valeur du champ correspondant à l’adresse par la valeur récupérée de l’abonné. Enfin, on précise explicitement que l’on valide les données contenues dans le dataset personnalisé.
  • A noter que tous les conflits d'un article qui ne sont pas gérés explicitement par la nouvelle logique métier personnalisée seront gérés par le programme de résolution par défaut pour l'article.

Nous pouvons à présent compiler notre DLL et l’inscrire au niveau du serveur de distribution.

 

Inscription de la DLL créée sur le serveur de distribution :

La DLL correspondante au résolveur .NET personnalisé étant écrite et générée, nous pouvons à présent la référencer sur le serveur de distribution.

Cette opération peut être également effectuée en C# grâce à l’API “Microsoft.SqlServer.Replication.BusinessLogicSupport”, cependant il est plus facile d’exécuter une procédure stockée prévue à cet effet, directement sur la base de distribution.

L’exécution de la procédure ci-dessous, avec les bons paramètres, permet d’inscrire la DLL sur le serveur de distribution :

USE distribution
GO
EXEC sp_registercustomresolver
@article_resolver = 'Resolveur Perso pour larticle CLIENT',
@is_dotnet_assembly = 'true',
@dotnet_assembly_name = 
'C:\DATA\Temp\ResolverPerso\ResolverPerso\bin\Debug\ResolverPerso.dll',
@dotnet_class_name = 'ResolverPerso.ResolverPersoCLIENT'
GO

Il est important de spécifier les bons paramètres :

  • @article_resolver : correspond à la description du résolveur.
  • @is_dotnet_assembly : doit être à “true” si la DLL a été écrite en .NET.
  • @dotnet_assembly_name : correspond au chemin exact de la DLL résolveur à inscrire.
  • @dotnet_class_name : correspond au nom de la classe qui comporte le code personnalisé du résolveur.

L’exécution de cette procédure stockée avec les bons paramètres permet donc d’inscrire convenablement le résolveur personnalisé sur le serveur de distribution.

 

Affectation du nouveau résolveur sur un article particulier :

La dernière étape consiste donc à affecter ce nouveau résolveur à un ou plusieurs articles de la publication.

Pour cela, il suffit de retourner une nouvelle fois sur les propriétés d’un article et de se positionner sur l’onglet “Resolver”.

Le nouveau résolveur personnalisé qui vient d’être créé doit normalement apparaitre dans la liste “custom resolver” :

image

Il suffit de le sélectionner pour l’article “CLIENT” puis de cliquer sur “OK” pour le prendre en compte.

Pi-R.

Réplication SQL pour les projets mobiles : Gestion des conflits et résolveurs - PARTIE 1/2 (Généralités)

Introduction :

La réplication SQL de fusion permet de disposer de plusieurs bases répliquées sur les différents terminaux mobiles. Ces différentes bases SQL Server Compact sont toutes des répliquas d’une seule et même base centrale SQL Server. La réplication permet à toutes ces différentes bases (base serveur et bases mobiles) de mettre à jour leurs données de manière autonome.

Dans certaines typologies, la réplication peut donc permettre à plusieurs bases d’effectuer des changements sur la même ligne de données. Lorsque les terminaux concernés synchroniseront avec la base centrale, il y aura donc un ou plusieurs cas de conflits : quelles mises à jour vont être prises en compte ? Quelle ligne modifiée sera la “ligne gagnante” d’un conflit particulier ?

 

Détection de conflits et niveaux de suivi :

Qu'une modification de données provoque ou non un conflit dépend du type de suivi de conflit qui est défini pour chacun des articles (= tables).

Dans le cadre de la réplication de fusion pour SQL Server Compact, il existe deux types de suivi : le suivi au niveau de la ligne ou le suivi au niveau de la colonne. En fait, ce sont ces deux types de suivi qui permettent de déterminer comment les conflits sont détectés lors des phases de synchronisation.

En résumé, voici ce qu’il faut retenir :

  • Si le suivi des conflits s’effectue au niveau des colonnes, il y a conflit si des modifications sont apportées à la même colonne d'une même ligne sur plusieurs nœuds de réplication.
  • Si le suivi des conflits s’effectue au niveau des lignes, il y a conflit si des modifications sont apportées à des colonnes quelconques d'une même ligne sur plusieurs nœuds de réplication (les colonnes affectées dans les lignes correspondantes ne doivent pas forcément être identiques).

Remarques importantes :

  • Le suivi au niveau de la colonne réduit le nombre de conflits quand les applications mobiles permettent à différents utilisateurs de modifier les mêmes données.
  • Le suivi au niveau de la colonne est un bon moyen de réduire la quantité d'informations qui doivent être envoyées au serveur de publication au cours de la synchronisation, tandis que le suivi de niveau ligne nécessite moins de surcharge en matière de suivi, mais a besoin de plus de stockage pour effectuer le suivi des modifications.
  • Peu importe le type de suivi, la résolution du conflit sera identique : toute la ligne de données sera écrasée par les données du vainqueur du conflit.

C’est la sémantique des applicatifs qui détermine l’option de suivi à appliquer sur chacun des articles d’une publication. Dans le cas où la mise à jour de plusieurs champs s’effectue en même temps (ex : le même écran pour modifier l’ensemble des coordonnées d’un client), il est préférable d’utiliser le suivi par ligne. Si les mises à jour des champs se font de manière individuelle, le suivi au niveau de la colonne est le mieux adapté, et permettra d’éviter de provoquer des détections de conflits inutiles.

Une option de suivi est attribuée à un article particulier. Les articles d’une même publication peuvent avoir des options de suivi différentes selon les besoins.

L’option de suivi, à utiliser pour un article particulier, peut être définie facilement dans les propriétés des articles. Après avoir sélectionné l’article à éditer, il est question de renseigner le champ “Tracking level avec la valeur “Row-level tracking” (suivi de la ligne) ou “Column-level tracking” (suivi de la colonne) :

image

Résolution de conflits et résolveurs :

Un programme de résolution de conflits (ou “résolveur”) est associé à chacun des articles de la publication. Il peut s’agir du même programme, ou d’un programme différent selon les articles et les besoins.

Après la détection d’un conflit, le moteur de réplication lance le programme de résolution de conflits sélectionné sur l’article (ou celui par défaut) pour déterminer le “vainqueur du conflit”. La ligne gagnante est appliquée au serveur de publication et à l’abonné tandis que les données de la ligne perdante sont automatiquement stockées dans des tables systèmes.

Les conflits sont toujours résolus automatiquement par les différents résolveurs et immédiatement par l'agent de fusion.

Le résolveur par défaut :

Pour utiliser le résolveur de conflits pas défaut, il n’est pas nécessaire d’effectuer des manipulations particulières de paramétrage. En effet, le résolveur par défaut est automatiquement attribué à chacun des articles lors de leur création.

Le résolveur par défaut propose plusieurs méthodes de résolution des conflits qui sont généralement les mieux adaptées aux différentes applications :

  • Si un conflit se produit entre la base serveur et un abonné, la modification du serveur est conservée tandis que celle de l'abonné est annulée.
  • Si un conflit se produit entre deux abonnés, la modification provenant du premier abonné qui se synchronise avec le serveur est conservée tandis que celle provenant du second abonné est annulée.

Utiliser un autre résolveur :

SQL Server propose toute une liste de résolveurs prédéfinis qui ont chacun des propriétés différentes en ce qui concerne les résolutions de conflits.

Pour utiliser un autre résolveur sur un article particulier, il est d’abord nécessaire d’entrer dans l’édition des propriétés de cet article, et de sélectionner l’onglet “Resolver” :

image

Par défaut, le résolveur par défaut sera sélectionné (“Use the default resolver”). Sélectionnez donc la seconde option pour choisir un autre modèle de résolveur dans la liste (“Use a custom resolver”) :

image

Les méthodes proposées par chacun de ces autres résolveurs sont évidemment différentes et seront importantes pour déterminer la “ligne gagnante” de chaque conflit. Il est important d’effectuer un choix judicieux selon les besoins, car il en dépend de la conformité des données de la base.

Par exemple, en sélectionnant le résolveur “DATETIME (Earlier Wins)”, le résolveur considèrera que la ligne modifiée en premier (par rapport à un champ de type DateTime qui doit être précisé) sera systématiquement la gagnante d’un conflit.

Peu importe le résolveur utilisé, après résolution d’un conflit, l’agent de fusion journalise systématiquement les données du conflit en fonction du type de conflit dans des tables systèmes, et par article.

Les données de ces tables de réplication sont purgées au terme de la période de rétention.

 

Rédaction d’un résolveur personnalisé en C# :

A suivre…

 

Pi-R.

News 21/10/2010 : C'est parti pour le Windows Phone !

image

Ce 21/10/2010 représente la date officielle de lancement du Windows Phone en Europe. La nouvelle plateforme mobile de Microsoft est aujourd’hui disponible chez tous les opérateurs français !

Microsoft revient dans la course des OS Smartphones, de quoi faire trembler ses concurrents…

Le nouvel OS mobile proposé par Microsoft est complètement différent de ses prédécesseurs Windows Mobile 6.x. En effet, tout a été retravaillé pour que la plateforme soit la mieux adaptée au marché actuel et peut de cette manière rivaliser face à la concurrence.

Windows Mobile 5/6.x restera pour le moment l’OS mobile de Microsoft installé sur les terminaux professionnels (PDA durcis) et utilisé comme base de la mobilité pour les entreprises.

Quoi qu’il en soit : Bienvenue au Windows Phone !

 

PS : De mon côté, je tacherai de publier sur mon blog de plus en plus d’articles concernant le développement d’applications sous Windows Phone.

N’hésitez pas à réagir également face à cette actualité en déposant vos éventuels commentaires.

 

Pi-R.

Développement Windows Mobile 6.x : Niveau de batterie / Alimentation / Reset / Mise en veille / Connectivité... Les états du terminal en C#

Introduction :

Afficher le niveau de la batterie du terminal dans une barre d’état, détecter la connectivité de l’appareil ou encore sa mise sous tension, effectuer un reset automatique du terminal… De nombreuses fonctionnalités qu’il est intéressant d’implémenter dans une application mobile professionnelle…

Malheureusement, le .NET Compact Framework est très limité en ce qui concerne la gestion du système et la récupération des états du terminal mobile. Pourtant ces différents aspects sont essentiels dans le cadre du développement d’une application mobile professionnelle…

C’est pourquoi, j’ai décidé de réunir dans cet article quelques méthodes et astuces qui permettent d’effectuer ces actions en C#, notamment en utilisant la technique du PInvoke, ou encore à l’aide du SDK Windows Mobile.

Liens :

 

Etats de la batterie & Alimentation :

Il peut s’avérer très intéressant de récupérer le niveau de la batterie à un instant t; notamment pour l’afficher sous la forme d’un pourcentage dans une barre d’état. De la même façon, et pour des besoins spécifiques, il peut être intéressant de savoir si le terminal est sous tension (en charge) avant de lancer une action particulière qui nécessite que le terminal reste alimenté.

La récupération des états de la batterie est possible grâce à la classe que je vous livre ci-dessous et qui utilise la technique du PInvoke :

using System.Runtime.InteropServices;

namespace TestPower
{
    public class BatteryState
    {
        public const byte AC_LINE_OFFLINE = 0x00;
        public const byte AC_LINE_ONLINE = 0x01;
        public const byte AC_LINE_BACKUP_POWER = 0x02;
        public const byte AC_LINE_UNKNOWN = 0xFF;
        public const byte BATTERY_FLAG_HIGH = 0x01;
        public const byte BATTERY_FLAG_LOW = 0x02;
        public const byte BATTERY_FLAG_CRITICAL = 0x04;
        public const byte BATTERY_FLAG_CHARGING = 0x08;
        public const byte BATTERY_FLAG_NO_BATTERY = 0x80;
        public const byte BATTERY_FLAG_UNKNOWN = 0xFF;
        public const byte BATTERY_PERCENTAGE_UNKNOWN = 0xFF;
        public const uint BATTERY_LIFE_UNKNOWN = 0xFFFFFFFF;
        public const byte BATTERY_CHEMISTRY_ALKALINE = 0x01;
        public const byte BATTERY_CHEMISTRY_NICD = 0x02;
        public const byte BATTERY_CHEMISTRY_NIMH = 0x03;
        public const byte BATTERY_CHEMISTRY_LION = 0x04;
        public const byte BATTERY_CHEMISTRY_LIPOLY = 0x05;
        public const byte BATTERY_CHEMISTRY_UNKNOWN = 0xFF;

        public byte ACLineStatus; 
        public byte BatteryFlag; 
        public byte BatteryLifePercent; 
        public byte Reserved1;
        public uint BatteryLifeTime;
        public uint BatteryFullLifeTime;
        public byte Reserved2;
        public byte BackupBatteryFlag;
        public byte BackupBatteryLifePercent;
        public byte Reserved3;
        public uint BackupBatteryLifeTime;
        public uint BackupBatteryFullLifeTime;
        public uint BatteryVoltage;
        public uint BatteryCurrent;
        public uint BatteryAverageCurrent;
        public uint BatteryAverageInterval;
        public uint BatterymAHourConsumed;
        public uint BatteryTemperature;
        public uint BackupBatteryVoltage;
        public byte BatteryChemistry;

        private BatteryState()
        {
            GetSystemPowerStatusEx(this, false);
        }

        public static BatteryState CurrentState
        {
            get
            {
                return new BatteryState();
            }
        }
        
        [DllImport("coredll")]
        private static extern uint 
            GetSystemPowerStatusEx(BatteryState lpSystemPowerStatus, bool fUpdate);
    }
}

La classe “BatteryState” se base sur la fonction “GetSystemPowerStatusEx” de la DLL système “coredll” pour récupérer les différents états de la batterie à un instant t. Grâce au PInvoke et à la méthode “GetSystemPowerStatusEx”, nous sommes capables de récupérer des informations telles que le niveau de charge de la batterie, ou simplement savoir si le terminal est actuellement en charge.

Pour utiliser cette classe: rien de plus simple. A n’importe quel endroit de votre code .NET (dans une Form par exemple), il vous suffit de faire appel à la propriété statique “CurrentState” de “BatteryState” pour récupérer les informations batterie à un instant t.

Exemples :

- Pour récupérer le niveau de charge actuel de la batterie sous la forme d’un pourcentage :

int niveauBatterie = BatteryState.CurrentState.BatteryLifePercent;

- Pour savoir si le terminal portable est actuellement alimenté :

bool enCharge = BatteryState.CurrentState.ACLineStatus == 1;

La valeur de la variable “ACLineStatus” vaut 1 si le terminal est en charge, 0 sinon.

Plus d’informations sur les états récupérés par la fonction système GetSystemPowerStatusEx ici : http://msdn.microsoft.com/fr-fr/library/ms940384.aspx 

 

Empêcher la mise en veille du terminal :

Nous allons voir ici comment nous pouvons “empêcher” le terminal portable de se mettre en veille en utilisant une fois de plus le PInvoke, et notamment la fonction “SystemIdleTimerReset”.

En fait, la fonction “SystemIdleTimerReset” de la DLL système “coredll” ne permet pas en réalité d’annuler la mise en veille du terminal, mais plus particulièrement de remettre à zéro le timer de mise en veille, c’est à dire de “réveiller” le terminal. Le fait d’appeler cette fonction de manière régulière est donc un bon moyen pour empêcher réellement le terminal de se mettre en veille.

L’idée de la classe “ResetIdleTimerThread”, présentée ci-dessous, est de centraliser l’appel à cette fonction dans un process en arrière-plan (Thread). La fonction “SystemIdleTimerReset” est ainsi appelée indéfiniment dans la boucle du thread, toutes les 20 secondes par exemple. De cette façon, nous effectuons un reset répétitif du timer de mise en veille du terminal.

Les méthodes “StartIdleTimerReset” et “StopIdleTimerReset”de la classe “ResetIdleTimerThread” permettent de démarrer ou d’arrêter l’exécution du thread, c’est à dire d’annuler ou non la mise en veille du terminal.

using System.Runtime.InteropServices;

namespace TestPower
{
    public class ResetIdleTimerThread
    {
        [DllImport("coredll")]
        private static extern void SystemIdleTimerReset();

        private System.Threading.Thread _threadReset = null;
        private System.Threading.ThreadStart _startReset = null;
        private bool _isRunning = false;

        public void StartIdleTimerReset()
        {
            if (!_isRunning)
            {
                _isRunning = true;
                _startReset = new System.Threading.ThreadStart(resetIdleTime);
                _threadReset = new System.Threading.Thread(_startReset);
                _threadReset.Start();
            }
        }

        public void StopIdleTimerReset()
        {
            if (_isRunning)
            {
                _isRunning = false;
                try
                {
                    _threadReset.Abort();
                }
                catch { }
            }
        }
        
        private void resetIdleTime()
        {
            while (_isRunning)
            {
                SystemIdleTimerReset();

                System.Threading.Thread.Sleep(20000);
            }
        }
    }
}

Si vous souhaitez que le terminal mobile ne se mette jamais en veille durant toute l’exécution de votre application, il vous suffit de faire appel à la classe “ResetIdleTimerThread”  et à sa méthode “StartIdleTimerReset” dans le constructeur de la Form principale (ou du moins la Form initiale de l’application), et mettre fin au thread en appelant la méthode “StopIdleTimerReset” lorsque l’utilisateur quitte l’application.

Exemple :

public partial class FormMain : Form
{
   private ResetIdleTimerThread _resetTimerThread = null;

   public FormMain()
   {
      InitializeComponent();

      _resetTimerThread = new ResetIdleTimerThread();
      _resetTimerThread.StartIdleTimerReset();
   }

   // (...)

   protected override void OnClosing(CancelEventArgs e)
   {
       base.OnClosing(e);
       if (_resetTimerThread != null)
           _resetTimerThread.StopIdleTimerReset();
   }
}

 

Reset du terminal en C# :

Pour des besoins particuliers, notamment pour des process de mises à jour logicielles, il peut parfois s’avérer pratique de programmer le reset du terminal au sein d’une application mobile .NET.

Je vous présente encore ci-dessous une classe qui vous permettra d’ordonner un reset du terminal.

La classe “SystemReset” s’appuie sur la fonction “KernelIoControl” de la DLL système “coredll” qui permet de provoquer un reset immédiat de l’appareil :

using System;
using System.Runtime.InteropServices;

namespace TestPower
{
    class SystemReset
    {
        private const uint FILE_DEVICE_HAL = 0x00000101;
        private const uint FILE_DEVICE_CONSOLE = 0x00000102;
        private const uint FILE_DEVICE_PSL = 0x00000103;
        private const uint METHOD_BUFFERED = 0;
        private const uint METHOD_IN_DIRECT = 1;
        private const uint METHOD_OUT_DIRECT = 2;
        private const uint METHOD_NEITHER = 3;
        private const uint FILE_ANY_ACCESS = 0;
        private const uint FILE_READ_ACCESS = 0x0001;
        private const uint FILE_WRITE_ACCESS = 0x0002;

        private static uint CTL_CODE(uint DeviceType,
            uint Function, uint Method, uint Access)
        {
            return ((DeviceType << 16) | (Access << 14) | (Function << 2) | Method);
        }

        [DllImport("Coredll.dll")]
        private extern static uint KernelIoControl
        (
            uint dwIoControlCode,
            IntPtr lpInBuf,
            uint nInBufSize,
            IntPtr lpOutBuf,
            uint nOutBufSize,
            ref uint lpBytesReturned
        );
        
        public static void ResetDevice()
        {
            uint bytesReturned = 0;
            uint IOCTL_HAL_REBOOT = CTL_CODE(FILE_DEVICE_HAL, 15,
                METHOD_BUFFERED, FILE_ANY_ACCESS);
            KernelIoControl(IOCTL_HAL_REBOOT, IntPtr.Zero, 0,
                IntPtr.Zero, 0, ref bytesReturned);
        }
    }
}

Pour effectuer un reset de l’appareil à un endroit précis de votre application mobile, il vous suffit alors d’écrire tout simplement la ligne de code suivante :

SystemReset.ResetDevice();

Bien sûr, libre à vous d’afficher un message de confirmation à l’utilisateur avant de provoquer un redémarrage brutal du terminal.

 

Classe SystemState  - Connectivité du terminal :

Enfin, dans ce paragraphe nous allons voir comment nous pouvons facilement détecter la connectivité du terminal mobile. Pour ce faire, nous n’allons plus utiliser le PInvoke, mais plutôt le SDK de Windows Mobile et les librairies qu’il propose.

Vous pouvez télécharger le SDK Windows Mobile 6 en cliquant sur ce lien :

http://www.microsoft.com/downloads/en/details.aspx?FamilyID=06111a3a-a651-4745-88ef-3d48091a390b&displaylang=en . Vous pouvez ainsi télécharger la version “professional” pour les pockets PC, ou la version “standard” pour les smartphones.

La classe “SystemState”, présente dans le namespace “Microsoft.WindowsMobile.Status” du SDK Windows Mobile, peut être utilisée pour récupérer très facilement toute une liste d’informations sur le système.

Les propriétés statiques de la classe permettent de récupérer des informations comme le journal d’appels du téléphone, les rendez-vous de l’agenda, les informations sur le propriétaire,… et également de nombreuses informations sur la connectivité du terminal.

Liste complète des propriétés de la classe “SystemState” :

http://msdn.microsoft.com/en-us/library/microsoft.windowsmobile.status.systemstate_properties.aspx

Pour utiliser la classe “SystemState” et l’ensemble de ses propriétés, vous devez dans un premier temps ajouter 2 références dans votre solution Visual Studio :

imageExemples :

- Pour savoir si le terminal est actuellement posé sur une station d’accueil (cradle):

bool cradle = SystemState.CradlePresent;

- Pour savoir si le terminal dispose actuellement d’une couverture GPRS :

bool grps = SystemState.PhoneGprsCoverage;

- Pour savoir si le terminal est actuellement connecté à un réseau :

bool connected = SystemState.ConnectionsCount > 0;

- Pour savoir si le terminal est actuellement connecté en Ethernet :

bool onEthernet = SystemState.ConnectionsNetworkCount > 0;

OU

bool onEthernet = SystemState.CradlePresent && SystemState.ConnectionsCount > 0;

- Pour savoir si une connection Bluetooth est établie :

bool bluetooth = SystemState.ConnectionsBluetoothCount > 0;

- Etc…

 

Remarque :

A noter que vous pouvez également “surveiller” facilement les changements d’états en instanciant la classe “SystemState” et en utilisant “SystemProperty” pour spécifier la propriété à surveiller :

private SystemState _systemState;

private void RunSystemStateMonitoring()
{
     _systemState = new SystemState(SystemProperty.ConnectionsCount);
     _systemState.Changed += new ChangeEventHandler(_systemState_Changed);
}

void _systemState_Changed(object sender, ChangeEventArgs args)
{
     if ((int)args.NewValue == 1)
     {
          // Le terminal vient de se connecter au réseau !
     }
}

L’évènement “Changed” est levé lorsque la valeur de la propriété spécifiée est modifiée. De cette manière, nous pouvons automatiquement lancer une action lorsque le terminal se connecte à un réseau (exemple ci-dessus).

 

Voilà, c’est tout pour le moment… ;-)

J’espère que toutes ces astuces vous seront utiles…

Encore une fois, n’hésitez pas à me laisser vos commentaires et/ou questions !

Pi-R.

Développement Windows Mobile 6.x : Comment obtenir le chemin de l'assembly en cours d'exécution ?

Un petit post, simple, concis, et fort utile lorsque l’on développe une application sous Windows Mobile 6.x.

Vous avez peut-être l’habitude d’utiliser la propriété “Environment.CurrentDirectory” pour récupérer le path de l’exécutable en .NET…

Sachez que cette propriété n’est pas prise en charge dans le .NET Compact Framework !

Pour récupérer le path de l’assembly exécutée dans une application Windows Mobile, il faut utiliser la “réflexion”.

 

La ligne de code ci-dessous permet de récupérer le chemin de l’assembly :

string path = System.IO.Path.GetDirectoryName(System.Reflection.
                    Assembly.GetExecutingAssembly().GetName().CodeBase) + @"\";

… Et celle-ci permet de récupérer la version de l’assembly :

string version = System.Reflection.Assembly.GetExecutingAssembly().
                    GetName().Version.ToString();

 

Puisse ce petit post vous être utile un jour ;-)

Pi-R.

Developpement Windows Mobile 6.x : Comment interagir avec un fichier de configuration app.config ?

Problématique :

Le .NET Compact Framework ne prend pas en charge les fichiers de configuration XML (app.config) comme dans le Framework .NET classique.

En effet, pour une application mobile, il est impossible d’ajouter un item “fichier de configuration” à une solution Visual Studio, et encore moins d’utiliser l’espace de noms “System.Configuration”.

Par exemple, l’instruction ci-dessous, que vous avez peut-être l’habitude d’écrire pour les autres types de développements, n’existe pas en Compact Framework !!

string value = ConfigurationManager.AppSettings["key"];

Pourtant, l’utilisation de fichiers app.config pour la mobilité pourrait présenter également un grand intérêt. Il pourrait s’avérer utile d’y stocker de nombreux paramètres tels que :

  • Chaines de connexion
  • Chemin de répertoires spécifiques au programme
  • Valeurs à utiliser pour les synchronisations de données (Nom du serveur, réplication SQL, FTP, etc…)
  • Paramètres GPRS
  • Valeurs par défaut utilisées par l’application
  • Etc…

Pas de panique… Cet article démontre comment utiliser des fichiers de config XML sur PDA, comme avec les autres types d’applications…

 

Ajouter un fichier de configuration au projet mobile :

Dans votre projet “Smart Device” de Visual Studio, commencez par ajouter un item de type “XML file”. Nommez ce fichier “app.config” :

appconfig

Ensuite, commencez à écrire les clefs et les valeurs de paramètres à l’intérieur de ce fichier en prenant soin de bien respecter la structure XML ci-dessous :

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>

    <add key="key1"    value="value1"/>
    <add key="key2"    value="value2"/>
    <add key="key3"    value="value3"/>
    <!-- ETC... -->

  </appSettings>
</configuration>

Enfin, avant de déployer la solution, assurez-vous que la valeur de la propriété “Copy to Output Directory” est bien “Copy if newer” ou “Copy Always” pour le fichier XML :

copyifnever

En déployant l’application sur un terminal mobile, vous remarquerez que le fichier XML réagit comme un fichier de configuration ordinaire, c’est à dire qu’il se copie dans le répertoire d’installation de l’application et se renomme automatiquement pour porter le nom de l’exécutable suffixé par “.config” :

dploy

Il ne reste plus qu’à trouver un moyen d’interagir avec ce fichier pour pouvoir lire et récupérer les valeurs de chacune des clefs…

 

Interagir avec un fichier de configuration XML - classe “ConfigurationManager” :

Le .NET Compact Framework ne prend pas en charge l’espace de noms “System.Configuration”. Aussi, il est donc nécessaire d’écrire “à la main” le code qui permettra de récupérer la valeur d’une clef à partir du format XML.

La classe “ConfigurationManager”, exposée ci-dessous répond parfaitement à nos besoins en offrant la possibilité de récupérer simplement une valeur de configuration !

La méthode “GetKey” effectue un parsing du flux XML et cherche la clef précise spécifiée en argument. Lorsque le parsing XML a bien trouvé la clef, la méthode retourne la valeur associée sous le bon type, celui qui a été spécifié lors de l’appel à la méthode.

Merci à Dorian Lamandé d’avoir travaillé sur le sujet avec moi et d’avoir participé à l’amélioration de cette classe :

using System;
using System.IO;
using System.Xml;

namespace TestAppConfig
{
    public class ConfigurationManager
   
{
        private static XmlTextReader GetReaderConfigFile()
        {
            // Récupération du chemin de l'éxécutable:
           
string appPath = System.IO.Path.GetFullPath(System.Reflection.
                Assembly.GetExecutingAssembly().GetName().CodeBase) + ".config";
            // Ouverture du fichier de config en lecture:
           
FileStream file = new FileStream(appPath, FileMode.Open,
                FileAccess.Read, FileShare.Read);

            return new XmlTextReader(file);
        }

        public static T GetKey<T>(string key)
        {
            XmlTextReader xmlRead = null;
            try
           
{
                // Récupère le flux XML du fichier de config:
               
xmlRead = GetReaderConfigFile();
                // Recherche la clef dans le fichier de config:
               
while (xmlRead.Read())
                {
                    XmlNodeType nodeType = xmlRead.NodeType;

                    if (nodeType == XmlNodeType.Element)
                    {
                        if (xmlRead.Name.Equals("add") &&
                            xmlRead.GetAttribute("key").Equals(key))
                        {
                            // Retourne la valeur de la clef sous le bon type:
                           
return (T)Convert.ChangeType(xmlRead.GetAttribute("value"),
                                typeof(T), null);
                        }
                    }
                }
            }
            catch (XmlException ex)
            {
                throw ex;
            }
            finally
           
{
                if (xmlRead != null)
                    xmlRead.Close();
            }

            return default(T);
        }
    }
}

Pour pouvoir récupérer des valeurs de configuration dans le code d’une application mobile, il est donc nécessaire de rajouter cette classe à la solution Visual Studio, puis d’appeler la méthode “GetKey” en précisant le nom de la clef et le type de la valeur à retourner.

Exemple :

// Récupération d'une valeur de type 'string' à partir du fichier de config:
string value1 = ConfigurationManager.GetKey<string>("key1");

// Récupération d'une valeur de type 'int' à partir du fichier de config:
int value2 = ConfigurationManager.GetKey<int>("key2");

J’espère que cette classe vous sera très utile pour vos futurs projets Windows Mobile 6.x ;-)

Pi-R.

Utilisation de la réplication SQL dans le code .NET d'une application mobile - Implémentation & Conseils : PARTIE 3/3 (Réinitialisations d'abonnements)

Ce post représente la dernière partie de notre article sur l’utilisation de la réplication SQL dans le code .NET d’une application mobile.

Précédemment, nous avons vu comment utiliser les méthodes Synchronize (partie 1) et BeginSynchronize (partie 2), pour effectuer une synchronisation de données entre un terminal mobile et un serveur.

Aujourd’hui, nous allons voir comment gérer les réinitialisations d’abonnements…

 

Un abonnement a besoin de subir une réinitialisation dans les cas ci-dessous :

  • Lorsque le schéma de la publication (articles, filtres) a été modifié et que les abonnements ont été notés “à réinitialiser”.
  • Lorsqu’un abonnement expire, suite au dépassement du délai de rétention.

La réinitialisation d’un abonnement sur le terminal est assurée grâce à la méthode ReinitializeSubscription de la classe SqlCeReplication.

La nécessité de réinitialiser un abonnement peut être détectée en attrapant une erreur particulière (exception) dans la clause “Catch” lors du processus de réplication.

Le code de l’erreur native de l’exception est alors le code 28587.

Si cette erreur particulière est attrapée, cela signifie que l’abonnement a expiré, et la méthode ReinitializeSubscription doit immédiatement être appelée :

private void btnSynchro_Click(object sender, EventArgs e)
{
     SqlCeReplication repl = null;
     try
     {
          repl = new SqlCeReplication();

          // (...)
          // (Définition des propriétés de l'objet repl)
          // (...)

          repl.Synchronize();
      }
      catch (SqlCeException ex)
      {
          switch (ex.NativeError)
          {
              case 28587:
                  repl.ReinitializeSubscription(true);
                  repl.Synchronize();
                  break;
              default:
                  throw ex;
           }
      }
      finally
      {
          if (repl != null)
              repl.Dispose();
      }
 }

 

Remarque importante :

Lors de la phase de réinitialisation, l’abonné redescend systématiquement l’ensemble des données pour reconstruire entièrement sa base mobile. Il s’agit d’une opération très couteuse en terme de volumétrie, d’où l’importance d’éviter les modifications de schéma sur la base serveur et de définir une période de rétention correcte.

 

Pi-R.

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 :

image

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.

Utilisation de la réplication SQL dans le code .NET d'une application mobile - Implémentation & Conseils : PARTIE 1/3 (classe SqlCeReplication & première synchro)

J’ai décidé de laisser de coté mes articles “théoriques” sur la réplication de fusion avec SQL Server Compact, pour laisser un peu plus de place à la pratique.

Voyons donc maintenant comment tout cela se met en action dans le code .NET d’une application mobile !

Dans le .NET Compact Framework, c’est la classe SqlCeReplication, de l’espace de nom System.Data.SqlServerCe qui permet de prendre en charge la réplication SQL.

Dans cette première partie, nous allons voir comment s’utilisent les propriétés et les méthodes de la classe SqlCeReplication. Nous allons étudier, étape par étape, la construction d’un code C# simple pour effectuer une synchronisation de données dans une application Windows Mobile.

 

Utilisation de la classe SqlCeReplication :

Partons d’une simple application, qui ne comporte qu’un seul écran sur lequel nous avons placé un bouton “SYNCHRO” qui va nous permettre de lancer la synchronisation :

imagePour pouvoir utiliser la réplication SQL et plus particulièrement la classe SqlCeReplication, il est d’abord nécessaire de rajouter une référence à l’assembly System.Data.SqlServerCe :

image Dans le code-behind de l’unique “form”, nous allons commencer par rajouter un “using” à l’espace de nom “System.Data.SqlServerCe” :

using System.Data.SqlServerCe;

Ensuite, nous allons définir une constante qui correspond au nom de la base mobile que nous allons créer par réplication :

private const string _dbName = "DBmob.sdf";

Dans le code de l’évènement “Click” associé au bouton synchro, nous allons créer un objet “repl” de type SqlCeReplication et l’instancier à l’intérieur d’une clause “try-catch-finally”.

Le code C# ci-dessous correspond au squelette de départ de notre exemple :

using System;
using System.Data.SqlServerCe;
using System.Windows.Forms;

namespace TestReplication1
{
    public partial class Form1 : Form
    {
        private const string _dbName = "DBmob.sdf";

        public Form1()
        {
            InitializeComponent();
        }

        private void btnSynchro_Click(object sender, EventArgs e)
        {
            SqlCeReplication repl = null;
            try
            {
                repl = new SqlCeReplication();
            }
            catch (SqlCeException ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                if (repl != null)
                    repl.Dispose();
            }

        }
    }
}

Remarque :

N’oubliez pas d’attraper les éventuelles SqlCeException dans la clause “catch” et de détruire l’objet “repl” dans la clause “finally”.

 

Définition des propriétés :

La classe SqlCeReplication possède de nombreuses propriétés qu’il est important de bien définir selon l’environnement utilisé (web synchro, Publisher, Distributor, etc…).

La définition de l’ensemble de ces propriétés n’est pas obligatoire et dépend entre autre du type de sécurité utilisée et de la configuration du distributor. Certaines propriétés, quant à elles, doivent obligatoirement être définies pour pouvoir effectuer une synchronisation, c’est le cas par exemple des propriétés Subscriber, Publication, InternetUrl, etc.…

Description de certaines propriétés :

  • InternetUrl: URL de l’agent de réplication exposé sur IIS.
  • InternetLogin: Login du compte utilisateur utilisé pour la connexion au répertoire virtuel IIS.
  • InternetPassword: Mot de passe du compte utilisateur utilisé pour la connexion au répertoire virtuel IIS.
  • Publisher: Nom de l’instance SQL sur laquelle tourne la base publisher.
  • PublisherSecurityMode: Type de sécurité utilisée pour la connexion à la base publisher (NT ou DB selon si la connexion à la base publisher s’effectue via un compte Windows ou un compte SQL Server).
  • PublisherLogin: Login du compte utilisé pour la connexion au publisher (dans le cas d’un type de sécurité DB).
  • PublisherPassword: Mot de passe du compte utilisé pour la connexion au publisher (dans le cas d’un type de sécurité DB).
  • PublisherDatabase: Base de données publisher à répliquer.
  • Publication: Nom de la publication à utiliser.
  • Distributor: Nom de l’instance SQL sur laquelle tourne la base de distribution (inutile de préciser dans le cas où le publisher et le distributor sont sur la même machine).
  • DistributorLogin: Login du compte utilisé pour la connexion à la base de distribution (inutile de préciser dans le cas où le publisher et le distributor sont sur la même machine).
  • DistributorPassword: Mot de passe du compte utilisé pour la connexion à la base de distribution (inutile de préciser dans le cas où le publisher et le distributor sont sur la même machine).
  • SubscriberConnectionString: Chaine de connexion à la base de données mobile répliquée.
  • Subscriber: Nom de l’abonnement.
  • HostName: valeur récupérée pour la fonction de filtrage HOST_NAME().
  • CompressionLevel: Cette propriété permet de personnaliser la compression de données. Elle peut prendre une valeur comprise entre 0 et 6. La valeur 0 désactive toute compression.  La valeur par défaut est 1.

Conseil :

En spécifiant une valeur faible pour la propriété CompressionLevel, les données transmises ne seront pas fortement compressées, ce qui rallonge donc la durée de transmission. Par contre, plus la valeur est élevée, plus le temps des traitements de compression sera long sur le serveur, mais la durée de transfert sera d’autant plus courte. Il est donc question de trouver un juste milieu par rapport aux besoins. Par exemple, si le coût des données transmises est important pour le projet (forfait opérateur GPRS), il sera peut-être nécessaire  d’augmenter la compression de données.

Astuce :

En ce qui concerne la chaine de connexion de l’abonnement SubscriberConnectionString, il faut savoir qu’il est possible de rajouter la clause “Max Database Size=X” afin de repousser la limite de taille définie par défaut pour les bases mobiles répliquées. En effet, par défaut la limite de taille de la base répliquée est fixée à 128Mo. Par exemple, en précisant “Max Database Size = 1000” dans la chaine de connexion, la base répliquée pourra atteindre une taille de 1Go. (La limite maximale réelle étant de 4 Go pour SQL Server Compact 3.5).

Procédure :

Juste après l’instanciation de l’objet “repl”, nous allons définir l’ensemble de ses propriétés de telle manière à pouvoir utiliser correctement une publication nommée “Pub” qui a été construite et configurée sur une base de données “BASE”.

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 = "PDA1";
repl.SubscriberConnectionString = @"Data Source=" + _dbName
                      + ";Max Database Size=1000;Default Lock Escalation =100;";

Dans l’exemple ci-dessus, nous avons donc précisé les paramètres de connexion au serveur ISS (dont l’URL de la DLL de réplication), ainsi que les paramètres du serveur de publication.

Le type de sécurité étant “NT Authentication” dans cet exemple, nous n’avons pas besoin de renseigner les propriétés PublisherLogin et PublisherPassword qui sont uniquement utilisées dans le cadre d’une authentification à l’aide d’un compte SQL Server.

Les propriétés du distributor n’ont pas non plus été précisées puisque nous considérons que le distributor et le publisher sont sur le même serveur.

La propriété HostName a été définie à “PDA1”. Pour rappel, cette valeur sera utilisée pour initialiser le système de filtres (Cf. article ici).

Astuce :

Les SDK et frameworks utilisés lors du développement d’une application mobile permettent en général de récupérer très facilement le numéro de série de l’appareil. Il peut s’avérer intéressant de créer une table “TERMINAUX”, sur la base de données serveur, qui contiendrait l’ensemble des références des terminaux portables, et qui serait utilisée pour initialiser le système de filtres.

Par exemple, en répliquant le terminal expose son numéro de série dans le “HostName”. Dès lors, le système de filtres s’applique par rapport à ce numéro de série stocké dans une table “TERMINAUX” de la base de données. Cette ligne spécifique de la table “TERMINAUX” permettra d’effectuer les jointures nécessaires avec les autres tables (utilisateurs, clients, périmètre géographique…), pour déterminer l’ensemble des données du référentiel qui doivent descendre sur ce terminal spécifique.

 

Méthodes et synchronisation des données :

La classe SqlCeReplication présente plusieurs méthodes importantes pour la synchronisation de données.

Description des méthodes :

  • AddSubscription : Cette méthode permet de créer l’abonnement. Cette méthode est indispensable dans le sens où l’abonnement doit obligatoirement être créé avant de pouvoir lancer la synchronisation des données. Il est possible de spécifier l’option “CreateDatabase” pour générer automatiquement la base de données mobile si elle n’existe pas.
  • DropSubscription: Cette méthode supprime l’abonnement lié à la base mobile.
  • SaveProperties: Cette méthode permet de stocker l’ensemble des propriétés définies par la classe SqlCeReplication à l’intérieur d’une table système de la base répliquée.
  • LoadProperties: Cette méthode peut être appelée suite à un “SaveProperties” pour retrouver l’ensemble des propriétés de la réplication mémorisées sur l’abonné.
  • Synchronize: C’est la méthode qui permet de lancer la synchronisation de données en mode synchrone.
  • BeginSynchronize: C’est la méthode qui permet de lancer la synchronisation de données en mode asynchrone.

Remarque & Conseils :

La méthode AddSubscription est très importante dans le sens où elle doit obligatoirement être appelée pour créer l’abonnement avant de pouvoir effectuer une synchronisation. Si la base de données mobile existe déjà, il est question de préciser l’option “ExistingDatabase”. Sinon, l’option “CreateDatabase” permettra de générer automatiquement la base de données par réplication, au chemin spécifié par la chaine de connexion SubscriberConnectionString.

La méthode LoadProperties est utile par exemple dans le cas où une base mobile serait générée par réplication en dehors de l’application mobile, et copiée sur l’appareil. Dans ce cas, il suffit de faire appel à LoadProperties, dans le code de l’application mobile, pour rappeler facilement l’ensemble des propriétés définies. Suite à un LoadProperties, la méthode AddSubscription n’a plus besoin d’être appelée. Seules certaines propriétés comme les mots de passe, ou le HOST_NAME ne sont pas sauvegardées et doivent de nouveau être précisées juste avant de lancer la synchronisation.

Procédure :

Maintenant que nous avons défini l’ensemble des propriétés nécessaires, nous allons rajouter l’abonnement sur la base en spécifiant que la base mobile doit être générée entièrement par réplication.

Pour ce faire, nous allons faire appel à la méthode AddSubscription et préciser l’option CreateDatabase pour créer entièrement la base par réplication :

repl.AddSubscription(AddOption.CreateDatabase);

La création de la base mobile et l’ajout de l’abonnement ne sont valables uniquement lors de la première synchro. En effet, pour les synchronisations suivantes, la base sera déjà créée et l’abonnement sera déjà présent sur la base. C’est pourquoi, il est intelligent de tester au préalablement l’existence de la base de données pour effectuer l’ajout de l’abonnement avec l’option CreateDatabase :

if (!System.IO.File.Exists(_dbName))
                    repl.AddSubscription(AddOption.CreateDatabase);

Suite à l’ajout de l’abonnement, nous pouvons enfin appeler la méthode Synchronize qui permet de lancer la synchronisation. Voici à quoi ressemble à présent l’ensemble du code de la “form” :

using System;
using System.Data.SqlServerCe;
using System.Windows.Forms;

namespace TestReplication1
{
    public partial class Form1 : Form
    {
        private const string _dbName = "DBmob.sdf";

        public Form1()
        {
            InitializeComponent();
        }

        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 = "PDA1";
                repl.SubscriberConnectionString = @"Data Source=" + _dbName
                      + ";Max Database Size=1000;Default Lock Escalation =100;";

                if (!System.IO.File.Exists(_dbName))
                    repl.AddSubscription(AddOption.CreateDatabase);

                repl.Synchronize();
            }
            catch (SqlCeException ex)
            {
                MessageBox.Show(ex.Message);
            }
            finally
            {
                if (repl != null)
                    repl.Dispose();
            }
        }
    }
}

Il s’agit bien entendu d’un exemple simple qui permet de comprendre au mieux comment faire appel à la réplication SQL dans une application mobile.

Le code de la réplication doit être modulé selon les projets. Il n’est surtout pas question de reprendre le code C# tel qu’il est présenté ci-dessus, mais plutôt de l’adapter à votre architecture et en fonction de vos besoins.

A suivre…

Pi-R.

Développement Windows Mobile 6.x : Gestion des Forms & Machines d'états

Introduction :

Dans cet article, je vais vous présenter la méthode que j’utilise pour gérer l’apparition des différents écrans d’une application mobile…

Cela fait longtemps que j’utilise cette manière particulière de construire des interfaces mobiles, et je vous la recommande fortement. En effet, nous allons voir ensemble quels sont les avantages que nous pouvons en tirer…

 

Une Form = plusieurs écrans ??

Oublions tout de suite certains principes qui consistent à dire qu’une Form correspond systématiquement à un et un seul écran (développement Windows, Web, etc…).

Pour le développement mobile, je vous propose une autre approche : voir les Forms comme des machines d’états.

Partons du principe qu’une Form ne correspond plus à un unique écran, mais à une petite collection d’écrans qui concernent tous un même process métier.

En effet, une Form peut facilement être découpée en plusieurs panels différents qu’il suffirait d’afficher ou de masquer selon les actions de l’utilisateur sur l’interface graphique.

La notion de Form n’est donc plus liée à un écran particulier mais plutôt à un groupe d’écrans faisant parti du même processus logique.

Exemple :

Process “Choix d’une intervention”. 1 Form = 3 écrans

image

Quels avantages pouvons-nous en tirer ?

Grâce à ce regroupement, nous affichons le panel correspondant au choix de l’utilisateur, c'est-à-dire lorsqu’il clique sur un bouton de navigation.

Au lieu de changer d’écran, on change cette fois de panel. Le panel précédent se masque et le nouveau s’affiche, et c’est tout : nous ne construisons aucun écran, nous effectuons juste un affichage / masquage de panels.

Cette décomposition permet donc d’assurer une fluidité optimale dans un process particulier.

En effet, l’application charge plusieurs écrans à la fois et cela évite donc les temps de chargement au sein d’un même process. L’utilisateur sera donc ravi de bénéficier d’une rapidité optimale durant la totalité de son process. Par contre, il est évident qu’un groupe complet d’écrans est plus long à charger, mais c’est un “mal pour un bien”.

Il est à noter que cette méthodologie garantie également une meilleure stabilité du système.

 

Comment procéder ?

Les différentes Forms d’une application peuvent donc être regroupées en plusieurs panels.

Il est alors question de ne plus penser “écran” mais plutôt “process” afin de diviser au mieux et de former des groupes logiques d’écrans mobiles.

Une fois les groupes identifiés, il s’agit d’étirer la longueur d’une Form dans Visual Studio pour venir y placer les différents panels qui la composeront.

Les écrans les moins utilisés, quant à eux, pourront être détachés des groupements. Le fait de les créer (instruction “new”) à chaque affichage s’effectuera de manière occasionnelle et ne sera donc pas trop gênant pour l’utilisateur. Cela concerne par exemple les écrans des transferts de fichiers, de saisie de commentaires exceptionnels, ou d’autres fonctionnalités spéciales…

Au niveau du code de l’application, cela se traduit de la manière suivante :

  • Une classe complète pour la totalité de la  Form que nous pouvons même décomposer avec un système de “régions” ou de “classes partielles” pour plus de clarté.
  • Une énumération principale des panels qui composent l’écran.
  • Une méthode de gestion servant à l’affichage d’un panel. Cette méthode doit prendre en paramètre un panel de l’énumération, pour le centrer et le rendre visible, et doit masquer l’ensemble des autres panels.

Exemple :

La méthode “ShowPanel” exposée ci-dessous permet de gérer facilement l’affichage et le masquage d’écrans au sein d’une même Form en se basant sur une énumération de panels :

private enum PanelEnum { None, PanelInfos, PanelAbout };
private PanelEnum _currentPanel;

/// 
/// Show and hide panels
/// 
/// The panel to show
private void ShowPanel(PanelEnum panel)
{
    Panel crtPanel = null;
    _currentPanel = panel;
    pnlInfos.Visible = false;
    pnlAbout.Visible = false;

    if (_currentPanel == PanelEnum.PanelInfos)
    {
        crtPanel = pnlInfos;
    }
    if (_currentPanel == PanelEnum.PanelAbout)
    {
        crtPanel = pnlAbout;
    }

    if (crtPanel != null)
    {
        crtPanel.Visible = true;
        crtPanel.Size = new Size(Screen.PrimaryScreen.WorkingArea.Width,
                                        Screen.PrimaryScreen.WorkingArea.Height);
        crtPanel.Location = new Point(0, 0);
        crtPanel.BringToFront();
     }
}

Avec la méthode “ShowPanel”, le basculement d’écran s’effectue rapidement en indiquant le panel précis à afficher suite à une action utilisateur :

private void btnAfficheEcranInfos_Click(object sender, EventArgs e)
{
     ShowPanel(PanelEnum.PanelInfos);
}

 

Empilage de Forms :

D’autre part, il est important de signaler que dans le cadre d’applications mobiles, il est préférable, par mesure de stabilité, d’afficher les Forms à l’aide de la méthode “ShowDialog”.

Au final, cela signifie que les Forms sont constamment “empilées” ou “dépilées” selon les actions de l’utilisateur.

Exemple :

 empil

 

Interprétation :

(1) : Form “FormLogin”

(2) : Empilage de la Form “FormMenuPrincipal” suite à la connexion de l’utilisateur

(3) : Empilage de la Form “FormChoixIntervention suite au choix de l’utilisateur de réaliser une intervention.

(4) : Empilage de la Form “FormCollectionMgtProcess” suite à la validation et au choix de l’intervention à réaliser.

Lorsque l’utilisateur mettra un terme à son rapport d’intervention, les deux dernières Forms vont être “dépilées” (méthode Close) et l’utilisateur reviendra ainsi sur le menu principal de l’application (2).

 

Conclusion :

La méthodologie que j’utilise pour construire des interfaces mobiles est celle que je viens de vous présenter. Elle consiste en 2 principales étapes :

  • Identification de plusieurs groupes d’écrans (= process).
  • Empilage de Forms entières de process qui sont ensuite “dépilées” pour revenir à un menu principal ou à un écran spécifique important.

Les avantages :

  • Découpage logique de processus.
  • Aucun temps de chargement d’écrans au sein d’un même process (rapidité de navigation optimale pour l’utilisateur).
  • Stabilité du système amélioré.

 

N’hésitez pas à me faire part de vos remarques concernant cette méthodologie. De la même façon, n’hésitez pas à me faire part de vos expériences personnelles.

Pi-R.

Réplication SQL pour les projets mobiles : Problématique de la configuration de la synchro web avec IIS 7.x et Windows 7

Introduction :

Pour rappel, le seul type de réplication SQL pris en charge par SQL Server Compact est la “web synchro”. Cela signifie que les terminaux doivent s’appuyer sur une DLL exposée sur un serveur web pour échanger leurs données (= Agent de réplication). La configuration du serveur IIS est donc essentielle pour pouvoir synchroniser les bases de données.

La configuration de la synchro web se déroule via un wizard. Pour l’administrateur, elle consiste à préciser le nom du répertoire virtuel utilisé, le nom du répertoire physique, le type d’identification au serveur, les droits d’accès utilisateurs, etc…

Le paramétrage de la “web synchro” s’initialise directement au sein de SQL Server Management Studio.

Pour lancer le wizard, il suffit de cliquer-droit sur une publication et de sélectionner “Configure Web Synchronization…” :

image 

La configuration de la synchro web est très facile via le wizard… mais attention aux surprises désagréables…………. :image !!!!!!!!!!!

 

Outils de compatibilité IIS 6 :

Pour que la configuration de la synchro web se déroule correctement dans SQL Server Management Studio, il est d’abord important de vérifier que les outils de compatibilité IIS 6 sont bien installés sur le système.

Procédure :

  • Cliquez sur Démarrer, Panneau de configuration, Programmes et fonctionnalités, puis Activer ou désactiver des fonctionnalités Windows.

image image

  • Ouvrez les Services IIS.
  • Ouvrez Outils d'administration Web.
  • Ouvrez Compatibilité avec la gestion IIS 6.
  • Activez les cases à cocher de compatibilité. image
   

Problème de la configuration de la synchro web sous Windows 7 :

Il s’agit d’un problème que j’ai remarqué en souhaitant configurer la synchronisation web sur mon ordinateur sous Windows 7.

Le message d’erreur illustré dans l’introduction de cet article “The user does not have permissions on the specified computer. Specify another computer name.” , apparaissait systématiquement en cliquant sur “Configure Web Synchronization…”. Il était donc pour moi impossible de lancer le wizard de configuration…

En regardant sur Internet, j’ai remarqué que je n’étais pas le seul à rencontrer ce problème sous Windows 7.

En fouinant un peu, j’ai fini par trouver un moyen de contourner l’anomalie:

Pour remédier à ce problème, il suffit de lancer le wizard de configuration à partir de l’exécutable “ConnWiz.exe” situé par défaut dans le répertoire « \Program Files\Microsoft SQL Server Compact Edition\v3.5\Tools » :

image

De cette façon, le message bloquant n’apparaitra pas et vous pourrez tranquillement effectuer le paramétrage et mettre en place la synchro web pour votre système de réplication.

Pi-R.

Développement Windows Mobile 6.x : La petite croix en haut à droite...

A quoi sert la petite croix en haut à droite d’une Form Windows Mobile ?

image

A fermer l’application ??? Eh ben NON !!!

Et c’est d’ailleurs bien là le problème et le sujet de cet article…

 

L’application mobile se met en arrière-plan mais ne se ferme pas !

Il existe un test simple à effectuer et qui peut vous convaincre qu’une application mobile ne se ferme pas en cliquant sur la croix :

Ouvrez une application mobile, par exemple le jeu “Solitaire”, et commencez à jouer une partie :

                  sol2                                    sol1

Ensuite, cliquez sur la fameuse croix en haut à droite puis ouvrez de nouveau le jeu “Solitaire”.

Surprenant non ? Vous retrouvez votre partie de cartes dans l’état même que vous l’aviez quittée !! De plus, le temps indiqué par le chronomètre en bas à gauche de la fenêtre de jeu montre que le jeu s’est déroulé en arrière-plan :

sol3 

Cette expérience simple prouve bien que la petite croix en haut à droite ne ferme pas l’application en cours.

En fait, sous Windows Mobile, la croix permet juste de cacher la fenêtre, c’est à dire de la mettre en arrière-plan, contrairement aux autres systèmes Windows (XP, Vista, 7, etc…) sur lesquels la croix permet bien la fermeture d’une Form.

D’ailleurs, sur certains PDA, il existe un menu “mémoire” permettant de visualiser l’ensemble des programmes en cours d’exécution. Après avoir cliqué sur la croix, allez donc faire un tour dans ce menu. Vous serez surpris de constater le nombre d’applications qui tournent en ce moment sur votre mobile :

                     mem1                      mem2

Pour fermer réellement l’application “Solitaire”, vous avez 2 possibilités :

  • La sélectionner dans la liste des “programmes en cours” puis cliquer sur “Arrêter”
  • Ou effectuer un reset du terminal mobile…

Cette différence de comportement avec les systèmes d’exploitation PC est amusante, mais peut générer quelques problèmes lorsque l’on développe une application mobile…

 

Développer une application mobile et maitriser sa fermeture :

Il est très important de maitriser la fermeture d’une application mobile que nous développons. En effet, il est impératif de libérer proprement les ressources allouées et la mémoire liée à l’exécution de l’application, lorsque l’utilisateur décide de quitter l’application.

Comme la petite croix ne permet pas la fermeture de l’application, il est important de trouver un autre moyen de le faire via le code .NET pour pouvoir assurer la libération des ressources liées au programme.

Si l’application ne se quitte pas proprement, l’application mobile continuera à tourner en arrière-plan, la mémoire virtuelle sera toujours utilisée et non retrouvée, ce qui pourra entrainer un plantage du terminal lors de l’ouverture d’autres programmes.

 

Lorsque vous commencez le développement d’un projet mobile, Visual Studio vous crée automatiquement une première Form :

ec1

Plusieurs possibilités s’offrent alors à vous pour maitriser la fermeture de l’application :

 

1) La première solution consiste à modifier la propriété “MinimizeBox” de la Form, et de mettre sa valeur à “False”.

Cette action aura pour effet, de remplacer la croix en haut à droite par un petit bouton “Ok”. La Form réagira alors comme une fenêtre modale et sa fermeture sera assurée si l’utilisateur clique sur ce petit bouton.

ec2

Dans cette premier solution, l’utilisateur de l’application mobile garde la barre de menu du haut et peut donc toujours facilement lancer une autre application mobile via le menu “démarrer” par exemple (à voir s’il s’agit d’un inconvénient).

L’action liée au bouton “Ok”, c’est à dire “fermer la fenêtre”, n’est pas très explicite et peut être gênant pour l’utilisateur…

 

2) La seconde solution consiste à supprimer complètement la barre de menu du haut (y compris la croix) en définissant la propriété “WindowState” de la forme à “Maximized”.

Cette action aura pour effet de supprimer complètement la barre de menu du haut. (inconvénient pour l’utilisateur ?).

ec3

Il est bien évident, que dans cette solution, le développeur doit obligatoirement prévoir un menu ou un bouton qui permettra à l’utilisateur de quitter explicitement l’application en cours. (Sinon, l’utilisateur sera bloqué, et n’aura guère le choix que de faire un reset de son PDA pour quitter l’application).

De cette solution en résulte une maitrise totale de la fermeture de la Form. La fermeture réelle de la Form sera ordonnée par une instruction “Close()” dans le code lié au clic sur le menu “Exit” par exemple :

mnu

private void mnuExit_Click(object sender, EventArgs e)
{
      // Ferme explicitement la Form:
      this.Close();
}
 

Conseil :

Peu importe la solution utilisée, il peut toujours être intéressant d’ “overrider” la méthode “OnClosing” sur chacune des Forms de l’application, et ce dans le but de disposer proprement l’ensemble des ressources.

En effet, comme la méthode “OnClosing” est levée systématiquement à chaque fois qu’une Form se ferme, il est intéressant de gérer la libération des ressources directement au sein de cette fonction pour s’affranchir de la manière dont la Form a été fermée :

protected override void OnClosing(CancelEventArgs e)
{
    base.OnClosing(e);
            
    // Arrête le lecteur de code à barres:
    StopBarcodeReader();

    // Arrête le minuteur:
    threadTime.Stop();
}

Pi-R.

Réplication SQL pour les projets mobiles : Système de filtres & HostName

Introduction :

Aujourd’hui, je vous propose de revenir un instant sur les synchronisations de données et plus particulièrement sur la réplication de fusion SQL.

Dans un précédent article, nous avons vu comment bien définir les articles des publications. Construire le système de filtres lié à une publication est également une phase importante qui intervient juste après celle du choix des articles.

Dans cet article, nous allons nous attarder un peu sur la construction de ce système de filtres.

Pourquoi est-ce important de filtrer ?

Comment construire une arborescence de filtres propre ?

 

Pourquoi filtrer ?

Les filtres jouent un rôle important pour la réplication dans le sens où ils permettent de filtrer l’ensemble des données qui descendent sur les terminaux mobiles.

En effet, tous les terminaux ne doivent pas forcément posséder les mêmes données dans leurs bases locales.

Considérons par exemple qu’un terminal mobile est affecté à un technicien nomade spécifique, chargé d’effectuer des interventions chez des clients. Dans cet exemple, le technicien T1 ne doit pas avoir le même référentiel de travail que le technicien T2. En effet, les interventions que doit réaliser le technicien T1 ne seront pas les mêmes que celles attribuées au technicien T2. Il en est de même pour les clients, les sites d’interventions, etc.…

Pour certains projets mobiles, l’implémentation de filtres peut également avoir un rôle important pour la sécurité des données. En effet, les filtres peuvent être parfois un bon moyen de restreindre la visibilité des données protégées par des droits.

Enfin, implémenter un système de filtres est essentiel quant à la volumétrie des bases mobiles. S’ils n’existaient pas, les bases mobiles répliquées pourraient être trop volumineuses et ne pourraient même peut-être pas être prises en charge par SQL Server Compact (limite physique à 4Go pour une base mobile SQL Server Compact 3.5).

 

Deux types de filtres :

Il existe 2 types de filtres :

  • Les filtres classiques (ou tous les articles sont au même niveau)
  • Les filtres “join” (arborescence)

Les filtres “join” permettent de former une arborescence d’articles de la publication.

Le système de filtre peut donc être représenté sous la forme d’un arbre composé de nœuds et de feuilles.

Les tables sont reliées entre elles à l’aide de jointures SQL.

Les filtres “join” définissent le fait que les données d’une table dépendent des données filtrées de la table parent, qui dépendent des données filtrées de la table parent et ainsi de suite…

Il s’agit donc d’un système de filtrage complexe, précis et très lié logiquement au fonctionnel attendu. En effet, le système de filtres s’établit en adéquation avec les besoins et le fonctionnel de l’application mobile.

 

Point d’entrée des filtres et HostName :

Le point d’entrée du système n’est autre que la table située en haut de l’arborescence.

Dans l’exemple ci-dessous, nous allons considérer qu’un terminal est attribué à un et un seul technicien nomade. Nous allons donc nous baser sur la table TECHNICIEN (par exemple), et plus particulièrement sur le champ IdTec, pour initialiser le système de filtres.

Pour ajouter le premier filtre de la publication, il faut cliquer sur le bouton “Add” puis sélectionner “Add filter…”, depuis cet écran du wizard :

image Sur l’écran suivant, nous allons sélectionner l’article TECHNICIEN dans le menu déroulant du haut, puis saisir le filtre “WHERE [IdTec] = HOST_NAME()” dans le champ de saisie de droite et cliquer sur “OK” :

image

Filtre d’entrée : WHERE [IdTec] = HOST_NAME()

Explications :

Le HOST_NAME n’est autre que la variable qui permet d’initialiser le filtrage des données. Dans notre cas, elle correspond à l’identifiant du technicien.

Cette variable devra être définie dans le code de l’application mobile et sera utilisée lorsque le PDA initialisera sa phase de réplication. (Propriété “HostName” de la classe “SqlCeReplication”).

Dès lors, lorsqu’un terminal se connecte au serveur SQL, il est immédiatement reconnu et le système de filtres peut ainsi déterminer l’ensemble des données qui doivent être affectées au technicien en question (en fonction de l’id contenu dans la variable HOST_NAME).

 

Construction de l’arborescence de filtres :

Nous allons à présent rajouter un filtre de type “join” qui sera lié au filtre de l’article TECHNICIEN que nous venons de créer.

Pour ce faire, il faut sélectionner le filtre précédemment créé dans la zone de gauche, puis cliquer sur le bouton “Add”, pour enfin sélectionner “Add Join to Extend the Selected Filter…” :

image

Dans l’écran qui apparaitra, il sera question de renseigner les éléments de la jointure, c’est à dire la table ainsi que les champs sur lesquels la jointure SQL va s’appliquer :

image

Dans notre exemple, en cliquant sur “OK”, vous validerez le fait qu’une ligne d’intervention sera distribuée uniquement au technicien concerné par cette intervention. Notez qu’il est tout à fait possible d’éditer au mieux la jointure à appliquer en précisant des clauses “Or” ou “And” pour le filtre.

Après validation, nous pouvons visualiser l’arborescence des filtres qui est en train de se former :

 imageNous pouvons alors continuer à construire l’arborescence en implémentant d’autres filtres à partir de ceux déjà créés, en allant plus loin (ou non) dans les niveaux de profondeurs de l’arbre. La construction de l’arborescence dépend bien sûr du fonctionnel de l’application mobile. L’arbre finit par prendre forme lorsqu’il rassemble plusieurs articles :

image

Remarques importantes :

  • La complexité de l’arborescence des filtres peut être un inconvénient au niveau des performances des synchronisations. Plus le système est complexe, plus les calculs liés à la phase d’initialisation des synchronisations seront longs.
  • Tous les articles n’ont pas la nécessité d’être filtrés. En effet, pour des raisons de performance, il est conseillé de ne pas filtrer les tables non-volumineuses.

Petite astuce pour finir :

En spécifiant un filtre du type “WHERE 0 = 1” le système purgera automatiquement les données remontées au serveur, sur la base SQL Compact du terminal. Les tables d’exportation de données étant définies en “Bidirectionnal” (Cf. précédent article), l’application du filtre WHERE 0 = 1 aura pour effet de supprimer les données sur les bases locales. Pour les articles “Bidirectionnal”, l’upload des données a lieu avant le download.

 

Pi-R.

Développement mobile : .NET Compact Framework & Limitations

Introduction :

Le développement d’applications mobiles est quelque peu différent du développement d’applications sous Windows. En effet, le développement d’applications mobiles se base sur le .NET Compact Framework qui constitue une version allégée du .NET Framework classique de Microsoft.

Quelles sont les différences entre le Compact Framework et le Framework de base ?

Comment peut-on repousser les limites du .NET Compact Framework ?

 

.NET Compact Framework VS .NET Framework :

Le .NET Compact Framework présente quelques différences importantes par rapport au Framework de base. En voici une liste non exhaustive :

Classes et types : Étant une version allégée, le Compact Framework ne prend en charge qu’un sous-ensemble de la bibliothèque de classes .NET Framework. Certains espaces de noms n’existent pas et certaines classes sont allégées en termes de fonctions.

ASP.NET : Le .NET Compact Framework ne prend pas en charge l’ASP.NET.

Langages : C# et Visual Basic sont les seuls langages disponibles pour le .NET Compact Framework.

XML : Le Compact Framework .NET fournit les fonctionnalités de base pour manipuler les documents XML (DOM). De nombreuses fonctionnalités intéressantes ne sont pas prises en charge par le Compact Framework (Validation de schéma XML, Transformation XSLT, Requêtes xpath)

Accès aux données : Les classes et fonctions d’accès aux données sont également très limitées au sein du Compact Framework. “Linq To SQL” et “Entity Framework” ne sont pas supportés.

 

Limitations des interfaces graphiques :

Le .NET Compact Framework a été conçu pour ses performances et sa taille qui lui permet une mobilité totale. La couche “interface graphique” du .NET Compact Framework pour Windows Mobile ne concerne que des appareils avec des résolutions réduites dont la mémoire est plus limitée que celle d’un PC.

Le .NET Compact Framework comporte la plupart des contrôles graphiques du .NET Framework et aussi des contrôles spécifiques aux périphériques mobiles (InputPanel, HardwareButton).

Par rapport au Framework de base, la couche “interface graphique” possède plus de restrictions dans le .NET Compact Framework :

  • Le .NET Compact Framework ne prend pas en charge le Drag-and-drop.
  • Le .NET Compact Framework ne prend pas en charge l’impression.
  • Le .NET Compact Framework ne prend pas en charge le MDI (Multiple Document Interface).
  • Le .NET Compact Framework ne prend pas en charge les contrôles ActiveX.
  • Le .NET Compact Framework prend en charge un nombre limité de méthodes de la classe Graphics.
  • Le .NET Compact Framework ne prend pas en charge la gestion de la transparence (code alpha)
  • Le .NET Compact Framework ne prend pas en charge la propriété KeyPreview pour les formulaires.
  • Le .NET Compact Framework prend en charge un nombre limité d’évènements sur l’interface graphique.

 

Comment repousser les limites ?

Deux possibilités:

1) Pour repousser les restrictions liées aux contrôles graphiques, il est tout à fait possible de créer soi même toute une collection de contrôles personnalisés, en héritant de contrôles existants et en “overridant” les méthodes et évènements.

2) Pour repousser les limites du Compact Framework, il est possible d’utiliser la technique du PInvoke. Le PInvoke (Plateform Invoke) consiste à consommer une API Win32 non managée au travers de code managé .NET.

Aussi, pour certains besoins particuliers qui ne sont pas pris en charge par le Compact Framework, il peut s’avérer nécessaire d’effectuer des appels directement aux DLL systèmes de Windows Mobile.

Pour plus d’informations sur le PInvoke, voici quelques liens intéressants :

http://morpheus.developpez.com/dlldotnet/

http://www.pinvoke.net/

 

Exemple de PInvoke :

L’exemple ci-dessous, non détaillé, montre comment la fonctionnalité du PInvoke peut être utilisée pour repousser les limitations du .NET Compact Framework.

Dans cet exemple, la classe “SoundManager” permet la lecture simple de sons en faisant appel à la DLL Windows Mobile “codedll.dll”.

Pour que le terminal mobile joue un son, il suffit d’appeler la méthode “PlaySound” en spécifiant le chemin du fichier son.

public class SoundManager
{
    [DllImport("CoreDll.dll", EntryPoint = "PlaySound", SetLastError = true)]
     private extern static int PlaySound(string szSound, IntPtr hMod, int flags);

     public enum PlaySoundFlags : int
     {
       SND_SYNC = 0x0000,
       SND_ASYNC = 0x0001,
       SND_NODEFAULT = 0x0002,
       SND_MEMORY = 0x0004,
       SND_LOOP = 0x0008,
       SND_NOSTOP = 0x0010,
       SND_NOWAIT = 0x00002000,
       SND_ALIAS = 0x00010000,
       SND_ALIAS_ID = 0x00110000,
       SND_FILENAME = 0x00020000,
       SND_RESOURCE = 0x00040004
     }

     public static void PlaySound(string soundPath)
     {
         PlaySound(soundPath, IntPtr.Zero, (int)(PlaySoundFlags.SND_FILENAME 
| PlaySoundFlags.SND_SYNC));
     }
}

Pi-R.

Réplication SQL pour les projets mobiles : Bien définir les articles des publications et leurs options

Introduction :

Revenons un instant sur les synchronisations de données et plus particulièrement sur la mise en place de la réplication SQL entre un serveur et des terminaux portables.

Aujourd’hui, je vais vous parlez des articles… Je vais tenter de vous apportez quelques précisions et conseils en ce qui concerne la définition des articles d’une publication.

Pour rappel, les articles représentent l’ensemble des tables qui vont être répliquées lors de la synchronisation SQL. La définition des articles représente la première phase importante lors de la configuration d’une publication.

 

Bien choisir les articles d’une publication :

L’écran ci-dessous est celui qui permet de définir les articles d’une publication.

Il apparait dans le wizard lors de la création d’une nouvelle publication.

Cet écran permet d’ajouter ou de supprimer facilement des tables à répliquer en les cochant ou décochant dans la liste.

Chacune des tables est représentée sous la forme d’un nœud, qui une fois déployé, permet également de cocher ou de décocher les colonnes répliquées.

art2 

Dans l’exemple ci-dessus, nous allons considérer que toutes les tables sont représentatives du référentiel de travail sauf RAPPORT_INTERVENTION  et RAPPORT_INTERVENTION_FOURNITURE. Ces dernières seront destinées à stocker les rapports d’intervention des techniciens et seront mises à jour uniquement à partir du terrain par les utilisateurs nomades.

Conseil :

Il est habile de créer une première publication regroupant tous les articles sauf RAPPORT_INTERVENTION  et RAPPORT_INTERVENTION_FOURNITURE. Une seconde publication, pourra donc être créée pour la remontée des informations terrain et devra contenir les 2 tables précédemment citées. On sépare ainsi la synchronisation du référentiel de travail de celle des informations saisies sur le terrain en 2 publications différentes. Les 2 publications implémentées ne possèderont pas les mêmes options, et seront configurées selon le sens des flux (descendant ou remontant).

Le fait de séparer les tables du référentiel de celles des données terrain en 2 publications différentes permet d’éviter les conflits.

Autre avantage : dans le code .NET de l’application mobile, il sera possible de lancer la synchro du référentiel ou celle des exports terrain indépendamment et à différents endroits de l’application (selon les besoins métiers).

Remarque :

L’option “Show only checked articles in the list” permet de ne visualiser que les articles sélectionnés dans la liste :

art3

Bien définir les options et le sens des flux :

Des propriétés supplémentaires peuvent également être définies sur chacun des articles.

Le bouton “Article Properties” permet d’afficher une fenêtre permettant de définir les options des articles. Ces propriétés peuvent être saisies pour des articles particuliers, ou pour l’ensemble des articles sélectionnés (option “Set properties for all table articles”) :

art4Le premier groupe de paramètres permet de définir les objets copiés sur l’abonné par le processus de réplication (indexes, clefs, contraintes…).

Le second groupe, quant à lui, est le plus important car il définit des propriétés concernant le comportement des tables répliquées.

Conseil :

La propriété importante, qu’il est nécessaire de faire attention, est la “Synchronisation direction”.

En effet, cette option permet de préciser si les données d’une table seront répliquées d’une manière bidirectionnelle ou uniquement dans le sens “Serveur à Abonné” (“Download only”).

Le fait de définir l’ensemble des tables du référentiel en “Download only” permet d’optimiser les processus de réplication et permet également d’éviter les modifications anormales des abonnés.

Il est évident que seules les tables du référentiel, autrement dit, les tables dont les données ne sont jamais modifiées sur le terrain, doivent être définies en “Download only”.

Les autres tables, destinées à stocker les données terrain, doivent être définies en “Bidirectionnal”. (L’option “Upload only” n’existe pas).

Pour la mise en place de la publication du référentiel, il faut donc modifier l’option “Synchronisation direction” pour choisir “Download only to Subscriber, prohibit Subscriber changes” :

art5

En validant l’option « Download-only » en cliquant sur le bouton « OK », les petites icones symbolisant les articles sélectionnés dans la liste seront grisées. De la même façon, l’option « Highlighted table is download only » sera cochée et l’intitulé « For this table, only changes made at the Publisher or a server subscription are replicated » sera visible :

art6Pour la seconde publication, c’est à dire celle qui permettra de remonter les informations saisies sur le terrain, il sera nécessaire de cocher les tables RAPPORT_INTERVENTION et RAPPORT_INTERVENTION_FOURNITURE et de ne pas modifier les propriétés afin de les laisser en mode “Bidirectionnal”. Le fait de déclarer ces 2 articles en “Download only” rendrait bien évidemment la remontée des saisies terrain impossible.

 

Conclusion :

Si le modèle de données le permet, il peut être intéressant de construire 2 publications différentes pour séparer les articles du référentiel de ceux qui permettent de remonter les données terrain. Le sens des flux peut facilement être paramétré dans le wizard lors de la définition des articles. Cette opération permet :

  • D’éviter au mieux les conflits
  • De sécuriser les données du référentiel, en empêchant les terminaux mobiles de les mettre à jour
  • D’optimiser les performances de la synchronisation en définissant des articles en “Download only”
  • De pouvoir lancer la synchro du référentiel ou celle des exports terrain indépendamment dans l’application mobile

La prochaine grande phase lors de la création d’une publication est de définir un système de filtres… (à suivre)

Pi-R.

Développement Windows Phone : Microsoft lance la compétition...

Microsoft a annoncé le 12/07 le lancement d’une compétition pour développeurs d’applications mobiles sur la plateforme Windows Phone 7.

Ce concours est ouvert à tous les développeurs d’applications mobiles : les étudiants, hobbyists, entrepreneurs, professionnels…

Par le biais de cette compétition, l’objectif de Microsoft sera d’identifier les futurs leaders français du monde des applications mobiles.

Ainsi, les développeurs devront réaliser une application mobile en choisissant l’un des thèmes suivant :

  • Divertissement
  • Productivité
  • Communication
  • Musique et Vidéo
  • Voyages
  • Utilitaires
  • News & Metéo
  • Social Networks
  • Lifestyle
  • Maps  & Search
  • Applis Business
  • Livres

Le jury se réunira le 7 Octobre pour désigner les gagnants. Steve Ballmer en personne sera membre de ce jury exceptionnel.

Plus d’infos ici : http://msdn.microsoft.com/fr-fr/isv/ff817766.aspx

Pi-R.

Plus de Messages Page suivante »


Les 10 derniers blogs postés

- Merci par Blog de Jérémy Jeanson le 10-01-2019, 20:47

- Office 365: Script PowerShell pour auditer l’usage des Office Groups de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 11:02

- Office 365: Script PowerShell pour auditer l’usage de Microsoft Teams de votre tenant par Blog Technique de Romelard Fabrice le 04-26-2019, 10:39

- Office 365: Script PowerShell pour auditer l’usage de OneDrive for Business de votre tenant par Blog Technique de Romelard Fabrice le 04-25-2019, 15:13

- Office 365: Script PowerShell pour auditer l’usage de SharePoint Online de votre tenant par Blog Technique de Romelard Fabrice le 02-27-2019, 13:39

- Office 365: Script PowerShell pour auditer l’usage d’Exchange Online de votre tenant par Blog Technique de Romelard Fabrice le 02-25-2019, 15:07

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Stream Portal par Blog Technique de Romelard Fabrice le 02-21-2019, 17:56

- Office 365: Script PowerShell pour auditer le contenu de son Office 365 Video Portal par Blog Technique de Romelard Fabrice le 02-18-2019, 18:56

- Office 365: Script PowerShell pour extraire les Audit Log basés sur des filtres fournis par Blog Technique de Romelard Fabrice le 01-28-2019, 16:13

- SharePoint Online: Script PowerShell pour désactiver l’Option IRM des sites SPO non autorisés par Blog Technique de Romelard Fabrice le 12-14-2018, 13:01