Answered by:
Uploads Failed -- Server Side...

Question
-
Right now when this happens I "continue" how can I force the changes to write to the server even if there is a failed upload? Right now I can do this with the client via something along the lines of what a user on here gave me, will this work for the server?
private static void _ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)
{
if ((e.Conflict.ErrorMessage ?? "").Contains("A duplicate value cannot be inserted into a unique index."))
{
if (e.Conflict.ClientChange.Rows.Count > 0)
{
e.Conflict.ClientChange.Rows.Remove(e.Conflict.ClientChange.Rows[0]);
e.Action = ApplyAction.RetryWithForceWrite;
}
return;
}
else
{
var Message = String.Format("Local Apply Changed for {1} Error at {0}", e.Conflict.SyncStage, e.Conflict.ServerChange != null ? e.Conflict.ServerChange.TableName : e.Conflict.ClientChange.TableName);
Message = (e.Conflict.ErrorMessage ?? "Unkown Error") + Environment.NewLine + Message;
//Log.LogException(Message, EventLogEntryType.Error);
Console.WriteLine("Sync Conflict Occurred: " + e.Conflict.ErrorMessage);
if (e.Conflict.ConflictType != ConflictType.ErrorsOccurred && e.Conflict.ConflictType != ConflictType.Unknown)
e.Action = ApplyAction.Continue;
}
}
}
TravichWednesday, March 31, 2010 5:49 PM
Answers
-
you would have a separate ApplyChangeFailed event on the remote provider, so you could apply the same approach on the server side.
- Proposed as answer by L Zhou [MSFT]Editor Friday, April 2, 2010 10:02 PM
- Marked as answer by Sid Singh [MSFT]Microsoft employee Friday, April 2, 2010 11:02 PM
Wednesday, March 31, 2010 7:35 PM
All replies
-
Bump. :)
TravichWednesday, March 31, 2010 7:07 PM -
you would have a separate ApplyChangeFailed event on the remote provider, so you could apply the same approach on the server side.
- Proposed as answer by L Zhou [MSFT]Editor Friday, April 2, 2010 10:02 PM
- Marked as answer by Sid Singh [MSFT]Microsoft employee Friday, April 2, 2010 11:02 PM
Wednesday, March 31, 2010 7:35 PM -
That's what I was hoping. Sweet!
TravichWednesday, March 31, 2010 8:35 PM -
Okay regarding this I have done this server side, but the client reports still having uploads fail. When I try to capture the message client side it's blank. Any ideas?
TravichSaturday, April 3, 2010 1:15 AM -
what's the exact error or message you're getting?Saturday, April 3, 2010 1:21 AM
-
That's the weird thing, I can't seem to catch any ERROR message, just the change conflict. This is what I'm logging client side (I can't recreate myself).
User had downloads failed: JIvy download failed: 2 upload failed: 3
Error:
Conflict Type:ClientUpdateServerUpdate SyncStageApplyingUpdates
I handle the change conflict the SAME way server side and I have some of my own logging turned on. My logging is either not working or the event is not firing on the server? I'm still trying to figure that out to help me get more of a clue.
TravichMonday, April 5, 2010 3:07 PM -
can you verify that you added the event handlers for the ApplyChangeFailed events? (e.g., this.ApplyChangeFailed += new EventHandler<ApplyChangeFailedEventArgs>(_ApplyChangeFailed); )Tuesday, April 6, 2010 1:39 AM
-
Should it be:
e.Conflict.ServerChange.Rows.Remove ...
or
e.Conflict.ClientChange.Rows.Remove
On the server side?????? Definitely failing to change on the server. I actually have a scenario that I can recreate -- but I can't capture the problem. :(
TravichFriday, April 9, 2010 11:32 PM -
travich,
Am assuming you've already confirmed that your code indeed passes thru the ApplyChangeFailed event.
Now your event handler/conflict resolution code below explicitly checks for a Unique Index constraint violation and the Else code simply does nothing to the conflict but simply generates message to be displayed and sets the action to Continue.
The type of conflict you are encountering is a ClientUpdateServerUpdate which occurs when you row has been updated on both client replica and server replica.
So in your code, it jumps to the Else statement and since the action is Continue, Sync Framework adds the row to the conflicts collection and continues with the processing.
In a ClientUpdateServerUpdate conflict, e.Conflict.ClientChange would contain the row from the client and e.Conflict.ServerChange will contain the corresponding row from the server.
How do yo want to resolve it by the way? ClientWins or ServerWins?
Saturday, April 10, 2010 12:01 AM -
I actually cannot confirm that because I cannot set a break point on the WCF service to debug it even running on the local host. This is frustrating as this was not a problem before.
So on the server side I could call the client or the server? In this instance it's an upload and on an upload I want the client to win against the server. So it should look like this?
private static void _ApplyChangeFailed(object sender, ApplyChangeFailedEventArgs e)
{
try
{
if ((e.Conflict.ErrorMessage ?? "").Contains("A duplicate value cannot be inserted into a unique index."))
{
if (e.Conflict.ClientChange.Rows.Count > 0)
{
e.Conflict.ClientChange.Rows.Remove(e.Conflict.ClientChange.Rows[0]);
e.Action = ApplyAction.RetryWithForceWrite;
}
return;
}
else
{
var Message = String.Format("Local Apply Changed for {1} Error at {0}", e.Conflict.SyncStage, e.Conflict.ServerChange != null ? e.Conflict.ServerChange.TableName : e.Conflict.ServerChange.TableName);
Message = (e.Conflict.ErrorMessage ?? "Unkown Error") + Environment.NewLine + Message;
//Log.LogException(Message, EventLogEntryType.Error);
Console.WriteLine("Sync Conflict Occurred: " + e.Conflict.ErrorMessage);
if (e.Conflict.ConflictType != ConflictType.ErrorsOccurred && e.Conflict.ConflictType != ConflictType.Unknown)
e.Action = ApplyAction.Continue;
}
}
catch (Exception ex)
{
}
}
TravichSaturday, April 10, 2010 1:07 AM -
Okay I had to attach to the process, let's see if I can recreate now.
The problem is the info that's not getting uploaded is vital -- it's a reference that comes from a 3rd party ERP system. :(
TravichSaturday, April 10, 2010 1:22 AM -
have you tried Attach to Process to do the debugging?
your conflict is ClientUpdateServerUpdate ( you have uploaded a row but that same row was already updated in the server).
If you're ApplyChangeFailed event is being called in the WCF side, you're client should actually be overwriting the server since ApplyAction.Continue is the same as ClientWins.
you can add an else similar to this:
if ((e.Conflict.ErrorMessage ?? "").Contains("A duplicate value cannot be inserted into a unique index.")) { if (e.Conflict.ClientChange.Rows.Count > 0) { e.Conflict.ClientChange.Rows.Remove(e.Conflict.ClientChange.Rows[0]); e.Action = ApplyAction.RetryWithForceWrite; } return; } else if(e.Conflict.ConflictType == ConflictType.ClientUpdateServerUpdate) { e.Action = ApplyAction.Continue; // or whatever action you want to take } else { var Message = String.Format("Local Apply Changed for {1} Error at {0}", e.Conflict.SyncStage, e.Conflict.ServerChange != null ? e.Conflict.ServerChange.TableName : e.Conflict.ServerChange.TableName); Message = (e.Conflict.ErrorMessage ?? "Unkown Error") + Environment.NewLine + Message; //Log.LogException(Message, EventLogEntryType.Error); Console.WriteLine("Sync Conflict Occurred: " + e.Conflict.ErrorMessage); if (e.Conflict.ConflictType != ConflictType.ErrorsOccurred && e.Conflict.ConflictType != ConflictType.Unknown) e.Action = ApplyAction.Continue; }
Saturday, April 10, 2010 1:30 AM -
Just attached to the debugger -- okay but not to be stupid -- but this code above would be an example on the SERVER side (WCF Service) or are you saying this can all be handled on the client? I think that is my biggest point of confusion.
You are correct about ClientUpdateServerUpdate I'm 100% certain that is why that field is being dropped. If I could just get my code to update correctly I'd be golden.
To answer your question -- I want the client to win in this instance so I'm assuming continue would work.
Travich- Edited by travich Saturday, April 10, 2010 1:47 AM
Saturday, April 10, 2010 1:38 AM -
it has to be on the Server side (WCF).
The ApplyChangeFailed event get's fired on the client when the client is applying downloaded changes, whereas the ApplyChangeFailed event gets fired on your WCF side when the server is applying the changes that was uploaded.
so you can actually handle the conflicts differently on the client and server side. for example if you think the client is authoritative in terms of updates, you can configure on the upload that the client always win and on the download, you can configure the client win again vs the changes from the server.
Saturday, April 10, 2010 1:44 AM -
Okay I completely understand everything except how I am specifying in this case that the client wins -- by saying continue or by removing the client row and adding it? Sorry I am reading about his on MSDN and still am not certain.
TravichSaturday, April 10, 2010 2:05 AM -
In the server side Continue is equivalent to ClientWins.
if you want to save on memory, you can clear/remove the conflict row information from the e.Conflict.ClientChange and e.Conflict.ServerChange since you already handled the conflict anyway.
e.g.
e.Conflict.ClientChange.Rows.Remove(e.Conflict.ClientChange.Rows[0]);
e.Conflict.ServerChange.Rows.Remove(e.Conflict.ServerChange.Rows[0]);
e.Action = ApplyAction.Continue;the e.Conflict.ClientChange and e.Conflict.ServerChange has nothing to do with conflict resolution, it simply stores the copies of conflicting rows so you can inspect them if you want to.
e.Action is what instructs Sync framework how to deal with the conflict.
Saturday, April 10, 2010 2:32 AM -
That's what I thought but for some reason that confused me...
TravichSaturday, April 10, 2010 2:39 AM -
Dude do you know how awesome it is that I can sit here and create the exact scenario and understand what's going on? Continue doesn't write to the DB, but I will try the force right now. Wow it still feels great when you have a problem for this long and finally figure out what's going on.
TravichSaturday, April 10, 2010 2:48 AM -
This is what I ended up doing and it ended fixing the issue, in test anyways. :)
e.Action = ApplyAction.RetryWithForceWrite;
Basically I am saying -- the client had a change that may be in conflict, but I want it to overwrite whatever is on the server.
TravichSaturday, April 10, 2010 3:16 AM -
great. I got myself confused too.
In the context of the client Continue = ClientWins (local wins over downloaded), RetryWithForceWrite=ServerWins (download over local copy)
In the server context Continue = ServerWins (server copy over uploaded), RetryWithForceWrite=ClientWins(upload over server copy) In the server context, an upload from a client is actually a download to a server.
So you're right, RetryWithForceWrite should be it.
- Edited by JuneT Monday, April 12, 2010 10:12 PM
Saturday, April 10, 2010 3:31 AM -
I can't thank you enough -- I had another issue this morning and was able to debug this much better since you took the time to explain. I want to write a blog post of my understanding of all this when I get time so maybe I can help others out. :)
TravichMonday, April 12, 2010 9:34 PM