locked
ConstraintConflictReason.Collision is causing InvalidCastException RRS feed

  • Question

  • I had previously asked about how to manage collisions of IDs for the same item (http://social.microsoft.com/Forums/en-US/synctechnicaldiscussion/thread/dae09728-16bc-4606-891f-2394ef147219) and was told to use ConstraintConflictReason.Collision rather than Identity. This now results in an InvalidCastException in ProcessChangeBatch on the call to NotifyingChangeApplier.ApplyChanges.

    The INotifyingChangeApplierTarget.SaveItemChange method that manages the collision has the following code:

            public void SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context)
            {
                if (context.ChangeData != null)
                {
                    DataItemId contextItemId = GetDataItemIdFromContext(context);
                    SyncId metadataId = MetadataManager.GetSyncId(contextItemId);
    
                    if (metadataId != null)
                    {
                        if (metadataId.Equals(change.ItemId) == false)
                        {
                            // This item is know by the MetadataManager as a different id
                            context.RecordConstraintConflictForItem(metadataId, ConstraintConflictReason.Collision);
    
                            return;
                        }
                    }
                }
    
                switch (saveChangeAction)
                {
                    case SaveChangeAction.Create:
                    {
                        ProcessCreateItem(context, change);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionOnly:
                    {
                        // Create the store item from the data
                        DataItemMetadata itemMetadata = GetMetadataFromContext(context);
    
                        MetadataManager.SaveItemMetadata(itemMetadata, change.ItemId, change.ChangeVersion);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionAndData:
                    {
                        ProcessUpdateItem(change, context);
    
                        break;
                    }
    
                    case SaveChangeAction.DeleteAndStoreTombstone:
                    {
                        // Delete the item from the item store and store a tombstone for it in the metadata store.
                        ProcessDeleteItem(change, context);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionAndMergeData:
                    case SaveChangeAction.DeleteAndRemoveTombstone:
                    {
                        // Neither merging of data nor removing tombstones is supported.
                        throw new NotImplementedException();
                    }
    
                    default:
                    {
                        throw new ArgumentOutOfRangeException();
                    }
                }
    
                // Save the knowledge in the metadata store as each change is applied. 
                // Saving knowledge as each change is applied is not required. 
                // It is more robust than saving the knowledge only after each change batch, 
                // because if synchronization is interrupted before the end of a change batch, 
                // the knowledge will still reflect all of the changes applied. 
                // However, it is less efficient because knowledge must be stored more frequently.
                SyncKnowledge knowledge;
                ForgottenKnowledge forgottenKnowledge;
    
                context.GetUpdatedDestinationKnowledge(out knowledge, out forgottenKnowledge);
    
                StoreKnowledgeForScope(knowledge, forgottenKnowledge);
            }
    

    An InvalidCastException is thrown by changeApplier.ApplyChanages() when the above method returns.

            public override void ProcessChangeBatch(
                ConflictResolutionPolicy resolutionPolicy,
                ChangeBatch sourceChanges,
                Object changeDataRetriever,
                SyncCallbacks syncCallbacks,
                SyncSessionStatistics sessionStatistics)
            {
                // Use the metadata store to get the local versions of changes received from the source provider.
                IEnumerable<ItemChange> destVersions = MetadataManager.GetLocalVersions(sourceChanges);
    
                // Use a NotifyingChangeApplier object to process the changes. 
                // This object is passed as the INotifyingChangeApplierTarget
                // object that will be called to apply changes to the item store.
                NotifyingChangeApplier changeApplier = new NotifyingChangeApplier(IdFormats);
                INotifyingChangeApplierTarget applier = new StoreChangeApplier(IdFormats, MetadataManager, Exchanger);
    
                changeApplier.ApplyChanges(
                    resolutionPolicy,
                    sourceChanges,
                    (IChangeDataRetriever)changeDataRetriever,
                    destVersions,
                    MetadataManager.GetKnowledge(),
                    MetadataManager.GetForgottenKnowledge(),
                    applier,
                    SessionContext,
                    syncCallbacks);
            }
    The stack trace is the following:
    Test method Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.SyncHandlesSameItemNotPreviouslyKnownTest threw exception:  System.InvalidCastException: Specified cast is not valid..
    
    Microsoft.Synchronization.CoreInterop.ISynchronousNotifyingChangeApplier2.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ISynchronousNotifyingChangeApplierImpl.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, CollisionConflictResolutionPolicy collisionConflictResolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, IConflictLogAccess conflictLogAccess, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
    Neovolve.Jabiru.Client.Synchronization.StoreSyncProvider.ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, Object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics) in R:\Codeplex\Jabiru\Jabiru\Neovolve.Jabiru.Client\Neovolve.Jabiru.Client.Synchronization\StoreSyncProvider.cs: line 216
    Microsoft.Synchronization.KnowledgeProviderProxy.ProcessChangeBatch(CONFLICT_RESOLUTION_POLICY resolutionPolicy, ISyncChangeBatch pSourceChangeManager, Object pUnkDataRetriever, ISyncCallback pCallback, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
    Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
    Microsoft.Synchronization.SyncOrchestrator.Synchronize()
    Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.RunSync(String localPath, IDataExchange exchanger, IMetadataManager syncManager) in R:\Codeplex\Jabiru\Jabiru\Neovolve.Jabiru.Client.Synchronization.IntegrationTests\StoreSyncProviderTests.cs: line 498
    Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.SyncHandlesSameItemNotPreviouslyKnownTest() in R:\Codeplex\Jabiru\Jabiru\Neovolve.Jabiru.Client.Synchronization.IntegrationTests\StoreSyncProviderTests.cs: line 236
    

    The changeApplier.ApplyChanges does not throw an exception when I am handing any other change that I have encountered so far.

    What is the correct way have handling an ID collision? In my scenario, I want the destination to be the source of truth for the ID, but then want to determine which provider will win with regard to what is the latest change of the item itself.
    • Moved by Max Wang_1983 Wednesday, April 20, 2011 9:48 PM Forum consolidation (From:SyncFx - Technical Discussion [ReadOnly])
    Friday, October 9, 2009 12:07 AM

Answers

  • Hi Rory,

    The reason you're getting the exception is that the only resolutions for constraints of type Other are defer or transfer and defer.  Collision is the reason you want to specify.  One reason you may not be getting the callback is that the change applier is trying to log the conflict temporarily to wait for a pending change to the conflicting item.  Have you updated your change applier target to implement INotifyingChangeApplierTarget2?  If so, try putting a breakpoint in SaveConstraintConflict and see if it is called when you set the conflict.  If this happens, also check to see if the temporary flag is true.  If this is the case, then the change applier has decided to wait for other changes to process before it continues.

    In order to see the change again, you need to do the following:

     Use the change applier ApplyChanges call that takes an IConflictLogAccess and Collision policy.
     Use an IConflictLogWriter backed by the same conflict log as the IConflictLogAccess in the previous step for saving conflicts in SaveConstraintConflict.

    A note about the conflict log...if you don't really care about having log conflicts persist after the sync, look at the MemoryConflictLog that we provide.  It implements both interfaces mentioned above, and the provider doesn't have to keep it around after the sync session.

    In regards to the issue about having conflict loops, you are correct that you can get into weird situations like this. There are a couple of things you can do:

     - Use a policy to resolve (you won't get callbacks, and you won't get the granularity of resolving things different ways)
     - Come up with a scheme on how to pick which one to keep (lower sync Id wins, greater sync Id wins, or something like that)
     - If the items are really the same, but have different sync ids, use merge, which will always pick the lower item id as the winner.  Introducing merge to the system does introduce a lot of complexity, as a large number of the save actions that were added were to deal with merge tombstones.

    I hope this clarifies a few things.

    Aaron


    SDE, Microsoft Sync Framework
    Tuesday, October 13, 2009 5:32 PM

All replies

  • Hi Rory,

    I'm not sure what is causing the invalid cast exception, but I'll attempt to answer your question and hopefully provide some insight.

    Unfortunately, I'm not sure that there is a way to do what you want in terms of the source of truth for the ID.  From looking at your code, it seems that an element of the data is serving as some sort of local ID.  Am I understanding that correctly?  If that is the case, then I don't think it will give you what you want.  When the change applier resolves constraint conflicts, it somewhat assumes that IDs and data travel together.  This means that if you choose source wins, you'll get a save action that tells you to delete the conflicting item on the destination and save the source item with its ID.  If you choose destination wins, you'll get a save action that tells you to store a tombstone for the source item.

    If it's really two items that collide because they're the same item, but with different sync ids, using ConstraintConflictResolutionAction.Merge may help.  The change action that the provider receives will be ChangeIdUpdateVersionAndMergeData.  It means to store a merge tombstone for the normal item id that you receive, merge the local data with the source data and store that in the winner (live) item id.  One thing, though, is that the change applier will always choose the lower sync item id as the winner.  This is to make propagation easier in the case that providers enumerate their items in order.

    A couple of notes...your SaveItemChange method isn't implementing of the constraint save actions, which means that you won't be able to handle them.  One thing that might be helpful is to see if you get a SaveChange call for the item for which you've reported a constraint.  This site: http://msdn.microsoft.com/en-us/library/microsoft.synchronization.savechangeaction(SQL.105).aspx might help.  The only save actions you don't care about for constraints are the ones related to Ghosts.  You can ignore others if you don't resolve constraints in certain ways.

    Another thing I noticed is that the code doesn't use the new ApplyChanges method which takes an IConflictLogAccess.  The change applier uses this to determine cases where you might want to temporarily log constraint conflict that could be resolved by a future change.  For example, the source send item A.  The destination reports a constraint conflict with item B.  The change applier looks at the source's knowledge for B and sees that there might be a change for B coming, so it tells the destination to save a conflcit for A temporarily.  A change to B comes in and then the change applier will attempt to reapply A.

    Based on these two things, the only thing I can think of for the InvalidCastException is that it might be the case that your change applier target doesn't implement INotifyingChangeApplierTarget2, which is required for constraint conflict scenarios, but not for others.

    Hope this helps,
    Aaron
    SDE, Microsoft Sync Framework
    Friday, October 9, 2009 1:13 AM
  • Hi Aaron,

    Thanks for the response. I'll make some changes using Merge and see how far I get.

    The source of truth idea was really about having multiple source providers (typically FileSyncProvider's) communicating with a server based custom provider. I didn't want each sync of a source provider to change the SyncId in the server for the same logical item that would result in a SyncId conflict when the next source provider syncs the same item.

    For example, consider SourceA, Server and SourceB. If both the sources and the server have a file of the same relative path before any sync has occured, the server will have its understanding of the SyncId when it searches for changes and so will SourceA when it syncs. If Server ends up changing its SyncId for the item to match SourceA, then another conflict will be encountered when SourceB attempts to sync the same item for the first time. If SyncA then syncs the next time, there is another conflict as Server now understands the item as SourceB's SyncId and it goes on and on.

    I haven't written an integration test for this scenario yet, but I was assuming that this would be the result.
    Monday, October 12, 2009 1:13 AM
  • I have changed the SaveItemChange method to identify ConstraintConflictReason.Other which now means that my remoteProvider.DestinationCallbacks.ItemConstraint callback gets invoked. I set the action in the callback to Merge and I'm back to the same exception encountered in my original thread.

    I have a break point at the start of SaveItemChange so I know that it isn't being entered after the constraint notification.

    Here is the updated SaveItemChange. The only update was to specify the conflict reason as Other. I am not sure if this is the correct way of getting the item conflict callback to fire which is where I specify the merge action.

            public void SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context)
            {
                if (context.ChangeData != null)
                {
                    DataItemId contextItemId = GetDataItemIdFromContext(context);
                    SyncId metadataId = MetadataManager.GetSyncId(contextItemId);
    
                    if (metadataId != null)
                    {
                        if (metadataId.Equals(change.ItemId) == false)
                        {
                            // This item is know by the MetadataManager as a different id
                            context.RecordConstraintConflictForItem(metadataId, ConstraintConflictReason.Other);
    
                            return;
                        }
                    }
                }
    
                switch (saveChangeAction)
                {
                    case SaveChangeAction.Create:
                    {
                        ProcessCreateItem(context, change);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionOnly:
                    {
                        // Create the store item from the data
                        DataItemMetadata itemMetadata = GetMetadataFromContext(context);
    
                        MetadataManager.SaveItemMetadata(itemMetadata, change.ItemId, change.ChangeVersion);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionAndData:
                    {
                        ProcessUpdateItem(change, context);
    
                        break;
                    }
    
                    case SaveChangeAction.DeleteAndStoreTombstone:
                    {
                        // Delete the item from the item store and store a tombstone for it in the metadata store.
                        ProcessDeleteItem(change, context);
    
                        break;
                    }
    
                    case SaveChangeAction.UpdateVersionAndMergeData:
                    case SaveChangeAction.DeleteAndRemoveTombstone:
                    {
                        // Neither merging of data nor removing tombstones is supported.
                        throw new NotImplementedException();
                    }
    
                    default:
                    {
                        throw new ArgumentOutOfRangeException();
                    }
                }
    
                // Save the knowledge in the metadata store as each change is applied. 
                // Saving knowledge as each change is applied is not required. 
                // It is more robust than saving the knowledge only after each change batch, 
                // because if synchronization is interrupted before the end of a change batch, 
                // the knowledge will still reflect all of the changes applied. 
                // However, it is less efficient because knowledge must be stored more frequently.
                SyncKnowledge knowledge;
                ForgottenKnowledge forgottenKnowledge;
    
                context.GetUpdatedDestinationKnowledge(out knowledge, out forgottenKnowledge);
    
                StoreKnowledgeForScope(knowledge, forgottenKnowledge);
            }
    The ItemConstraint callback is:
            private static void DestinationCallbacks_ItemConstraint(Object sender, ItemConstraintEventArgs e)
            {
                String message = String.Format("Constraint {0}", e.ConstraintConflictReason);
    
                Trace.WriteLine(message);
    
                e.SetResolutionAction(ConstraintConflictResolutionAction.Merge);
            }
    This then results in the following exception:
    Test method Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.SyncHandlesSameItemNotPreviouslyKnownTest threw exception:  System.ArgumentException: Value does not fall within the expected range..
    
    Microsoft.Synchronization.CoreInterop.IConstraintConflict.SetConstraintResolveActionForChange(SYNC_CONSTRAINT_RESOLVE_ACTION constraintResolveAction)
    Microsoft.Synchronization.ItemConstraintEventArgs.SetResolutionAction(ConstraintConflictResolutionAction resolutionAction)
    Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.DestinationCallbacks_ItemConstraint(Object sender, ItemConstraintEventArgs e)
    Microsoft.Synchronization.SyncCallbacks.OnItemConstraint(ItemConstraintEventArgs args)
    Microsoft.Synchronization.SyncCallbacks.CallbackProxy.OnConstraintConflict(IConstraintConflict pConstraintCnflict)
    Microsoft.Synchronization.CoreInterop.ISynchronousNotifyingChangeApplier2.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ISynchronousNotifyingChangeApplierImpl.ApplyChanges(CONFLICT_RESOLUTION_POLICY resolutionPolicy, COLLISION_CONFLICT_RESOLUTION_POLICY collisionPolicy, ISyncChangeBatch pSourceChanges, Object pUnkDataRetriever, IEnumSyncChanges pDestinationVersions, ISyncKnowledge pDestinationKnowledge, IForgottenKnowledge pDestinationForgottenKnowledge, ISynchronousNotifyingChangeApplierTarget pChangeApplierTarget, IConflictLogAccess pConflictLogAccess, ISyncSessionState pSessionState, ISyncCallback pCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, CollisionConflictResolutionPolicy collisionConflictResolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, IConflictLogAccess conflictLogAccess, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
    Microsoft.Synchronization.NotifyingChangeApplier.ApplyChanges(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, IChangeDataRetriever changeDataRetriever, IEnumerable`1 destinationVersions, SyncKnowledge destinationKnowledge, ForgottenKnowledge destinationForgottenKnowledge, INotifyingChangeApplierTarget changeApplierTarget, SyncSessionContext syncSessionState, SyncCallbacks syncCallback)
    Neovolve.Jabiru.Client.Synchronization.StoreSyncProvider.ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, Object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics) in R:\Codeplex\Jabiru\Jabiru\Neovolve.Jabiru.Client\Neovolve.Jabiru.Client.Synchronization\StoreSyncProvider.cs: line 207
    Microsoft.Synchronization.KnowledgeProviderProxy.ProcessChangeBatch(CONFLICT_RESOLUTION_POLICY resolutionPolicy, ISyncChangeBatch pSourceChangeManager, Object pUnkDataRetriever, ISyncCallback pCallback, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
    Microsoft.Synchronization.CoreInterop.ISyncSession.Start(CONFLICT_RESOLUTION_POLICY resolutionPolicy, _SYNC_SESSION_STATISTICS& pSyncSessionStatistics)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWaySyncHelper(SyncIdFormatGroup sourceIdFormats, SyncIdFormatGroup destinationIdFormats, KnowledgeSyncProviderConfiguration destinationConfiguration, SyncCallbacks DestinationCallbacks, ISyncProvider sourceProxy, ISyncProvider destinationProxy, ChangeDataAdapter callbackChangeDataAdapter, SyncDataConverter conflictDataConverter, Int32& changesApplied, Int32& changesFailed)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.DoOneWayKnowledgeSync(SyncDataConverter sourceConverter, SyncDataConverter destinationConverter, SyncProvider sourceProvider, SyncProvider destinationProvider, Int32& changesApplied, Int32& changesFailed)
    Microsoft.Synchronization.KnowledgeSyncOrchestrator.Synchronize()
    Microsoft.Synchronization.SyncOrchestrator.Synchronize()
    Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.RunSync(String localPath, IDataExchange exchanger, IMetadataManager syncManager)
    Neovolve.Jabiru.Client.Synchronization.IntegrationTests.StoreSyncProviderTests.SyncHandlesSameItemNotPreviouslyKnownTest()
    

    Any pointers? Am I notifying the sync framework of the failure in the correct way in order to cause a merge action?

    BTW, the DataItemId type simply describes the metadata for an item. It is defined as a class with String Path and Boolean IsContainer properties and has some equality and HashCode logic.

    Monday, October 12, 2009 2:26 AM
  • Hi Rory,

    The reason you're getting the exception is that the only resolutions for constraints of type Other are defer or transfer and defer.  Collision is the reason you want to specify.  One reason you may not be getting the callback is that the change applier is trying to log the conflict temporarily to wait for a pending change to the conflicting item.  Have you updated your change applier target to implement INotifyingChangeApplierTarget2?  If so, try putting a breakpoint in SaveConstraintConflict and see if it is called when you set the conflict.  If this happens, also check to see if the temporary flag is true.  If this is the case, then the change applier has decided to wait for other changes to process before it continues.

    In order to see the change again, you need to do the following:

     Use the change applier ApplyChanges call that takes an IConflictLogAccess and Collision policy.
     Use an IConflictLogWriter backed by the same conflict log as the IConflictLogAccess in the previous step for saving conflicts in SaveConstraintConflict.

    A note about the conflict log...if you don't really care about having log conflicts persist after the sync, look at the MemoryConflictLog that we provide.  It implements both interfaces mentioned above, and the provider doesn't have to keep it around after the sync session.

    In regards to the issue about having conflict loops, you are correct that you can get into weird situations like this. There are a couple of things you can do:

     - Use a policy to resolve (you won't get callbacks, and you won't get the granularity of resolving things different ways)
     - Come up with a scheme on how to pick which one to keep (lower sync Id wins, greater sync Id wins, or something like that)
     - If the items are really the same, but have different sync ids, use merge, which will always pick the lower item id as the winner.  Introducing merge to the system does introduce a lot of complexity, as a large number of the save actions that were added were to deal with merge tombstones.

    I hope this clarifies a few things.

    Aaron


    SDE, Microsoft Sync Framework
    Tuesday, October 13, 2009 5:32 PM
  • Hi Aaron,

    Thanks for the response. My appologies for taking so long to get back to this. I have reviewed this thread and had neglected to use INotifyingChangeApplierTarget2 as you had suggested. The SaveConstraintConflict method is now called as soon as the collision constraint is identified now that I have fixed this up.

    I don't think I need a MemoryConflictLog in this situation as I'm not expecting other changes within the sync session to affect the situation.

    Could you please provide some guidance about how to use the SaveConstraintConflict method? There seems to be no information on the net about how to use this method other than the MSDN library which does not really provide anything to work with.

    My prefered outcome is that the local id would be updated but no data changed. I want to limit the number of changes made to the destination knowledge (the server) to avoid unnecessary conflicts with other clients that sync the same data with dfferent ids as previously discussed.

    Cheers
    Thursday, November 26, 2009 2:16 AM
  • Thursday, November 26, 2009 2:30 AM
  • Rory, hopefully you have also seen the Sync101 sample for Constraint Conflicts:

    http://code.msdn.microsoft.com/Release/ProjectReleases.aspx?ProjectName=sync&ReleaseId=3417
    Tuesday, December 1, 2009 6:21 PM
    Moderator
  • Hey Rory,

    For SaveConstraintConflict, it is used for two situations:

    1. You have a constraint conflict that you resolve with SCRA_TRANSFER_AND_DEFER.  This isn't that interesting...it's basically saying that you want to persist the conflict and perhaps do something with it after the session.

    2. You send across an item A and report a constraint conflict with item B.  Then the change applier detects that the source knowledge contains the creation version of B (so the source has seen B) and the destination knowledge doesn't contain the source knowledge for item B. This is because we lean toward the scenario where both sides have the same set of constraints, which means that if A coexists on the source, then applying B will remove the constraint with A.  Note, however, that this can happen even if there is no change for B because of the way the knowledge works (if you have a scope vector, then destination knowledge won't contain the source knowledge for any item).    When you get SaveConstraintConflict for this scenario, the fIsTemporary flag will be set to true, and the best thing to do is to store it in a conflict log that backs the IConflictLogAccess passed in to the ApplyChanges call.  These conflicts will be enumerated at the end of the session

    Scenario #2 is the reason I suggested the MemoryConflictLog.  It's the easiest way to have the interfaces implemented and not have to write much code.  You can also drop it on the floor at the end of the session and everything will work.

    In regards to your preferred outcome, if by local id you mean an id that is not the global sync id, then the ConstraintResolutionAction.RenameSource resolution may work for you.  This essentially means "change the local id of the item, but keep the same global id".  You can think of this in terms of file sync....you may get a constraint on the file name, which is effectively a local id, and you can rename it while maintaining the same sync id, which keeps knowledge nice.

    If your scenario is such that it is the same item with different sync ids (say first-time sync on two directories that were copied from one another and each file got a different sync id), then you probably want to do ConstraintResolutionAction.Merge.  Merge will ensure that the sync id changes get propagated directly, but you don't get to choose the sync id.

    If you just want one item at the end but you don't necessarily need things to be merge correctly, you could choose SourceWins or DestinationWins, which will cause the other item to be deleted.

    Hope this helps,
    Aaron


    SDE, Microsoft Sync Framework
    Tuesday, December 1, 2009 8:11 PM
  • Thanks for the reply guys.

    Aaron, I think I'll have to play with each of these options to see what works best for my scenario. There appears to be pros and cons with each but not necessarily a best fit option.

    Cheers
    Tuesday, December 1, 2009 10:04 PM