Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0

 
CONTINUER À LIRE
Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0
Premiers pas avec Microsoft
   Synchronization Framework
         For ADO .NET
                                        Version 1.0

James RAVAILLE
http://blogs.dotnet-france.com/jamesr
Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0
2       Premiers pas avec Microsoft Synchronization Framework For ADO .NET

                                                                  Sommaire

    1      Introduction ..................................................................................................................................... 3
        1.1       Présentation ............................................................................................................................ 3
        1.2       Pré-requis ................................................................................................................................ 3
    2      Présentation de l’application .......................................................................................................... 4
        2.1       Architecture de l’application ................................................................................................... 4
        2.2       Construction de la base de données ....................................................................................... 4
           2.2.1          Schéma de base de données ........................................................................................... 4
           2.2.2          Script de création ............................................................................................................ 4
    3      Création de l’application ................................................................................................................. 7
        3.1       Création du projet ................................................................................................................... 7
        3.2       Création du composant de synchronisation ........................................................................... 7
        3.3       Création de l’interface graphique ......................................................................................... 12
           3.3.1          Présentation de l’interface ............................................................................................ 12
           3.3.2          Mouvement et gestion des données ............................................................................. 14
           3.3.3          Implémentation des actions .......................................................................................... 15
        3.4       Exécution et test de l’application .......................................................................................... 21
        3.5       Mise en œuvre de la synchronisation bi-directionnelle ........................................................ 22
    4      Gestion des conflits de données ................................................................................................... 24
        4.1       Présentation des cas de conflit de données .......................................................................... 24
        4.2       Présentation du formulaire de gestion de conflits de données ............................................ 24
        4.3       Réalisation du formulaire de gestion de conflits de données ............................................... 24
        4.4       Résolution des conflits de données....................................................................................... 26
    5      Conclusion .............................................. Erreur ! Signet non défini.Error! Bookmark not defined.

                                            Dotnet France Association – James RAVAILLE
Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0
3    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

    1       Introduction

    1.1 Présentation
            Dans ce cours, nous allons mettre en œuvre les composants de Synchronization Framework
    For ADO .NET, sous-ensemble de Microsoft Synchronization Framework. Nous allons créer une
    application, permettant de synchroniser des données entre une base de données locale de type SQL
    Server CE, et une base de données distante de type SQL Server 2008.

    1.2 Pré-requis
             Nous vous conseillons de lire les cours suivants, publiés sur Dotnet-France:

        -    Les bases fondamentales d’ADO .NET.
        -    L’introduction à Microsoft Synchronization Framework.
        -    Les bases fondamentales de Synchronization Framework For ADO .NET.

                                Dotnet France Association – James RAVAILLE
Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0
4    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

    2 Présentation de l’application

    2.1 Architecture de l’application
            Voici l’architecture de l’application que nous allons développer :

                                                           TCP/IP

                     Serveur de bases de données
                          SQL Server 2008

           Cette application affiche et gère des données en mode CRUD. Ces données sont contenues
    dans une base de données SQL Server 2008 distante. Pour mettre en œuvre Synchronization
    Framework For ADO .NET, cette application possède une source de données locale, implémentée par
    une base de données SQL Server CE (fichier d’extension .sdf).

    2.2 Construction de la base de données
    2.2.1  Schéma de base de données
           Nous allons créer la base de données SQL Server 2008, nommée DotnetFrance, dont le
    diagramme est le suivant :

    2.2.2   Script de création
            Voici un script permettant de créer la base de données :

                                Dotnet France Association – James RAVAILLE
Premiers pas avec Microsoft Synchronization Framework For ADO .NET - Version 1.0
5   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

    -- SQL

    CREATE TABLE [dbo].[Stagiaire](
           [Identifiant] [int] IDENTITY(1,1) NOT NULL,
           [Nom] [varchar](50) NOT NULL,
           [Prenom] [varchar](50) NOT NULL,
      CONSTRAINT [PK_Stagiaire] PRIMARY KEY CLUSTERED
    (
           [Identifiant] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
    OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
      CONSTRAINT [UQ__Stagiaire__7F60ED59] UNIQUE NONCLUSTERED
    (
           [Identifiant] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
    OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    CREATE TABLE [dbo].[Cours](
           [Identifiant] [int] IDENTITY(1,1) NOT NULL,
           [Libelle] [varchar](50) NOT NULL,
           [NombreJours] [int] NOT NULL,
      CONSTRAINT [PK_Cours] PRIMARY KEY CLUSTERED
    (
           [Identifiant] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
    OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY],
      CONSTRAINT [UQ__Cours__023D5A04] UNIQUE NONCLUSTERED
    (
           [Identifiant] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
    OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    CREATE TABLE [dbo].[Stagiaire2Cours](
           [IdStagiaire] [int] NOT NULL,
           [IdCours] [int] NOT NULL,
      CONSTRAINT [PK_Stagiaire2Cours] PRIMARY KEY CLUSTERED
    (
           [IdStagiaire] ASC,
           [IdCours] ASC
    )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY =
    OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
    ) ON [PRIMARY]
    GO

    ALTER TABLE [dbo].[Stagiaire2Cours] WITH CHECK ADD                   CONSTRAINT
    [FK_Stagiaire2Cours_Cours] FOREIGN KEY([IdCours])
    REFERENCES [dbo].[Cours] ([Identifiant])
    ON UPDATE CASCADE
    ON DELETE CASCADE
    GO

    ALTER TABLE [dbo].[Stagiaire2Cours] WITH CHECK ADD CONSTRAINT
    [FK_Stagiaire2Cours_Stagiaire] FOREIGN KEY([IdStagiaire])
    REFERENCES [dbo].[Stagiaire] ([Identifiant])
    ON UPDATE CASCADE
    ON DELETE CASCADE
    GO
    ALTER TABLE [dbo].[Stagiaire2Cours] CHECK CONSTRAINT
    [FK_Stagiaire2Cours_Stagiaire]
    GO

                           Dotnet France Association – James RAVAILLE
6   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

         Et voici un script permettant de l’alimenter en données :

    -- SQL

    USE [DotnetFrance]
    GO

    /****** Object: Table [dbo].[Stagiaire] ******/
    SET IDENTITY_INSERT [dbo].[Stagiaire] ON
    INSERT [dbo].[Stagiaire] ([Identifiant], [Nom], [Prenom])                     VALUES (1,
    N'DEROUX', N'Alain')
    INSERT [dbo].[Stagiaire] ([Identifiant], [Nom], [Prenom])                     VALUES (2,
    N'RAVAILLE', N'James')
    INSERT [dbo].[Stagiaire] ([Identifiant], [Nom], [Prenom])                     VALUES (3,
    N'SIRON', N'Karl')
    INSERT [dbo].[Stagiaire] ([Identifiant], [Nom], [Prenom])                     VALUES (4,
    N'EMATO', N'Julie')
    SET IDENTITY_INSERT [dbo].[Stagiaire] OFF

    /****** Object: Table [dbo].[Cours] ******/
    SET IDENTITY_INSERT [dbo].[Cours] ON
    INSERT [dbo].[Cours] ([Identifiant], [Libelle],                   [NombreJours]) VALUES (1,
    N'SQL Server - Administration de serveurs', 5)
    INSERT [dbo].[Cours] ([Identifiant], [Libelle],                   [NombreJours]) VALUES (2,
    N'XHTML / CSS', 3)
    INSERT [dbo].[Cours] ([Identifiant], [Libelle],                   [NombreJours]) VALUES (3,
    N'C#', 5)
    INSERT [dbo].[Cours] ([Identifiant], [Libelle],                   [NombreJours]) VALUES (4,
    N'ASP .NET 3.5', 5)
    INSERT [dbo].[Cours] ([Identifiant], [Libelle],                   [NombreJours]) VALUES (5,
    N'ASP .NET AJAX', 3)
    SET IDENTITY_INSERT [dbo].[Cours] OFF

    /****** Object: Table [dbo].[Stagiaire2Cours]                    ******/
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (1,   1)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (1,   3)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (2,   1)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (2,   2)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (2,   3)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (2,   4)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (3,   1)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (3,   4)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (3,   5)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (4,   4)
    INSERT [dbo].[Stagiaire2Cours] ([IdStagiaire],                   [IdCours])   VALUES   (4,   5)

                            Dotnet France Association – James RAVAILLE
7    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

    3 Création de l’application

    3.1 Création du projet
           Après avoir ouvert Visual Studio, créons un projet de type Windows Forms, nommé
    AppliSynchroDonnees :

    3.2 Création du composant de synchronisation
             Pour créer un composant de synchronisation de données, ajoutons à la racine de notre
    projet, un composant de type « Cache de base de données locale », nommé CompoSynchro :

                            Dotnet France Association – James RAVAILLE
8    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

           Après avoir cliqué sur le bouton Ajouter, la fenêtre suivante apparaît :

                                                                                                   2
                                                                         1

                                                                   3

                                                                             4

                                                                                 6

              5

            Commençons par sélectionner une connexion vers votre base de données SQL Server (1), ou
    en créer une si elle n’existe pas (2). Dans le cas où votre connexion utilise l’authentification SQL
    Server pour se connecter sur le serveur de base de données, la fenêtre suivante apparaît :

                              Dotnet France Association – James RAVAILLE
9   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

          Saisissons le mot de passe de l’utilisateur, puis validons. Revenu sur la fenêtre précédente :

      -   Nous ne modifions pas la connexion Cliente. Lors de la validation, cet assistant créera un
          fichier de base de données local (d’extension .sdf). (3)
      -   Nous décidons d’utiliser le mécanisme de suivi des modifications proposées par SQL Server
          2008 (4). Ainsi, il ne sera pas nécessaire de modifier la structure des tables, pour lesquelles
          nous souhaitons assurer le suivi.
      -   Nous synchronisons les données au sein d’une transaction (6).
      -   Cliquons sur le bouton Ajouter (en bas à droite - 5). La fenêtre suivante apparaît :

                             Dotnet France Association – James RAVAILLE
10    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

             Sélectionnons les tables pour lesquelles nous voulons synchroniser les données. Dans notre
     application, nous le gérons uniquement pour la table Stagiaire.

             Etant donné que nous avons précédemment choisi, le système de suivi des modifications
     proposé par SQL server 2008, les options de détections et d’application des modifications sont
     désactivées pour les tables à synchroniser (tables sélectionnées).

            En cliquant sur le bouton OK, la fenêtre suivante apparaît :

            Une fois cette étape réalisée, la fenêtre suivante apparaît :

             Choisissons les champs des tables que nous souhaitons synchroniser. Dans notre cas, nous
     sélectionnons tous les champs de la table. Puis cliquons sur le bouton « Terminer ». De nombreuses
     opérations sont effectuées par Visual Studio sur notre projet :

            -   Ajout des références :

                               Dotnet France Association – James RAVAILLE
11   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

                   o Microsoft.Synchronization.Data
                   o Microsoft.Synchronization.Data.Server
                   o Microsoft.Synchronization.Data.SqlServerCe
                   o System.Data.SqlServerCe
                   o System.Transactions
          -   Le fichier CompoSynchro.sync est créé. Ce fichier contient tous les paramètres que nous
              venons de définir.
          -   Un répertoire nommé SQLScripts contenant les fichiers suivant :
                   o CompoSynchro.DotnetFrance.sql : script SQL permettant d’activer la surveillance
                       des modifications des données sur la base de données DotnetFrance.

     -- SQL

     IF NOT EXISTS (SELECT * FROM sys.change_tracking_databases WHERE
     database_id = DB_ID(N'DotnetFrance'))
        ALTER DATABASE [DotnetFrance]
        SET CHANGE_TRACKING = ON
     GO

                  o   CompoSynchro.dbo.Stagiaire.sql : script SQL permettant d’activer la surveillance
                      des modifications des données sur la table Stagiaire. Un fichier de ce type est
                      généré par table synchronisée.

     -- SQL

     IF NOT EXISTS (SELECT * FROM sys.change_tracking_tables WHERE object_id =
     OBJECT_ID(N'[dbo].[Stagiaire]'))
        ALTER TABLE [dbo].[Stagiaire]
        ENABLE CHANGE_TRACKING
     GO

          -   Un      autre    répertoire   nommé       SQLUndoScripts     contenant     les   fichiers
              CompoSynchro.DotnetFrance.disable.sql et CompoSynchro.dbo.Stagiaire.disable.sql. A
              l’inverse des scripts précédents, ils permettent respectivement d’annuler la surveillance
              des données sur la base de données DotnetFrance et la table Stagiaire :

     -- SQL

     IF EXISTS (SELECT * FROM sys.change_tracking_databases WHERE database_id
     = DB_ID(N'DotnetFrance'))
        ALTER DATABASE [DotnetFrance]
        SET CHANGE_TRACKING = OFF
     GO

     -- SQL

     IF EXISTS (SELECT * FROM sys.change_tracking_tables WHERE object_id =
     OBJECT_ID(N'[dbo].[Stagiaire]'))
        ALTER TABLE [dbo].[Stagiaire]
        DISABLE CHANGE_TRACKING
     GO

                            Dotnet France Association – James RAVAILLE
12    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

             -   Création du fichier DotnetFrance.sdf. Il constitue une source de données locale pour
                 notre application, sous forme de fichier. La propriété « Copier dans le répertoire de
                 sortie », à savoir le répertoire d’exécution de l’application, est valorisée à « Copier si plus
                 récent ». Ce fichier sera à déployer lors du déploiement de l’application. Il constitue une
                 base de données qui contient autant de tables qu’il y a de tables à synchroniser. Dans
                 notre cas, elle ne contiendra qu’une seule table, nommée Stagiaire. Elle aura les mêmes
                 champs et contraintes que la table Stagiaire de la base de données distante.
             -   Le fichier DotnetFranceDataSet.xsd est créé. Il s’agit d’un DataSet typé permettant
                 d’accéder aux données contenues dans le fichier DotnetFrance.sdf.
             -   Le fichier de configuration est modifié, de manière à contenir deux chaînes de
                 connexion :
                     o Une vers la base de données distante (DotnetFrance).
                     o Une vers la base de données locale (DotnetFrance.sdf).

              Une fois cette étape terminée, notre base de données a été configurée, et la nous avons créé
     notre source de données locale, enfin créer et configurer de manière « basique » notre composant
     de synchronisation. En effet, toutes les tâches liées à la synchronisation de données ne sont pas
     traitées. Par exemple, les conflits de synchronisation (des mêmes données modifiées à la fois côté
     client et côté serveur) ne sont pas traités. Cependant, Synchronization Framework For ADO .NET met
     à notre disposition des mécanismes de gestion de conflits, que nous étudierons ultérieurement dans
     ce cours.

     3.3 Création de l’interface graphique
     3.3.1   Présentation de l’interface
             Voici l’interface graphique du formulaire principal de notre application, que nous allons
     construire :

                                Dotnet France Association – James RAVAILLE
13   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

                                               6

                                  1

                                      2
                                                                               5
                                                       4

                                                   3

          Elle doit permettre :

          -   De synchroniser la base de données distante, et la source de données locale
              DotnetFrance.sdf de l’application, via le bouton Synchroniser (1).
          -   D’afficher les données contenues dans la base de données locales (2). Pour ce faire, nous
              utiliserons le DataSet typé DotnetFranceDataSet.xsd.
          -   De permettre à l’utilisateur d’ajouter, de modifier et de supprimer un stagiaire (au
              travers de la grille - 3).
          -   De valider (avec le bouton Up - 4) ou d’annuler les modifications (avec le bouton Down -
              2).

          Ce formulaire affiche aussi des informations :

          -   Concernant la gestion des données dans la base de données locale : nombre
              d’enregistrements à ajouter / modifier / supprimer (5). Ces données sont réinitialisées à
              0 lors de la validation ou annulation des données dans la source de données locale.
          -   Concernant la synchronisation entre la base de données locale et la base de données
              distantes (6):

                             Dotnet France Association – James RAVAILLE
14    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

                      o    Heure de départ de la synchronisation ;
                      o    Heure de fin de la synchronisation ;
                      o    Nombre d’enregistrements total à uploader ; uploadés et ayant levé une erreur
                           (par exemple, pour un conflit de données) ;
                      o    Nombre d’enregistrements total à downloader ; downloadés et ayant levé une
                           erreur.

     3.3.2  Mouvement et gestion des données
            Voici un schéma présentant les mouvements de données entre les différents endroits de
     stockage de l’application :

                   Composant de synchronisation
                                                                TableAdapter
                        Sync Framework

                          Synchronisation              Alimentation / répercution
                          bidirectionnelle                 des modifications

                                                                                                  Annulation des
                                                                                                   modifications
                                                                                               (opération mémoire)

      Base de données distante
                                       Base de données locale                   DataSet typé
         SQL Server 2008

              Comme le montre le schéma ci-dessus, il existe trois endroits de stockage pour l’application,
     qu’il est nécessaire de bien distinguer :

             -   La base de données SQL Server, base de données distante, utilisée par tous les
                 utilisateurs de l’application.
             -   La base de donnes locale DotnetFrance.sdf, propre à l’application.
             -   Le DataSet, qui permet de gérer les données contenues dans la base de données locale,
                 en utilisant les objets du modèle ADO .NET en mode déconnecté (en l’occurrence un
                 DataSet typé, contenant des DataTable et des TableAdapters).

            Les composants de l’application, permettant de manipuler les données contenues dans ces
     sources de données, sont multiples :

             -   Le composant de synchronisation Sync Framework. Dans notre exemple, nous
                 choisissons la synchronisation bidirectionnelle. Autrement dit, toutes les modifications
                 effectuées dans la base de données locales seront répercutées dans la base de données
                 distante, et vice-versa.
             -   Les TableAdapters : composants DataAdapters spécialisés, définis dans le DataSet typé,
                 pour gérer en mode CRUD les données contenues dans une table particulère. Ils
                 permettent de lire des données dans la base de données locales pour alimenter une
                 table du DataSet typé, et de répercuter dans cette même table de cette même base,
                 toutes les modifications effectuées par l’utilisateur dans cette table. Dans notre cas,
                                   Dotnet France Association – James RAVAILLE
15    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

                 notre DataSet ne contenant qu’une seule table (stagiaire), un seul TableAdapter a été
                 créé.
             -   Un contrôle de type BindingSource nommé BdsStagiaires, permet d’alimenter la grille
                 des stagiares.

     3.3.3   Implémentation des actions

     3.3.3.1 Les objets gérés par le formulaire
             Dans les différentes méthodes du formulaire que nous allons présenter ci-dessous, nous
     allons gérer les objets suivants :

             -   Une instance du DataSet typé DotnetFranceDataSet, nommée oDataSet.
             -   Une instance de la classe StagiaireTableAdapter, permettant d’alimenter le DataSet typé
                 DotnetFranceDataSet et de répercuter les modifications effectuées sur la source de
                 données DotnetFrance.sdf.
             -   Une instance du composant de synchronisation CompoSynchroClientSyncProvider,
                 nommée AgentSynchro.

             Ainsi, soit les déclarations suivantes, constituant les attributs du formulaire :

      // C#

      DotnetFranceDataSet oDataSet;
      StagiaireTableAdapter oStagiaireTableAdapter;
      CompoSynchroSyncAgent oAgentSynchro;

      ' VB.NET

      Dim oDataSet As DotnetFranceDataSet
      Dim oStagiaireTableAdapter As StagiaireTableAdapter
      Dim oAgentSynchro As CompoSynchroSyncAgent

     Remarque : pour utiliser la classe oStagiaireTableAdapter, importer l’espace                de   nom
     AppliSynchroDonnees.DotnetFranceDataSetTableAdapters.

     3.3.3.2 Chargement du formulaire
             Lors du chargement du formulaire en mémoire, nous allons instancier les attributs déclarés
     ci-dessus. Ainsi, en implémentant l’évènement Load sur notre formulaire, nous écrivons le bloc
     d’instructions suivant :

      // C#

      private void FrmSynchroDonnees_Load(object sender, EventArgs e)
      {
          oDataSet = new DotnetFranceDataSet();
          oStagiaireTableAdapter = new StagiaireTableAdapter();
          oAgentSynchro = new CompoSynchroSyncAgent();
      }

                                 Dotnet France Association – James RAVAILLE
16    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      ' VB.NET

      Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
      System.EventArgs) Handles MyBase.Load
          oDataSet = New DotnetFranceDataSet()
          oStagiaireTableAdapter = New StagiaireTableAdapter()
          oAgentSynchro = New CompoSynchroSyncAgent()
      End Sub

             Ainsi, nous pouvons utiliser ces objets dans toutes les autres méthodes de notre
      formulaire.

     3.3.3.3 Synchronisation des données entre la base de données distante et la base de
             données locale
            Nous implémentons l’évènement Click sur le bouton dont le libellé est Synchroniser

      // C#

      private void CmdSynchroniser_Click(object sender, EventArgs e)
      {
          // Gestion des conflits.
          CompoSynchroClientSyncProvider clientSyncProvider =
      (CompoSynchroClientSyncProvider)oAgentSynchro.LocalProvider;

           //syncAgent.Stagiaire.SyncDirection = SyncDirection.Bidirectional;
           SyncStatistics syncStats = oAgentSynchro.Synchronize();

           // Affichage des stastistiques sur la synchronisation.
           this.AfficherInfosSynchronisation(syncStats);

           // Chargement des données.
           oStagiaireTableAdapter.Fill(oDataSet.Stagiaire);
           BdsStagiaires.DataSource = oDataSet.Stagiaire;
      }

      ' VB.NET

      Private Sub CmdSynchroniser_Click(ByVal sender As System.Object, ByVal e
      As System.EventArgs) Handles CmdSynchroniser.Click
          ' Gestion des conflits.
          Dim clientSyncProvider As CompoSynchroClientSyncProvider =
      CType(oAgentSynchro.LocalProvider, CompoSynchroClientSyncProvider)

           'syncAgent.Stagiaire.SyncDirection = SyncDirection.Bidirectional;
           Dim syncStats As SyncStatistics = oAgentSynchro.Synchronize()

           ' Affichage des stastistiques sur la synchronisation.
           Me.AfficherInfosSynchronisation(syncStats)

          ' Chargement des données.
          oStagiaireTableAdapter.Fill(oDataSet.Stagiaire)
          BdsStagiaires.DataSource = oDataSet.Stagiaire
      End Sub

                              Dotnet France Association – James RAVAILLE
17    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

     Remarque : pour utiliser la          classe   SyncStatistics,   importer   l’espace   de   nom
     Microsoft.Synchronization.Data.

     3.3.3.4 Affichage des informations sur la synchronisation
             Dans le bloc de code présenté ci-dessus, une méthode nommée AfficherInfosSynchronisation
     est utilisée pour afficher les informations résultant de la dernière synchronisation. Voici son
     implémentation :

      // C#

      private void AfficherInfosSynchronisation(SyncStatistics aStatsSynchro)
      {
          TxtHeureDebutSynchronisation.Text =
      aStatsSynchro.SyncStartTime.ToString();
          TxtHeureFinSynchronisation.Text =
      aStatsSynchro.SyncCompleteTime.ToString();

          TxtNombreModificationsDownloadees.Text =
      aStatsSynchro.TotalChangesDownloaded.ToString();
          TxtNombreModificationsDownloadeesAppliquees.Text =
      aStatsSynchro.DownloadChangesApplied.ToString();
          TxtNombreModificationsDownloadeesEnErreur.Text =
      aStatsSynchro.DownloadChangesFailed.ToString();

          TxtNombreModificationsUploadees.Text =
      aStatsSynchro.TotalChangesUploaded.ToString();
          TxtNombreModificationsUploadeesAppliquees.Text =
      aStatsSynchro.UploadChangesApplied.ToString();
          TxtNombreModificationsUploadeesEnErreur.Text =
      aStatsSynchro.UploadChangesFailed.ToString();
      }

      ' VB.NET

      Private Sub AfficherInfosSynchronisation(ByVal aStatsSynchro As
      SyncStatistics)
          TxtHeureDebutSynchronisation.Text =
      aStatsSynchro.SyncStartTime.ToString()
          TxtHeureFinSynchronisation.Text =
      aStatsSynchro.SyncCompleteTime.ToString()

          TxtNombreModificationsDownloadees.Text =
      aStatsSynchro.TotalChangesDownloaded.ToString()
          TxtNombreModificationsDownloadeesAppliquees.Text =
      aStatsSynchro.DownloadChangesApplied.ToString()
          TxtNombreModificationsDownloadeesEnErreur.Text =
      aStatsSynchro.DownloadChangesFailed.ToString()

          TxtNombreModificationsUploadees.Text =
      aStatsSynchro.TotalChangesUploaded.ToString()
          TxtNombreModificationsUploadeesAppliquees.Text =
      aStatsSynchro.UploadChangesApplied.ToString()
          TxtNombreModificationsUploadeesEnErreur.Text =
      aStatsSynchro.UploadChangesFailed.ToString()
      End Sub

                              Dotnet France Association – James RAVAILLE
18    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

     3.3.3.5 Affichage des modifications en cours sur le DataSet typé
              Dans le bloc de code présenté ci-dessus, une méthode est utilisée pour afficher les
     modifications en cours dans le DataSet typé local (nombre de stagiaires marqués pour l’ajout, pour la
     modification et pour la suppression). Cette méthode est appelée lors de tout changement de ligne
     dans la liste des stagiaires affichée :

      // C#

      private void AfficherInformationsDataSetType()
      {
          TxtNombreAjouts.Text = "0";
          TxtNombreModifs.Text = "0";
          TxtNombreAjouts.Text = "0";

          if (oDataSet.Stagiaire != null && oDataSet.Stagiaire.GetChanges() !=
      null)
          {
              if (oDataSet.Stagiaire.GetChanges(DataRowState.Added) != null)
              {
                  TxtNombreAjouts.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Added).Rows.Count.ToString();
              }

              if (oDataSet.Stagiaire.GetChanges(DataRowState.Modified) != null)
              {
                  TxtNombreModifs.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Modified).Rows.Count.ToString(
      );
              }

              if (oDataSet.Stagiaire.GetChanges(DataRowState.Deleted) != null)
              {
                  TxtNombreSuppression.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Deleted).Rows.Count.ToString()
      ;
              }
          }
      }

      private void BdsStagiaires_CurrentChanged(object sender, EventArgs e)
      {
          this.AfficherInformationsDataSetType();
      }

                               Dotnet France Association – James RAVAILLE
19    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      ' VB.NET

      Private Sub AfficherInformationsDataSetType()
          TxtNombreAjouts.Text = "0"
          TxtNombreModifs.Text = "0"
          TxtNombreAjouts.Text = "0"

          If (oDataSet.Stagiaire IsNot Nothing AndAlso
      oDataSet.Stagiaire.GetChanges() IsNot Nothing) Then
              If (oDataSet.Stagiaire.GetChanges(DataRowState.Added) IsNot
      Nothing) Then
                  TxtNombreAjouts.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Added).Rows.Count.ToString()
              End If

              If (oDataSet.Stagiaire.GetChanges(DataRowState.Modified) IsNot
      Nothing) Then
                  TxtNombreModifs.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Modified).Rows.Count.ToString(
      )
              End If

              If (oDataSet.Stagiaire.GetChanges(DataRowState.Deleted) IsNot
      Nothing) Then
                  TxtNombreSuppression.Text =
      oDataSet.Stagiaire.GetChanges(DataRowState.Deleted).Rows.Count.ToString()
              End If
          End If
      End Sub

      Private Sub BdsStagiaires_CurrentChanged(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles BdsStagiaires.CurrentChanged
          Me.AfficherInformationsDataSetType()
      End Sub

     3.3.3.6 Répercution des modifications effectuées sur le DataSet typé vers la base de
                données locale
              Le bloc de code ci-dessous permet de mettre à jour la source de données locale de
     l’application (DotnetFrance.sdf), avec les données contenues dans le DataSet typé. Il correspond à
     l’implémentation de l’évènement Click sur le bouton CmdValiderModifications labellisé UP :

      // C#

      private void CmdValiderModifications_Click(object sender, EventArgs e)
      {
          oStagiaireTableAdapter.Update(oDataSet.Stagiaire);
          this.AfficherInformationsBaseDonneesLocale();
      }

                               Dotnet France Association – James RAVAILLE
20    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      ' VB.NET

      Private Sub CmdValiderModifications_Click(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles CmdValiderModifications.Click
          oStagiaireTableAdapter.Update(oDataSet.Stagiaire)
          Me.AfficherInformationsBaseDonneesLocale()
      End Sub

     3.3.3.7 Annulation des modifications effectuées sur le DataSet typé
            Le bloc de code ci-dessous permet d’annuler les données contenues dans le DataSet typé. Il
     correspond à l’implémentation de l’évènement Click sur le bouton CmdAnnulerModification labellisé
     DOWN :

      // C#

      private void CmdAnnulerModification_Click(object sender, EventArgs e)
      {
          oDataSet.Stagiaire.RejectChanges();
          this.AfficherInformationsDataSetType();
      }

      ' VB.NET

      Private Sub CmdAnnulerModification_Click(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles CmdAnnulerModification.Click
          oDataSet.Stagiaire.RejectChanges()
          Me.AfficherInformationsDataSetType()
      End Sub

     3.3.3.8 Fermeture du formulaire
            Pour permettre la fermeture du formulaire au travers du bouton cliquer, implémentons
     l’évènement Click sur le bouton, et ajoutons l’instruction suivante :

      // C#

      private void CmdFermer_Click(object sender, EventArgs e)
      {
          // Fermeture du formulaire.
          this.Close();
      }

      ' VB.NET

      Private Sub CmdFermer_Click(ByVal sender As System.Object, ByVal e As
      System.EventArgs) Handles CmdFermer.Click
          ' Fermeture du formulaire.
          Me.Close()
      End Sub

                              Dotnet France Association – James RAVAILLE
21    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

     3.4 Exécution et test de l’application
            Nous pouvons maintenant exécuter cette application et nous obtenons l’IHM suivante :

            Nous exécutons alors les actions suivantes :

     1 – Cliquons sur le bouton Synchroniser. Cette action a pour conséquence de mettre à jour notre
     base de données locale (DotnetFrance.sdf). Le DataSet typé est créé et alimenté avec les données, et
     les données affichées dans la grille. Si des modifications sont effectuées dans la base de données
     locale et/ou distante, alors les compteurs situés dans le haut du formulaire sont mis à jour.

     2 – Nous modifions les données les données dans la grille : ajout d’un stagiaire, modification d’un
     autre stagiaire… On remarque que ces modifications sont comptabilisées.

     3 – Ensuite, nous cliquons sur le bouton UP, ce qui a pour conséquences de mettre à jour la base de
     données locale, et de remettre les marqueurs associés à zéro.

     4 – Puis on cliquer sur le bouton synchroniser, de manière à répercuter les modifications effectuées
     dans notre base de données locale dans la base de données distante, et vice-versa. Les modifications
     effectuées dans la base de données locale et/ou distante, alors les compteurs situés dans le haut du
     formulaire sont-elles mis à jour ? Nous remarquons que nos modifications ne sont pas uploadées !

                               Dotnet France Association – James RAVAILLE
22     Premiers pas avec Microsoft Synchronization Framework For ADO .NET

     Pas d’inquiétude : par défaut, la synchronisation n’est pas bi-directionnelle, mais uni-directionnelle
     dans le sens base de données distante vers la base de données locale.

     3.5 Mise en œuvre de la synchronisation bi-directionnelle
             Le sens de la synchronisation peut être effectué :

             -   Soit sur chaque table.
             -   Soit sur chaque regroupement de table (syncGroup). Le sens de synchronisation est alors
                 appliqué à toutes les tables du groupe.

              Dans notre cas, nous allons l’effectuer sur la table Stagiaire. Pour ce faire, une fois l’instance
     de la classe de synchronisation créée, on écrit l’instruction marquée en gras ci-dessus :

     // C#

     private void FrmGestionListeStagiaires_Load(object sender, EventArgs e)
     {
         oStagiaireTableAdapter = new StagiaireTableAdapter();
         oDataSet = new SyncSourceDataSet();
         oAgentSynchro = new clientSyncAgent();
         oAgentSynchro.Stagiaire.SyncDirection = SyncDirection.Bidirectional;
     }

     ' VB.NET

     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As
     System.EventArgs) Handles MyBase.Load
         oStagiaireTableAdapter = New StagiaireTableAdapter()
         oDataSet = New DotnetFranceDataSet()
         oAgentSynchro = New CompoSynchroSyncAgent()
         oAgentSynchro.Stagiaire.SyncDirection = SyncDirection.Bidirectional
     End Sub

            Si vous souhaitez centraliser la définition des sens de synchronisation au sein même du
     composant de synchronisation CompoSynchro.sync, pour ne la définir qu’une seule fois, vous devez
     vous positionner à la classe code-behind du composant de ce composant, et écrire l’instruction
     indiquée ci-dessous :

       // C#

       public partial class clientSyncAgent
       {
           partial void OnInitialized()
           {
               this.Stagiaire.SyncDirection = SyncDirection.Bidirectional;
           }
       }

                                 Dotnet France Association – James RAVAILLE
23    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      ' VB.NET

      Partial Public Class CompoSynchroSyncAgent
          Private Sub OnInitialized()
              Me.Stagiaire.SyncDirection = SyncDirection.Bidirectional
          End Sub
      End Class

     Remarque : pour utiliser l’énumération            SyncDirection,    importer    l’espace    de       nom
     Microsoft.Synchronization.Data.

            Une fois la synchronisation bi-directionnelle mise en œuvre, réalisons l’exercice suivant :

     1 – Lançons notre application, et synchronisons les données, de manière à les afficher.

     2 – Modifions les données d’un stagiaire, et mettons à jour notre base de données locale.

     3 – Sans fermer l’application, ouvrons SQL Server Management Studio, l’interface d’administration
     de SQL Server. Connectons-nous de manière à accéder à la base de données DotnetFrance.

     4 – Ouvrons la table Stagiaire, et modifions les mêmes données du même enregistrement, avec
     d’autres valeurs.

             Que se passe-t-il ? Les données de la base de données locales, y compris les modifications,
     sont écrasées par les données de la base de données distantes. Il s’agit du fonctionnement par
     défaut du composant de synchronisation. Mais est-ce un fonctionnement qui répond à nos attentes ?
     Peut-être que dans certains cas, nous ne souhaitons pas écraser les données de la base de données
     locale ? Ainsi, sans règle fixe, nous souhaitons faire intervenir l’utilisateur, de manière à ce qu’il
     précise pour chacun des conflits de données détectés, quelle donnée fait foi. Pour ce faire, il est
     nécessaire d’apprendre à gérer les conflits de données.

                                Dotnet France Association – James RAVAILLE
24    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

     4 Gestion des conflits de données

     4.1 Présentation des cas de conflit de données
             Comme nous l’avons vu précédemment, une politique de gestion de conflits est nécessaire,
     de manière à résoudre les conflits de données, survenant lors des synchronisations de données. Dans
     notre cas, nous allons gérer le conflit de données suivant : un même donnée, a été modifiée à la fois
     dans la base de données distante, ainsi que dans la base de données locale.

               Pour traiter ce conflit, nous allons afficher une fenêtre à l’utilisateur, afin de présenter à
     l’utilisateur l’état des données en conflit de la base distantes, ainsi que les données correspondantes
     côté client. Il n’aura alors plus qu’à choisir les données à conserver.

     4.2 Présentation du formulaire de gestion de conflits de données
             Voici l’IHM du formulaire que nous allons réaliser :

            L’ensemble des contrôles Textbox de ce formulaire ont leur propriété ReadOnly à True, de
     manière à ce que l’utilisateur ne puisse pas modifier les données.

            Ce formulaire sera affiché pour chaque conflit de données détecté, lors de la synchronisation
     des données concernant les stagiaires.

             Vous pouvez réaliser le design de ce formulaire.

     4.3 Réalisation du formulaire de gestion de conflits de données
               Notre formulaire devra afficher les données contenu dans deux enregistrements en conflits
     (provenant de deux tables différentes). Dans le Framework .NET, les données d’un enregistrement
     peuvent être contenues un objet de type System.Data.DataRow. Pour gérer les données contenues
     dans ces deux enregistrements, nous définissons deus attributs de type DataRow , que nous
     initialisons dans le constructeur de la classe, comme indiqué ci-dessous :

                                Dotnet France Association – James RAVAILLE
25    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      // C#

      public partial class FrmGestionConflitDonnees : Form
      {
          private DataRow StagiaireLocal;
          private DataRow StagiaireDistant;

          public FrmGestionConflitDonnees(DataRow aStagiaireLocal, DataRow
      aStagiaireDistant)
          {
              InitializeComponent();

                 // Initialisation des attributs.
                 this.StagiaireLocal = aStagiaireLocal;
                 this.StagiaireDistant = aStagiaireDistant;
           }
      }

      ' VB.NET

      Public Class FrmGestionConflitDonnees

           Private StagiaireLocal As DataRow
           Private StagiaireDistant As DataRow

          Public Sub New(ByVal aStagiaireLocal As DataRow, ByVal
      aStagiaireDistant As DataRow)

                 ' Cet appel est requis par le Concepteur Windows Form.
                 InitializeComponent()

              ' Initialisation des attributs.
              Me.StagiaireLocal = aStagiaireLocal
              Me.StagiaireDistant = aStagiaireDistant
          End Sub
      End Class

             Puis, nous allons afficher les données contenues dans nos DataRows dans les contrôles de
     l’IHM. Pour ce faire, nous allons implémenter l’évènement Load du formulaire :

      // C#

      private void FrmConflitDonnees_Load(object sender, EventArgs e)
      {
          // Affichage des informations.
          TxtNomLocal.Text = StagiaireLocal["Nom"].ToString();
          TxtPrenomLocal.Text = StagiaireLocal["Prenom"].ToString();
          TxtNomDistant.Text = StagiaireDistant["Nom"].ToString();
          TxtPrenomDistant.Text = StagiaireDistant["Prenom"].ToString();
      }

                              Dotnet France Association – James RAVAILLE
26    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      ' VB.NET

      Private Sub FrmGestionConflitDonnees_Load(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles MyBase.Load
          ' Affichage des informations.
          TxtNomLocal.Text = StagiaireLocal("Nom").ToString()
          TxtPrenomLocal.Text = StagiaireLocal("Prenom").ToString()
          TxtNomDistant.Text = StagiaireDistant("Nom").ToString()
          TxtPrenomDistant.Text = StagiaireDistant("Prenom").ToString()
      End Sub

             Puis, pour enregistrer au sein même du formulaire, le choix de l’utilisateur sur les données
     faisant foi, nous implémentons l’évènement Click sur les deux boutons, et fournissons
     l’implémentation suivante :

      // C#

      private void CmdChoisirDonneesDistantes_Click(object sender, EventArgs e)
      {
          this.DialogResult = DialogResult.No;
      }

      private void CmdChoisirDonneesLocales_Click(object sender, EventArgs e)
      {
          this.DialogResult = DialogResult.Yes;
      }

      ' VB.NET

      Private Sub CmdChoisirDonneesDistantes_Click(ByVal sender As
      System.Object, ByVal e As System.EventArgs) Handles
      CmdChoisirDonneesDistantes.Click
          Me.DialogResult = DialogResult.No
      End Sub

      Private Sub CmdChoisirDonneesLocales_Click(ByVal sender As System.Object,
      ByVal e As System.EventArgs) Handles CmdChoisirDonneesLocales.Click
          Me.DialogResult = DialogResult.Yes
      End Sub

     4.4 Résolution des conflits de données
             Lors de la synchronisation des données, pour que ce formulaire soit affiché
     automatiquement si un conflit de données est rencontré, nous devons étendre la classe
     CompoSynchroServerSyncProvider en créant une classe partielle. Nous pouvons créer cette classe,
     dans la classe code-behind du composant de synchronisation CompoSynchro.sync. Dans cette classe,
     nous allons implémenter la méthode partielle OnInitialized. Dans cette méthode, nous nous
     abonnons à l’évènement ApplyChangeFailed. Nous obtenons alors le code suivant :

                               Dotnet France Association – James RAVAILLE
27    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      // C#

      public partial class CompoSynchroServerSyncProvider
      {
          partial void OnInitialized()
          {
              this.ApplyChangeFailed +=
      CompoSynchroServerSyncProvider_ApplyChangeFailed;
          }

          private void CompoSynchroServerSyncProvider_ApplyChangeFailed(object
      sender, ApplyChangeFailedEventArgs e)
          {

            }
      }

      ' VB.NET

      Partial Public Class CompoSynchroServerSyncProvider
          Private Sub OnInitialized()
              AddHandler Me.ApplyChangeFailed, AddressOf
      CompoSynchroServerSyncProvider_ApplyChangeFailed
          End Sub

          Private Sub CompoSynchroServerSyncProvider_ApplyChangeFailed(ByVal
      sender As Object, ByVal e As ApplyChangeFailedEventArgs)

          End Sub
      End Class

            L’évènement ApplyChangeFailed est levé autant de fois que de conflits de données sont
     détectées.

              Dans le gestionnaire d’évènement CompoSynchroServerSyncProvider_ApplyChangeFailed,
     nous allons implémenter le traitement d’un conflit de données détectées :

            -   Affichage modal du formulaire de gestion de conflits. Une fois que l’utilisateur clique sur
                un des boutons pour indiquer son choix, le formulaire se ferme.
            -   En fonction de la valeur de la propriété DialogResult, on détermine sur quel bouton
                l’utilisateur a cliqué. Si la propriété DialogResult vaut DialogResult.Yes, alors les données
                de la base de données cliente concernant le stagiaire écrasent celles sur le serveur. Si la
                propriété DialogResult vaut DialogResult.No, alors les données de la base de données
                distante concernant le stagiaire écrasent celles dans la base de données cliente.

            L’action à réaliser est précisée au travers de l’attribut Action de l’argument du gestionnaire
     d’évènement :

            -   Si nous précisions la valeur ApplyAction.RetryWithForceWrite, alors les données de la
                base de données distante sont mises à jour avec les données de la base de données
                locale.
            -   Si nous précisions la valeur ApplyAction.Continue, alors les données de la base de
                données locale sont mises à jour avec les données de la base de données locale.

                               Dotnet France Association – James RAVAILLE
28    Premiers pas avec Microsoft Synchronization Framework For ADO .NET

      // C#

      private void clientServerSyncProvider_ApplyChangeFailed(object sender,
      ApplyChangeFailedEventArgs e)
      {
          // Variables locales.
          DialogResult oResult;
          FrmConflitDonnees oFrmConflitStagiaire;

             if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerUpdate)
             {
                 oFrmConflitStagiaire = new FrmConflitDonnees(
                     e.Conflict.ClientChange.Rows[0],
                     e.Conflict.ServerChange.Rows[0]);
                 oResult = oFrmConflitStagiaire.ShowDialog();

              if (oResult == DialogResult.Yes)
              {
                   e.Action =
      Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite;
              }
              else
              {
                   e.Action =
      Microsoft.Synchronization.Data.ApplyAction.Continue;
              }
          }
      }

      ' VB.NET

      Private Sub CompoSynchroServerSyncProvider_ApplyChangeFailed(ByVal sender
      As Object, ByVal e As ApplyChangeFailedEventArgs)
          ' Variables locales.
          Dim oResult As DialogResult
          Dim oFrmConflitStagiaire As FrmGestionConflitDonnees

             If e.Conflict.ConflictType = ConflictType.ClientUpdateServerUpdate
      Then
                 oFrmConflitStagiaire = New FrmGestionConflitDonnees( _
                     e.Conflict.ClientChange.Rows(0), _
                     e.Conflict.ServerChange.Rows(0))
                 oResult = oFrmConflitStagiaire.ShowDialog()

              If (oResult = DialogResult.Yes) Then
                   e.Action =
      Microsoft.Synchronization.Data.ApplyAction.RetryWithForceWrite
              Else
                   e.Action =
      Microsoft.Synchronization.Data.ApplyAction.Continue
              End If
          End If
      End Sub

             Si nous exécutons de nouveau le même scénario que précédemment (modification du nom
     du stagiaire côté client en spécifiant EMATOU, et modification du nom du stagiaire côté serveur en
     spécifiant EMAT), la fenêtre suivante apparaît :

                               Dotnet France Association – James RAVAILLE
29   Premiers pas avec Microsoft Synchronization Framework For ADO .NET

          En fonction du choix de l’utilisateur, les données seront mises à jour et synchronisées.

                             Dotnet France Association – James RAVAILLE
Vous pouvez aussi lire