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 2967 fois, 7 pages
 6 | Résolution Complexe ou conditionnelle
Supposons que vous souhaitiez établir une règle de priorité, conditionnée par les données elle-même.
Vous devez établir qui a raison, du client ou du serveur, suivant les données en conflit.
Dans le cas Update-Update, que nous étudions, changeons la règle métier qui devient :
« L’adresse mise à jour est celle contenant le plus de caractères »
Dans ce cas là, la résolution devient « conditionnelle ».
Par « tout blanc, tout noir » nous sous-entendons, la résolution du conflit sera soit la version cliente, soit la version serveur. Par lui suite nous établirons le cas « mi-teinte » où les données seront fusionnées.
Mais revenons à notre cas de résolution conditionnelle, simple : Il va nous falloir jouer sur l’action à appliquer suivant les données en entrées.
Pour cela, à notre disposition :
  • La ligne en conflit provenant du client : e.Conflict.ClientChange.Rows[0]
  • La ligne en conflit déjà mise à jour sur le serveur : e.Conflict.ServerChange.Rows[0]
  • La ligne complète, résultat, qui doit (ou pas être mise à jour) :
  • DataRow drToChange = serveurChanges.Select( String .Format( "ClientId = '{0}'" , drClient[ "ClientId" ].ToString()))[0];
  • L’action à effectuer : e.Action
Les étapes de résolution, et de prise de position consistent à comparer les deux enregistrements, décider qui des deux a la priorité, de reporter le résultat dans la ligne complète, et d’appliquer la bonne action : ApplyAction.RetryWithForce si nous décidons que le client a raison, ou ApplyAction.Continue, si nous décidons que c’est le serveur qui l’emporte.
La résolution, par code devient :

DataRow drClient = e.Conflict.ClientChange.Rows[0];

DataRow drServeur = e.Conflict.ServerChange.Rows[0];

 

// Récupération du Prénom et du Nom à remplacer

string adresseFromClient = drClient["Adresse"] as string;

string adresseFromServeur = drServeur["Adresse"] as string;

 

// Récuparation de la table qui va être mise à jour coté serveur

DataTable serveurChanges = e.Context.DataSet.Tables[0];

// Puis la ligne

DataRow drToChange = serveurChanges.Select(

    String.Format("ClientId = '{0}'", drClient["ClientId"].ToString()))[0];

 

// Mise à jour

if (adresseFromClient.Length > adresseFromServeur.Length)

{

    // Ici le client gagne, on force l'application coté serveur

    // Rien ne remontera coté client puisque l'enregistrement est déjà correct.

    drToChange["Adresse"] = adresseFromClient;

    e.Action = ApplyAction.RetryWithForceWrite;

}

else

{

    // Dans ce cas, le serveur gagne, la ligne reviendra en état de modification coté client

    e.Action = ApplyAction.Continue;

}

return;

Résolution conditionnelle terminée !
Que se passe-t-il si notre règle métier devient : « Prenez la concaténation de la version cliente + la version serveur, en précisant d’où vient quoi »
Exemple sur notre adresse :
  • L’adresse renseignée depuis le client est : « 1 Avenue Charles 1er »
  • L’adresse renseignée coté serveur est « 2 Avenue Charles 1er »
  • Le résultat doit être « Fusion de champs : Client [1 Avenue Charles 1er] , Serveur [2 Avenue Charles 1er ] A VERIFIER »
Nous avons une problématique à résoudre sur l’action a mener, une fois la fusion de la ligne principale effectuée
  • Si nous appliquons l’action ApplyAction.RetryWithForce, la ligne sera bien mise à jour coté serveur, mais celle-ci n’étant pas renvoyée coté client, nous avons deux versions différentes de chaque coté.
  • Si nous appliquons ApplyAction.Continue, rien ne se passe et nous perdons notre fusion.
Aujourd’hui, il n’existe pas de solution miracle, intégrée à Sync. Services sur ce genre de conflit.
Nous allons devoir nous adapter, en adoptant une solution mixte :
  • Premièrement, l’action à appliquer sera bien ApplyAction.RetryWithForce. De ce fait, la ligne fusionnée est bien écrite en base de données coté serveur.
  • Deuxièmement, pour s’assurer que cette ligne sera renvoyée au client ; une fois l’ensemble des mises à jour effectuées, nous allons faire un update « à blanc » de la ligne pour faire évoluer le critère de dernière mise à jour (Timestamp ou Long) pour que la ligne soit vue comme une ligne à mettre à jour coté client.
Nous avons à notre disposition un évènement levé par le fournisseur de synchronisation coté serveur qui nous notifie lorsque l’ensemble des modifications ont été apportées.

/// <summary>

/// Nous mettons à jour les lignes pour générer des lignes à renvoyer coté client

/// </summary>

private void ServerChangesApplied(object sender, ChangesAppliedEventArgs e)

{

 

}

De plus nous avons en argument, la connexion en cours ainsi que la transaction courante. Tout va bien, nous sommes dans un contexte transactionnel, notre modification fera entièrement parti d’un groupe de changement atomique.
Avant de remplir notre méthode, nous nous abonnons à l’évènement levé par le fournisseur de synchronisation, et nous créons une liste de guids contenant les guids à mettre à jour :

/// <summary>

/// Stocke les id à mettre à jour (Exemple 2 Update-Update )

/// </summary>

private List<Guid> lstClientIdToUpdate;

 

partial void OnInitialized()

{

    this.ApplyChangeFailed += ServerApplyChangeFailed;

 

    // Utile pour la résolution de conflit conditionnelle avec fusion des champs (Exemple 2 Update-Update )

    this.ChangesApplied += ServerChangesApplied;

    this.lstClientIdToUpdate = new List<Guid>();

}

 

Nous résolvons le conflit dans la méthode ServerChangedFailed, sans oublier de bien remplir la liste de Guids :

DataRow drClient = e.Conflict.ClientChange.Rows[0];

DataRow drServeur = e.Conflict.ServerChange.Rows[0];

 

// Récupération du Prénom et du Nom à remplacer

string adresseFromClient = drClient["Adresse"] as string;

string adresseFromServeur = drServeur["Adresse"] as string;

 

// Récuparation de la table qui va être mise à jour coté serveur

DataTable serveurChanges = e.Context.DataSet.Tables[0];

// Puis la ligne

DataRow drToChange = serveurChanges.Select(

    String.Format("ClientId = '{0}'", drClient["ClientId"].ToString()))[0];

 

String newAddress = string.Format("CONFLIT : Version client : {0} Version Serveur : {1} ",

                                  adresseFromClient,

                                  adresseFromServeur);

drToChange["Adresse"] = newAddress;

 

e.Action = ApplyAction.RetryWithForceWrite;

 

// Ajout de cette ligne à la liste des lignes à mettre à jour

this.lstClientIdToUpdate.Add((Guid)drClient["ClientId"]);

Enfin lors de la levée d’évènement de fin de mise à jour, nous effectuons notre mise à jour à blanc :

/// <summary>

/// Nous mettons à jour les lignes pour générer des lignes à renvoyer coté client

/// </summary>

private void ServerChangesApplied(object sender, ChangesAppliedEventArgs e)

{

 

    if (lstClientIdToUpdate.Count > 0)

    {

        foreach (Guid clientId in lstClientIdToUpdate)

        {

            SqlCommand updateTable = new SqlCommand();

            updateTable.Connection = (SqlConnection)e.Connection;

            updateTable.Transaction = (SqlTransaction)e.Transaction;

 

            updateTable.CommandText = "UPDATE dbo.Client SET ClientId = @ClientId Where ClientId = @ClientId";

 

            SqlParameter p = new SqlParameter("@ClientId", SqlDbType.UniqueIdentifier);

            p.Value = clientId;

 

            updateTable.Parameters.Add(p);

 

            updateTable.ExecuteNonQuery();

 

        }

    }

}

La ligne deviant une ligne de mise à jour à envoyer au client, elle est renvoyée sur le GetChanges serveur, notre conflit est résolu !
Les deux exemples précédents sont remarquables sur un point particulier, qui me semble très important :
Quelque soit la solution de conflit adopter, nous n’avons pas eu besoin de mettre à jour la partie client ! Seul la partie serveur a du évoluer.
Vous êtes donc capable, sans avoir a recompiler votre client, de résoudre des conflits via un moteur de règle évolutif, par exemple.
 
» Démarrer une discussion
 
Discussion démarée par realtn le 01/02/2010 à 16:58, 1 commentaire(s).