Wednesday, July 2, 2008

Sync for ADO.NET - Resolving Conflicts

I've been spending my spare time for the last few days looking into how to resolve conflicts. The samples in the help files with CTP2 are (IMHO) missing a key bit of info: the ApplyChangeFailed event on the SqlCeClientSyncProvider may not be what some expect, it is only raised if a change is made to a record after the upload phase but before a server change is downloaded and applied.

On slower connections that aren't using batching this can happen more often since the time from upload to commit of the "apply server changes" stage is longer.

I admit that I was expecting the following:

  1. A data row exists on the server (lets call it ID:1, Label:Original, Version:1000)
  2. Client A downloads it and makes a change (ID:1, Label:ClientA)
  3. Client B downloads it and makes a change (ID:1, Label:ClientB)
  4. Client A syncs (last anchor 1000), server saves new version (ID:1, Label:ClientA, Version:1001)
  5. Client B syncs (last anchor 1000), server provider gets an ApplyChangeFailed event, we choose to Continue (basically saying Server copy wins)
    1. I was EXPECTING the client to then raise an ApplyChangeFailed event so I could tell the user that their change has a conflict with the server (record 1001), and allow them to pick one or merge
    2. What actually happens is that version 1001 is downloaded and replaces our change, destroying it forever

So, what can we do here? What we need to do is in the Server Provider decide what to do with the conflicting rows. If we want to keep the first change (saved as version 1001) we need to write the change from Client B somewhere else, such as <TableName>_Conflicts, including a note of the ClientID that caused the conflict. If this table is made part of our sync then that client (or all clients, depending on your needs) will now have it's change in the <TableName>_Conflicts table and the version from Client A in the <TableName> table. The UI can respond to this and show a conflict resolution dialog, if a merge or replace is decided upon then the <TableName> row is updated (and will be part of the next sync) and the <TableName>_Conflicts row will be deleted (and will be removed from the server next sync).

A special thanks goes out to Paul Stovell for helping me discover where my expectation and the actual implementation differed.

1 comment:

Unknown said...

Hi Timothy,
This post is a life saver. Yours is the only post I have found on this subject. I have not been able to get the ApplyChangeFailed event to work on the client side and I was wondering why.

Thanks,
Craig