locked
Conflict - ClientUpdateServerUpdate - Let Client Win RRS feed

  • Question

  •  

    Hi.

     

    When an Update-Update conflict arises I want the value of updated row of the client to overwrite that on the server.  It was my understanding that to do this I should subscribe to the ApplyChangeFailed event for my server Sync Provider (residing within a WebService) and apply the RetryWithForceWrite action. 

     

    void oracleSyncProvider_ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)

    {

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

         {

              e.Action = ApplyAction.RetryWithForceWrite;

         }

         else

         {

              e.Action = ApplyAction.Continue;

         }

    }

     

    When I do this a "target of an invocation" exception is thrown because the ApplyChanges method times out.  What am I doing wrong?

     

    Thanks.

    • Moved by Max Wang_1983 Friday, April 22, 2011 6:40 PM forum consolidation (From:SyncFx - Microsoft Sync Framework Database Providers [ReadOnly])
    Friday, July 18, 2008 9:25 PM

Answers

  • It seems the udpate query is incorrect. the server provider relies on the value of the OUT parameter: sync_row_count to determinte if the row is applied sucessfuly or not. in your case, since your command doesn't support this. it will alway think the row is not sucessfuly applied and reports it as the conflict, since you set the RetrywithForceWrite on, the query will be raised again and again, hence the infinate you are experiencing.

     

    Could you modify your command and to see if this fixed it.

     

    thanks

    Yunwen

    Thursday, August 7, 2008 10:14 PM
    Moderator

All replies

  • would you debug the server side or log out the details on the server side ?  How long do you expect the sync to be complete ?

     

    the "target of an invocation" exception with a timeout is very generic on the client side.

     

    thanks

    Yunwen

    Friday, July 25, 2008 6:42 PM
    Moderator
  •  

    While debugging on the server shows me that the "ApplyChangeFailed" event keeps firing after each attempt to force the write until the server timeout occurs.  The value of e.Conflict.ServerChange remains the same after each firing.  Is there something else I need to be doing other than applying the "RetryWithForceWrite" action in the ApplyChangeFailed event to get these changes forcefully written?
    Tuesday, July 29, 2008 10:08 PM
  •  

    for a update-update conflict, use RetryWithForceWrite should be good enought to have the rows override the existing one at the server db.

     

    In order to let the this action to take effect, you will need to have the right logic in you update query at the DbSyncAdapter. the logic should be something like:

    update MyTable Set Col1 = @1, Col2=@2 where pkCol = @pkVal and ( @retryWIthForceWrite = 1 OR ( other nomral conditions for a normal update ) ).

     

    the Action of RetryWIthForceWrite only turns on the retryWithForceWrite flag in the query, so if your query doesn't have this, it will end up an infinate loop since the row will never updated.

     

    Could you check if this is the case, I know you are using Oracle server, so the query syntax could be bit different ( e.g. the parameter prefix ).

     

    thanks

    Yunwen

     

     

     

    Wednesday, July 30, 2008 6:05 AM
    Moderator
  • Thanks Yunwen,

     

    Things are starting to make sense.  I did not have the SyncSession.SyncForceWrite parameter in my query that updates the server.  So when a "ApplyChangeFailed" event occurs and the "RetryWithForceWrite" action is taken, the update query is retried with the bit switched on.  Completely logical. I have made the change but I am still having trouble though.  Now, when debugging on the server, it hits the "ApplyChangeFailed" event, sets the action to "RetryWithForceWrite", and, I'm guessing, tries to do the rewrite.  It then hangs and eventually times out with no further information other than the ubiquitous "Timed Out" error.  It no longer infinate loops but, as soon as I step out of the "ApplyChangeFailed" event, all debugging ceases.  It's like it has sent the update command to the server but it gets hung there.  Any ideas as to what is happening?

    Wednesday, July 30, 2008 11:26 PM
  • Could you get the query and run it against the server directly to see if this can repro ? does Oracle has the similar thing as SQL profiler so we can track the querries on the server?

     

    thanks

    Yunwen

    Thursday, July 31, 2008 12:30 AM
    Moderator
  •  ryeandi wrote:

    It no longer infinate loops but, as soon as I step out of the "ApplyChangeFailed" event, all debugging ceases.

     

    Scratch the above.  It does still infinate loop.  Has anyone been able to accomplish a force write to Oracle?  If so, could you please post your UpdateCommand for your SyncAdapter complete with parameters?  This brings up another question, when adding the  SyncSession.SyncForceWrite parameter to the SyncAdapter's UpdateCommand, what

    OracleType do you use?  All of the conflict resolution examples I've seen involve SqlServer where the parameters are passed as SqlDbType.Bit.  Oracle does not have an equivalent.  I've been using OracleType.Int16 but have tried most of the ones that could possibly make sense hoping that this might solve the infinate loop problem.  None have worked.  Please help as this is quite annoying. 

    Thursday, July 31, 2008 5:13 PM
  •  

    Thanks. We are trying to repro this and will post what we found or we need more info for this issue.

     

    thanks

    Yunwen

    Friday, August 1, 2008 4:49 AM
    Moderator
  • I have tried a few cases and cannot repro the infinate loop.

     

    here is my updateCommand and the selectConflictUpdateCommand:

     

    cmd.CommandText

    "BEGIN update TABLE_1 set ID = :ID , C2 = :C2 , C3 = :C3 , C4 = :C4 , C5 = :C5 , C6 = :C6 , C7 = :C7 , C8 = :C8 , C9 = :C9 , sync_Update_originator_id = Tongue Tiedync_originator_id where ID = :ID AND ( Tongue Tiedync_force_write = 1 OR local_timestamp <= Tongue Tiedync_last_received_anchor ) ; Tongue Tiedync_row_count := SQL%ROWCOUNT; END; "

     

    here is the code I used to setup the update and selectConclif. sorry I cannot put all the code here due to the length restriction. but the most important things are the command query, parameters, especially the output rowcount parameter. I simply use the OracleType.Int32 for the SyncForceWrite session variable.

     

    Code Snippet

    updateCmd = new OracleCommand();

    updateCmd.CommandText = string.Format("BEGIN update {0} set {1} sync_Update_originator_id = :{2} {3} ; :{4} := SQL%ROWCOUNT; END; ", tableName, updateClause, SyncSession.SyncOriginatorId, whereClauseWithSessionVar, SyncSession.SyncRowCount);

    SetupCommandParameters(updateCmd, schematable, collist);

    updateCmd.Parameters.Add(":" + SyncSession.SyncOriginatorId, System.Data.OracleClient.OracleType.Char, 32, "sync_Update_originator_id");

    updateCmd.Parameters.Add(":" + SyncSession.SyncRowCount, System.Data.OracleClient.OracleType.Int32);

    updateCmd.Parameters[":" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;

    updateCmd.Parameters.Add(":" + SyncSession.SyncForceWrite, OracleType.Int32);

    updateCmd.Parameters.Add(":" + SyncSession.SyncLastReceivedAnchor, anchorType);

    // conflict commands

    selectConflictUpdateCommand = new OracleCommand();

    selectConflictUpdateCommand.CommandText = string.Format("select {0} from {1} {2} ", selectClause, tablename, whereClause);

    SetupCommandParameters(selectConflictUpdateCommand, schematable, pkCols);

     

     

    could you check you code to make sure the query logic and the parameters are all bound, for both the updateCommand and the selectConflictCommand? if you still see the infinate loop after that, would you share some info about:

    1. the call stack

    2. the conflict error in your ApplyChangeFailed event arg.

    3. your code snippet, such as I put here ?

     

    thanks

    Yunwen

    Sunday, August 3, 2008 3:47 AM
    Moderator
  • Any update to this thread folks ?

     

    thanks

    Yunwen

    Wednesday, August 6, 2008 11:33 PM
    Moderator
  •  

    Hi Yunwen,

     

    Yes, I'm still getting the infinate loop.  As requested:

     

    1)  Call stack after "time out" exception is thrown while running the Synchronize method.

     

    at System.RuntimeMethodHandle._InvokeMethodFast(Object target, Object[] arguments, SignatureStruct& sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)

    at System.RuntimeMethodHandle.InvokeMethodFast(Object target, Object[] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeTypeHandle typeOwner)

    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture, Boolean skipVisibilityChecks)

    at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)

    at Microsoft.Synchronization.Data.ServerSyncProviderProxy.ApplyChanges(SyncGroupMetadata groupMetadata, DataSet dataSet, SyncSession syncSession)

    at Microsoft.Synchronization.SyncAgent.UploadChanges(SyncGroupMetadata groupMetadata)

    at Microsoft.Synchronization.SyncAgent.Synchronize()

    at EventManager.EventControlPanel.dbSync() in C:\Users\pkp5\Documents\Visual Studio 2008\Projects\EventManager\EventManager\EventControlPanel.cs:line 1065

     

    2)  Conflict error in "ApplyChangeFailed" event arg. - None.  While trying to sync no error occurs it just infinately tries to do the forced write and keeps conflicting with the ClientUpdateServerUpdate ConflictType.

     

    3)  Here's some code.

     

    -- Update Command --

    Works fine when no conflict occurs which tells me that it's formatted correctly and the parameters are bound.

    Code Snippet

    //Command to update the server table rows that have been updated in the client DB

    OracleCommand updateEventsCmd = new OracleCommand();

    updateEventsCmd.CommandType = CommandType.Text;

    updateEventsCmd.CommandText =

    "UPDATE nau_event " +

    "SET EventName = :EventName," +

    " EventStatus = :EventStatus," +

    " EventStart = :EventStart," +

    " EventEnd = :EventEnd," +

    " EventVenue = :EventVenue," +

    " EventGroup = :EventGroup," +

    " EventBudget = :EventBudget," +

    " EventInviteeFee = :EventInviteeFee," +

    " EventSpouseFee = :EventSpouseFee," +

    " EventAdultGuestFee = :EventAdultGuestFee," +

    " EventChildGuestFee = :EventChildGuestFee," +

    " EventVenueCharges = :EventVenueCharges," +

    " EventAutoManageVenueCharges = :EventAutoManageVenueCharges," +

    " amd_originator_id = :" + SyncSession.SyncClientIdHash + " " +

    "WHERE ActivityCode = :ActivityCode" +

    " AND (" +

    " :" + SyncSession.SyncForceWrite + " = 1" +

    " OR date_modified <= :" + SyncSession.SyncLastReceivedAnchor +

    " )";

    updateEventsCmd.Parameters.Add(":EventName", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventStatus", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventStart", OracleType.DateTime);

    updateEventsCmd.Parameters.Add(":EventEnd", OracleType.DateTime);

    updateEventsCmd.Parameters.Add(":EventVenue", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventGroup", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventBudget", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventInviteeFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventSpouseFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventAdultGuestFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventChildGuestFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventVenueCharges", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventAutoManageVenueCharges", OracleType.Number);

    updateEventsCmd.Parameters.Add(":ActivityCode", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncClientIdHash, OracleType.Number);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncLastReceivedAnchor, OracleType.Timestamp);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncForceWrite, OracleType.Int32);

    updateEventsCmd.Connection = oraConn;

    eventsSyncAdaptor.UpdateCommand = updateEventsCmd;

     

     

    -- SelectConflictUpdatedRowsCommand --

    Code Snippet

    //Command to get the rows where an insert, update, or delete returned failed (either direction)

    OracleCommand updateConflictEventsCmd = new OracleCommand();

    updateConflictEventsCmd.CommandType = CommandType.Text;

    updateConflictEventsCmd.CommandText =

    "SELECT " +

    " ActivityCode," +

    " EventName," +

    " EventStatus," +

    " EventStart," +

    " EventEnd," +

    " EventVenue," +

    " EventGroup," +

    " EventBudget," +

    " EventInviteeFee," +

    " EventSpouseFee," +

    " EventAdultGuestFee," +

    " EventChildGuestFee," +

    " EventVenueCharges," +

    " EventAutoManageVenueCharges " +

    "FROM " +

    " nau_event " +

    "WHERE ActivityCode = :ActivityCode";

    updateConflictEventsCmd.Parameters.Add(":ActivityCode", OracleType.VarChar);

    updateConflictEventsCmd.Connection = oraConn;

    eventsSyncAdaptor.SelectConflictUpdatedRowsCommand = updateConflictEventsCmd;

     

     

    I did notice that when stepping through debugging that the first time that the "ApplyChangeFailed" event is raised that the

    e.Conflict.ClientChange value seems is different from e.Conflict.ServerChange and on each successive call they appear to be the same.  It seems strange to me thatthe Update-Update conflict would continue to be identified when the rows appear to be the same.  Any new thoughts?

    Thursday, August 7, 2008 6:51 PM
  • It seems the udpate query is incorrect. the server provider relies on the value of the OUT parameter: sync_row_count to determinte if the row is applied sucessfuly or not. in your case, since your command doesn't support this. it will alway think the row is not sucessfuly applied and reports it as the conflict, since you set the RetrywithForceWrite on, the query will be raised again and again, hence the infinate you are experiencing.

     

    Could you modify your command and to see if this fixed it.

     

    thanks

    Yunwen

    Thursday, August 7, 2008 10:14 PM
    Moderator
  • We have a winner!  Way to go Yunwen. 

     

    The following code succesfully let the client win:

    Code Snippet

    //Command to update the server table rows that have been updated in the client DB

    OracleCommand updateEventsCmd = new OracleCommand();

    updateEventsCmd.CommandType = CommandType.Text;

    updateEventsCmd.CommandText =

    "BEGIN " +

    " UPDATE nau_event " +

    "    SET EventName = :EventName," +

    "        EventStatus = :EventStatus," +

    "        EventStart = :EventStart," +

    "        EventEnd = :EventEnd," +

    "        EventVenue = :EventVenue," +

    "        EventGroup = :EventGroup," +

    "        EventBudget = :EventBudget," +

    "        EventInviteeFee = :EventInviteeFee," +

    "        EventSpouseFee = :EventSpouseFee," +

    "        EventAdultGuestFee = :EventAdultGuestFee," +

    "        EventChildGuestFee = :EventChildGuestFee," +

    "        EventVenueCharges = :EventVenueCharges," +

    "        EventAutoManageVenueCharges = :EventAutoManageVenueCharges," +

    "        amd_originator_id = :" + SyncSession.SyncClientIdHash + " " +

    " WHERE ActivityCode = :ActivityCode" +

    "   AND (" +

    "           :" + SyncSession.SyncForceWrite + " = 1" +

    "        OR date_modified <= :" + SyncSession.SyncLastReceivedAnchor +

    "      );" +

    " " +

    " :" + SyncSession.SyncRowCount + " := SQL%ROWCOUNT;" +

    "END;";

     

    updateEventsCmd.Parameters.Add(":EventName", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventStatus", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventStart", OracleType.DateTime);

    updateEventsCmd.Parameters.Add(":EventEnd", OracleType.DateTime);

    updateEventsCmd.Parameters.Add(":EventVenue", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventGroup", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":EventBudget", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventInviteeFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventSpouseFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventAdultGuestFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventChildGuestFee", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventVenueCharges", OracleType.Number);

    updateEventsCmd.Parameters.Add(":EventAutoManageVenueCharges", OracleType.Number);

    updateEventsCmd.Parameters.Add(":ActivityCode", OracleType.VarChar);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncClientIdHash, OracleType.Number);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncLastReceivedAnchor, OracleType.Timestamp);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncForceWrite, OracleType.Int32);

    updateEventsCmd.Parameters.Add(":" + SyncSession.SyncRowCount, OracleType.Int32);

    updateEventsCmd.Parameters[":" + SyncSession.SyncRowCount].Direction = ParameterDirection.Output;

    updateEventsCmd.Connection = oraConn;

    eventsSyncAdaptor.UpdateCommand = updateEventsCmd;

     

     

    I find it curious that "Un-Conflicted" updates perform just fine without the need of a rowcount.  I was under the impression that it tried to run the UpdateCommand once with the "ForceWrite" bit turned off and after it failed it tried it again with the bit turned on.  I'm still not exactly sure where the SelectConflictUpdatedRowsCommand comes into all of this.  I always thought of things going something like this:

     

    1 - Get all of the rows that the server has for the client to update via the SelectIncrementalUpdatesCommand

    2 - Get all of the rows that the client has for the server to update via the UpdateCommand

    3 - Get all of the rows that are in both data sets returned from 1 & 2 via the SelectConflictUpdatedRowsCommand

    4 - Apply the changes that were not in both data sets returned from 1 & 2 appropriately.

    5 - If 3 returned rows, raise the "ApplyChangeFailed" event passing the data sets returned from 1 & 2 in the args.

    6 - If ApplyAction.Continue then server data set "wins", if ApplyAction.RetryWithForceWrite then client data set "wins".

     

    Obviously there is more going on here.  Anyway, I'm happy because things are working.  Thanks so much.

     

    Thursday, August 7, 2008 11:29 PM