Sébastien Pertus
Chapitre IV : Sync Services for ADO.NET. Créer un Custom Resolver
Création d’un système de résolution des conflits pendant une synchronisation entre un serveur de données et un client consommateur. Utilisation de Sync Services for ADO.Net
Par Sébastien Pertus publié le 20/10/2008 à 00:42, lu 1639 fois, 7 pages
 4 | Résolution d’un conflit coté serveur
La solution de résolution la plus simple reste le choix totalitaire entre la version cliente ou la version serveur.
Dans la plupart des cas, le référentiel restant le serveur, on peut même dire que le choix le plus simple reste que le serveur a toujours raison lors de la levée d’un conflit, bien que Sync. Services nous permet d’adopter une attitude différente sur chacun des conflits possibles.
Lors d’un conflit, chacun des providers va lever un évènement lorsque celui-ci survient sur leur source de données respectives : ApplyChangedFailed.
Il vous donne la main par la suite pour apporter une solution.
Vous aurez alors la possibilité de gérer le ou les conflits survenus, avec entre autre la possibilité de :
  • Forcer le changement, et passer outre le conflit.
  • Retenter d'appliquer le changement (la ligne en conflit peut très bien évoluée entre temps, même si ce cas est très improbable).
  • Résoudre le conflit, par votre propre code et forcer le changement.
  • Ignorer la ligne en conflit.
Cette résolution est ce que l’on nomme l’action à mener. Il existe une énumération que nous aurons à manipuler pour donner l’action à réaliser :

public enum ApplyAction

{

    Continue = 0,

    RetryApplyingRow = 1,

    RetryWithForceWrite = 2,

}

  • L’action Continue stipule que le conflit est déjà résolu, sous entendu c’est la version du fournisseur en cours qui gagne.
  • L’action RetryApplyingRow tente de réappliquer la ligne en conflit, sait-on jamais ! Attention, on peut vite partir en boucle infini, si le conflit ne résout pas de « lui-même » (croyez moi sur parole, j’ai donné !)
  • L’action RetryWithForce écrase la version du fournisseur en cours, avec la version venant de l’autre fournisseur, sous entendu la version du fournisseur en cours perd.
Voici les règles de résolution que nous allons imposer sur notre projet :
1) Lors de la suppression d’un enregistrement, si celui-ci a été modifié de l’autre coté, la suppression l’emporte toujours.
Ce qui se traduit par :
  • ClientUpdateServerDelete : Le serveur l’emporte
  • ClientDeleteServerUpdate : Le client l’emporte
2) Lors d’une mise à jour d’un enregistrement des deux cotés, l’enregistrement qui prime sera celui provenant du serveur
Ce qui se traduit par :
  • ClientUpdateServerUpdate : Le serveur l’emporte
3) Si par hasard, par le plus grand des hasards, un enregistrement est inséré en double des deux cotés, , l’enregistrement qui prime sera celui provenant du serveur
Ce qui se traduit par :
  • ClientInsertServerInsert : Le serveur l’emporte
Nous ouvrons, coté Serveur WCF, le code de notre objet sync.
Le designer rajoute un fichier code, nous permettant de ne pas toucher au code généré.
L’apport des méthodes partielles nous permet de plus d’itérer sur le OnInitialized de la classe.
 
/content/17ca4685-7c88-4fcc-9de9-44e7e7cb9bf2/image6.gif
 

partial void OnInitialized()

{

    this.ApplyChangeFailed += ServerApplyChangeFailed;

 

}

 

 

/// <summary>

/// Resolver de conflits

/// Les règles de résolutions sont :

///    * ClientDeleteServerUpdate : Le client l'emporte. La suppression coté client est maintenue.

///    * ClientUpdateServerDelete : Le Serveur l'emporte. La mise à jour coté client est annulée.

///    * ClientInsertServerInsert : Le Serveur l'emporte. L'insertion client est annulée.

///    * ClientUpdateServerUpdate : Le Serveur l'emporte. La mise à jour coté client est annulée.

/// </summary>

private void ServerApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)

{

    if (e.TableMetadata.TableName == "Client")

        ResolveTableClientConflict(e);

}

 

private void ResolveTableClientConflict(ApplyChangeFailedEventArgs e)

{

Notez que déjà nous différencions l’origine des conflits par table. Ce renseignement nous ait donné via la propriété TableMetadata de notre EventArgs de type ApplyChangeFailedEventArgs.
Maintenant, il nous faut, dans la méthode ResolveTableClientConflict, distinguer les types de conflits que nous pouvons rencontrer. Le conflit en cours, et son type, étant aussi une propriété de notre EventArgs :

private void ResolveTableClientConflict(ApplyChangeFailedEventArgs e)

{

 

    //

    // ClientUpdateServerUpdate

    //

    // Ici Résolution conditionnelle

    // Le retour ou non vers le client d'une ligne en conflit dépend d'une règle métier imposée

    if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerUpdate)

    {

    }

 

    //

    // ClientDeleteServerUpdate

    //

    // Ici le client l'emporte

    // La suppression coté cliente l'emporte, les données modifiées coté serveur sont effacées

    // On force donc la suppression

    if (e.Conflict.ConflictType == ConflictType.ClientDeleteServerUpdate)

    {

    }

 

    //

    // ClientUpdateServerDelete

    //

    // Ici le serveur l'emporte

    // Les changements devront bien être appliqués coté client

    // Le serveur ayant raison, on applique pas la mise à jour, on continue donc.

    if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerDelete)

    {

    }

 

    //

    // ClientInsertServerInsert

    //

    // Ici le serveur l'emporte

    // Les changements devront bien être appliqués coté client

    // Le serveur ayant raison, on ne garde que l'insertion coté serveur

    if (e.Conflict.ConflictType == ConflictType.ClientInsertServerInsert)

    {

    }

 

}

Occupons nous du cas ClientUpdateServerUpdate. Nous avions établi que le serveur gagnait toujours dans ce cas là.
Nous nous trouvons coté serveur, l’action à mener sur ce conflit est donc ApplyAction.Continue :
Nous n’appliquons pas de modification à la ligne serveur.

if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerUpdate)

{

    e.Action = ApplyAction.Continue;

    return;

}

Que va-t-il se passer lors du retour vers le client ?
Et bien une ligne coté serveur est bien en modification, et va être rapatriée coté client. Celui-ci (le fournisseur client) va mettre à jour la ligne comme n’importe quelle autre ligne qui aurait été modifiée coté serveur.
 
/content/17ca4685-7c88-4fcc-9de9-44e7e7cb9bf2/image7.gif
 
Notre conflit est résolu !
Analysons maintenant la résolution du conflit si nous avions utilisé un autre type d’action:
Dans ce cas de figure, la ligne coté client prime sur la ligne coté serveur. La ligne va donc être mise à jour sur le serveur, écrasant donc la version Serveur.
Celle-ci existant donc en bonne version sur le client, elle n’a pas d’intérêt à revenir coté client.
 
/content/17ca4685-7c88-4fcc-9de9-44e7e7cb9bf2/image8.gif
 
Ce cas de figure est, à mon avis, à bannir. Le fournisseur serveur va tenter de réappliquer la ligne, sans toucher ou sans forcer sa mise à jour. Résultat : une boucle infinie.
 
/content/17ca4685-7c88-4fcc-9de9-44e7e7cb9bf2/image9.gif
 
Voici le code de résolution complète coté serveur, si nous suivons correctement les règles imposées par nos règles métiers :

private void ResolveTableClientConflict(ApplyChangeFailedEventArgs e)

  {

 

      //

      // ClientUpdateServerUpdate

      //

      if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerUpdate)

      {

          e.Action = ApplyAction.Continue;

          return;

      }

 

      //

      // ClientDeleteServerUpdate

      //

      // Ici le client l'emporte

      // La suppression coté cliente l'emporte, les données modifiées coté serveur sont effacées

      // On force donc la suppression

      if (e.Conflict.ConflictType == ConflictType.ClientDeleteServerUpdate)

      {

          e.Action = ApplyAction.RetryWithForceWrite;

          return;

      }

 

      //

      // ClientUpdateServerDelete

      //

      // Ici le serveur l'emporte

      // Les changements devront bien être appliqués coté client

      // Le serveur ayant raison, on applique pas la mise à jour, on continue donc.

      if (e.Conflict.ConflictType == ConflictType.ClientUpdateServerDelete)

      {

          e.Action = ApplyAction.Continue;

          return;

      }

 

      //

      // ClientInsertServerInsert

      //

      // Ici le serveur l'emporte

      // Les changements devront bien être appliqués coté client

      // Le serveur ayant raison, on ne garde que l'insertion coté serveur

      if (e.Conflict.ConflictType == ConflictType.ClientInsertServerInsert)

      {

          e.Action = ApplyAction.Continue;

          return;

      }

 

  }

 
» Démarrer une discussion
 
Discussion démarée par realtn le 01/02/2010 à 16:58, 1 commentaire(s).