Detecting and Resolving Constraint Conflicts

Constraint conflicts are conflicts that violate constraints that are put on items, such as the relationship of folders or the location of identically named data within a file system. Sync Framework provides change applier objects that simplify resolution of constraint conflicts.

Constraint conflicts are detected by the destination provider during the change application phase of synchronization. When the destination provider detects a constraint conflict, it reports the conflict to the change applier. The change applier resolves the conflict according to either the conflict resolution policy set for the session, or the conflict resolution action set by the application for the specified conflict. The change applier then dispatches any necessary calls to the destination provider so that the destination provider can apply the resolved conflict to the destination replica.

When a provider reports constraint conflicts and uses a change applier, it must also provide a conflict log that the change applier uses to process and log conflicts. Sync Framework supplies an in-memory implementation of a conflict log for providers that do not implement their own conflict log. For more information, see Logging and Managing Conflicts.

Be aware that constraint conflicts cannot be used by a provider that uses custom filters or the change application service, or unexpected results may occur.

Kinds of Constraint Conflicts

Constraint conflicts depend on the data store used by the destination replica and so can take many forms. Sync Framework divides constraint conflicts into the following three kinds.

  • A collision conflict occurs when the item cannot be saved because it conflicts with another item in the destination store, such as when the source provider sends a file that has the same name and location as a file that already exists in the destination replica.

  • A missing parent conflict occurs when an item cannot be saved in a hierarchical data store because it requires a parent item that does not exist, such as when the source provider sends a file to be saved in a directory that does not exist on the destination replica.

  • Other constraint conflicts occur when the item to be saved violates a constraint of the destination replica, such as when the source provider sends a file that is too large to be saved on the destination replica or when the change violates some business logic on the destination replica. As an example of a business logic conflict, consider a low-fidelity replica that stores two change units: name and country. Also consider a high-fidelity replica that stores three change units: name, state/province, and country. The high-fidelity replica contains business logic that checks the state/province field against the country field, and will not store a change that does not pass the check. The low-fidelity replica acts as the source and sends an item with country set to "USA". The destination provider attempts to apply the change to the high-fidelity replica, but on the high-fidelity replica the item contains "British Columbia" in its state/province field. Therefore, the change violates the business logic and causes a constraint conflict.

The destination provider selects from the following values to specify the reason for a constraint conflict.

Constraint conflict reason Description

Collision (for managed code), CCR_COLLISION (for unmanaged code)

The item cannot be saved because it conflicts with another item in the store, such as an item that has the same name as an existing item. The provider must specify the ID of the destination item as the conflicting item ID.

NoParent (for managed code), CCR_NOPARENT (for unmanaged code)

The item cannot be saved in the hierarchical data store because the item requires a parent item that does not exist in the store. The provider can optionally specify the ID of the missing parent as the conflicting item ID.

Other (for managed code), CCR_OTHER (for unmanaged code)

The item or change unit violates some other constraint of the destination replica. The provider can optionally specify the ID of the conflicting item as the conflicting item ID.

Detecting and Reporting Constraint Conflicts

Detecting a constraint conflict depends on the data store used by a replica. Therefore, constraint conflicts must be detected by the destination provider. For example, a provider that represents a hierarchical file system must be able to detect store-specific constraints that are put on persisted data, such as location, naming, size, and so on.

Constraint conflicts are detected by the destination provider during the change application phase of synchronization.

Managed code Constraint conflicts are typically detected by the destination provider in its SaveItemChange or SaveChangeWithChangeUnits method and reported by calling RecordConstraintConflictForItem or RecordConstraintConflictForChangeUnit.

Unmanaged code Constraint conflicts are typically detected by the destination provider in its ISynchronousNotifyingChangeApplierTarget::SaveChange or ISynchronousNotifyingChangeApplierTarget::SaveChangeWithChangeUnits method and reported by calling ISaveChangeContext2::SetConstraintConflictOnChange or ISaveChangeWithChangeUnitsContext2::SetConstraintConflictOnChangeUnit.

When the change applier receives report of a constraint conflict, it takes several actions:

  • When the constraint conflict is a collision conflict, the change applier determines whether the conflict is a new conflict, a temporary conflict, or a merge resolution propagation. During processing of the conflict, the change applier may temporarily store the conflict in the conflict log by calling SaveConstraintConflict (for managed code) or ISynchronousNotifyingChangeApplierTarget2::SaveConstraintConflict (for unmanaged code). The change applier removes temporary conflicts from the log at the end of the synchronization session.

  • Resolves collision conflicts either according to the collision conflict resolution policy set for the session, or according to the conflict resolution action determined by the application. The application is called to determine the conflict resolution action when the collision conflict resolution policy is set to ApplicationDefined (for managed code) or CCRP_NONE (for unmanaged code).

  • Resolves non-collision constraint conflicts according to the conflict resolution action determined by the application. A conflict resolution policy cannot be set for non-collision constraint conflicts.

Resolving Constraint Conflicts

The change applier helps the destination provider resolve constraint conflicts by dispatching calls to the change applier target object that is specified by the provider. When a collision conflict resolution policy is specified, the change applier uses it to determine the correct conflict resolution action to take to resolve each collision conflict that occurs. When custom collision conflict resolution is specified, the change applier notifies the synchronization application of the collision conflict, and the application specifies the conflict resolution action. A conflict resolution policy cannot be specified for non-collision constraint conflicts, so when a non-collision constraint conflict is reported, the change applier always notifies the application so that the application can specify the conflict resolution action. In all cases, the change applier calls the appropriate change applier target method and the change applier target object performs the action, such as to save the change to the replica or to log the conflict for later processing.

Specifying a Collision Conflict Resolution Policy

The synchronization application typically specifies a collision conflict resolution policy before synchronization is started.

Managed code The application specifies the policy by setting the CollisionConflictResolutionPolicy property of the destination provider to the desired value.

Unmanaged code The application specifies the policy by using a custom mechanism, such as a custom interface the application obtains by calling QueryInterface on the destination provider object.

The destination provider passes the collision conflict resolution policy to the ApplyChanges (for managed code) or ISynchronousNotifyingChangeApplier2::ApplyChanges (for unmanaged code) method of the change applier so the change applier can properly dispatch methods to the change applier target. The change applier target is represented by the INotifyingChangeApplierTarget2 (for managed code) or ISynchronousNotifyingChangeApplierTarget2 (for unmanaged code) object

Collision Conflict Resolution Policies for Managed Code

Sync Framework defines the following collision conflict resolution policies for managed code.

Conflict resolution policy Description

SourceWins

The change made on the source replica always wins. Sync Framework specifies a conflict resolution action of SourceWins.

DestinationWins

The change made on the destination replica always wins. Sync Framework specifies a conflict resolution action of DestinationWins.

RenameSource

The change sent from the source provider is renamed so that it no longer collides with the conflicting item on the destination replica, and the source change is applied to the destination replica. Sync Framework specifies a conflict resolution action of RenameSource.

RenameDestination

The conflicting item on the destination replica is renamed so that it no longer collides with the change sent from the source provider, and the source change is applied to the destination replica. Sync Framework specifies a conflict resolution action of RenameDestination.

Merge

The data from the source item is combined with the destination item. Sync Framework specifies a conflict resolution action of Merge.

ApplicationDefined

The change applier notifies the synchronization application of each collision conflict as it occurs, by using the ItemConstraint event. The application examines the conflicting items and specifies the conflict resolution action by calling SetResolutionAction.

Collision Conflict Resolution Policies for Unmanaged Code

Sync Framework defines the following collision conflict resolution policies for unmanaged code.

Conflict resolution policy Description

CCRP_SOURCE_PROVIDER_WINS

The change made on the source replica always wins. Sync Framework specifies a conflict resolution action of SCRA_ACCEPT_SOURCE_PROVIDER.

CCRP_DESTINATION_PROVIDER_WINS

The change made on the destination replica always wins. Sync Framework specifies a conflict resolution action of SCRA_ACCEPT_DESTINATION_PROVIDER.

CCRP_RENAME_SOURCE

The change sent from the source provider is renamed so that it no longer collides with the conflicting item on the destination replica, and the source change is applied to the destination replica. Sync Framework specifies a conflict resolution action of SCRA_RENAME_SOURCE.

CCRP_RENAME_DESTINATION

The conflicting item on the destination replica is renamed so that it no longer collides with the change sent from the source provider, and the source change is applied to the destination replica. Sync Framework specifies a conflict resolution action of SCRA_RENAME_DESTINATION.

CCRP_MERGE

The data from the source item is combined with the destination item. Sync Framework specifies a conflict resolution action of SCRA_MERGE.

CCRP_NONE

The change applier notifies the synchronization application of each collision conflict as it occurs, by using the ISyncConstraintCallback::OnConstraintConflict event. The application examines the conflicting items and specifies the conflict resolution action by calling IConstraintConflict::SetConstraintResolveActionForChange or IConstraintConflict::GetConstraintResolveActionForChangeUnit.

Specifying Custom Conflict Resolutions

An application can indicate that it will specify a conflict resolution action for each constraint conflict that occurs. To accomplish this, the application registers to receive notification of constraint conflicts. When a constraint conflict is reported, Sync Framework notifies the application. The application can then analyze the conflict and set the desired conflict resolution action.

Specifying Custom Conflict Resolutions by Using Managed Code

To receive notification of collision conflicts, an application performs the following actions before starting synchronization.

  • Registers an event handler for the ItemConstraint event of the destination provider.

  • Sets the CollisionConflictResolutionPolicy property of the destination provider to ApplicationDefined.

If the application has performed these steps, the change applier raises the ItemConstraint event one time for each collision constraint conflict that is reported during synchronization.

Because a conflict resolution policy cannot be specified for non-collision constraint conflicts, the change applier also raises the ItemConstraint event one time for each non-collision constraint conflict that is reported.

The event handler for the ItemConstraint event receives an ItemConstraintEventArgs object that contains metadata and item data for the two changes in conflict. The event handler can examine the two changes in conflict, make changes to the metadata or item data, and set the resolution action for the conflict by using the SetResolutionAction method. The change applier then processes the conflict and dispatches the appropriate call to the change applier target object. Be aware that, when the constraint conflict is not a collision, the only valid conflict resolution actions are SaveConflict and SkipChange.

Sync Framework provides the following set of constraint conflict resolution actions for which the change applier handles most of the processing.

Conflict resolution action Description Valid for Conflict Type

SourceWins

The change made on the source replica wins. The change applier passes the change to the SaveItemChange method and specifies a save action of DeleteConflictingAndSaveSourceItem. The source change is applied to the destination replica and the conflicting destination item is deleted from the destination replica.

Collision conflicts only.

DestinationWins

The change made on the destination replica wins. The change applier passes the source change to the SaveItemChange method and specifies a save action of DeleteAndStoreTombstone. The destination provider creates a tombstone for the source change. When the destination acts as the source in a later synchronization, it will enumerate a change that represents the deletion of the source item, and so remove it from the synchronization community.

Collision conflicts only.

RenameSource

The change sent from the source provider is renamed so that it no longer collides with the conflicting item on the destination replica, and the source change is applied to the destination replica. The change applier passes the change to the SaveItemChange method and specifies a save action of RenameSourceAndUpdateVersionAndData.

Collision conflicts only.

RenameDestination

The conflicting item on the destination replica is renamed so that it no longer collides with the change sent from the source provider, and the source change is applied to the destination replica. The change applier passes the change to the SaveItemChange method and specifies a save action of RenameDestinationAndUpdateVersionData.

Collision conflicts only.

Merge

The data from the source item is combined with the destination item. The change applier passes the source replica's change data to the SaveItemChange method and specifies a save action of ChangeIdUpdateVersionAndMergeData. For details, see Merging Conflicting Items, below.

Collision conflicts only.

SaveConflict

Log the conflict and do not apply the change. The change applier passes the conflict data to the SaveConstraintConflict method, which saves the conflict in a conflict log. For more information on logging conflicts, see Logging and Managing Conflicts.

All constraint conflicts.

SkipChange

Ignore the conflict and do not apply the change. The change applier does not pass the conflict data to the destination provider.

All constraint conflicts.

Specifying Custom Conflict Resolutions by Using Unmanaged Code

To receive notification of collision conflicts, an application performs the following actions before starting synchronization.

If the application has performed these steps, the change applier calls the OnConstraintConflict method one time for each collision constraint conflict that is reported during synchronization.

Because a conflict resolution policy cannot be specified for non-collision constraint conflicts, the change applier also calls the OnConstraintConflict method one time for each non-collision constraint conflict that is reported.

The OnConstraintConflict method receives an IConstraintConflict object that contains metadata and item data for the two changes in conflict. The method can examine the two changes in conflict, make changes to the metadata or item data, and set the resolution action for the conflict by using the IConstraintConflict::SetConstraintResolveActionForChange or IConstraintConflict::GetConstraintResolveActionForChangeUnit method. The change applier then processes the conflict and dispatches the appropriate call to the change applier target object. Be aware that, when the constraint conflict is not a collision, the only valid conflict resolution actions are SCRA_TRANSFER_AND_DEFER and SCRA_DEFER.

Sync Framework provides the following set of constraint conflict resolution actions for which the change applier handles most of the processing.

Conflict resolution policy Description Valid for Content Type

SCRA_ACCEPT_SOURCE_PROVIDER

The change made on the source replica always wins. The change applier passes the change to the ISynchronousNotifyingChangeApplierTarget::SaveChange method and specifies a save action of SSA_DELETE_CONFLICTING_AND_SAVE_SOURCE_ITEM. The source change is applied to the destination replica and the conflicting destination item is deleted from the destination replica.

Collision conflicts only.

SCRA_ACCEPT_DESTINATION_PROVIDER

The change made on the destination replica always wins. The change applier passes the source change to the SaveChange method and specifies a save action of SSA_DELETE_AND_STORE_TOMBSTONE. The destination provider creates a tombstone for the source change. When the destination acts as the source in a later synchronization, it will enumerate a change that represents the deletion of the source item, and so remove it from the synchronization community.

Collision conflicts only.

SCRA_RENAME_SOURCE

The change sent from the source provider is renamed so that it no longer collides with the conflicting item on the destination replica, and the source change is applied to the destination replica. The change applier passes the change to the SaveChange method and specifies a save action of SSA_RENAME_SOURCE_AND_UPDATE_VERSION_AND_DATA.

Collision conflicts only.

SCRA_RENAME_DESTINATION

The conflicting item on the destination replica is renamed so that it no longer collides with the change sent from the source provider, and the source change is applied to the destination replica. The change applier passes the change to the SaveChange method and specifies a save action of SSA_RENAME_DESTINATION_AND_UPDATE_VERSION_AND_DATA.

Collision conflicts only.

SCRA_MERGE

The data from the source item is combined with the destination item. The change applier passes the source replica's change data to the SaveChange method and specifies a save action of SSA_CHANGE_ID_UPDATE_VERSION_AND_MERGE_DATA. For details, see Merging Conflicting Items, below.

Collision conflicts only.

SCRA_TRANSFER_AND_DEFER

Log the conflict and do not apply the change. The change applier passes the conflict data to the ISynchronousNotifyingChangeApplierTarget2::SaveConstraintConflict method, which saves the conflict in a conflict log. For more information on logging conflicts, see Logging and Managing Conflicts.

All constraint conflicts.

SCRA_DEFER

Ignore the conflict and do not apply the change. The change applier does not pass the conflict data to the destination provider.

All constraint conflicts.

Merging Conflicting Items

Merging two items in a collision constraint conflict differs from merging items in a concurrency conflict because the two items involved in a collision conflict have different item IDs. For example, a file named "FavoriteBooks.txt" is created on a replica and given an item ID of id1. A file named "FavoriteBooks.txt" is also created on another replica and given an item ID of id2. When the replicas are synchronized, Sync Framework treats these two items as different items because they have different item IDs, but the destination replica treats them as the same item because they have the same name. So, the destination provider reports a collision conflict and specifies that the contents of the two items should be merged. The change applier assigns the merged item an ID of id1 and specifies that the destination replica must store a merge tombstone for id2.

When a collision conflict is resolved by merging, it is necessary to select one of the item IDs as the winning item ID that is assigned to the merged item, and to properly track that the losing item ID has been merged. The change applier selects the winning item ID by comparing the two item IDs and selecting the smaller ID as the winning ID. The winning item ID is used to identify the merged item on the destination replica. A merge tombstone is created and stored in the destination replica. The merge tombstone tracks that the losing item ID identifies the same item as the winning item ID in the synchronization community. The metadata for a merge tombstone is the same as that for a deleted item tombstone, with the addition of the winning item ID.

Managed code When the change resolution action is Merge, the change applier calls SaveItemChange and specifies a save action of ChangeIdUpdateVersionAndMergeData. The change applier passes the change with the losing item ID as the change parameter. The change with the winning item ID can be obtained by calling the GetWinnerChange method of the SaveChangeContext object passed in the context parameter.

Unmanaged code When the change resolution action is SCRA_MERGE, the change applier calls ISynchronousNotifyingChangeApplierTarget::SaveChange and specifies a save action of SSA_CHANGE_ID_UPDATE_VERSION_AND_MERGE_DATA. The change applier passes the change with the losing item ID as the pChange parameter. The change with the winning item ID can be obtained by calling the ISaveChangeContext2::GetWinnerChange method of the ISaveChangeContext2 object passed in the pSaveContext parameter.

The destination provider must perform several steps to properly process the merge action. Consider a merge action that specifies id1 as the losing item ID and id2 as the winning item ID. The destination provider must perform the following steps in one transaction.

  1. Store a merge tombstone in the destination metadata. The merge tombstone contains id1 as the losing item ID and id2 as the winning item ID. If a merge tombstone already exists in the destination replica that contains id1 as the losing item ID and another item ID, id3, as the winning item ID, the provider performs the following steps.

    1. If id2 is greater than id3, the provider creates and stores two merge tombstones. One merge tombstone contains id1 as the losing item ID and id2 as the winning item ID. The other merge tombstone contains id2 as the losing item ID and id3 as the winning item ID. This second merge tombstone may already exist, in which case it is simply left alone. In this way, a chain of merge tombstones is created, in decreasing order by item ID.

    2. If id3 is greater than id2, the provider returns an error.

  2. Merges the data for the item in the destination replica with the data for the item from the source provider. The destination item may by identified by either id1 or id2.

  3. Applies the metadata for the change to the destination metadata and the merged data for the change to the destination item store, using the winning item ID, id2, as the item ID for the merged change. The metadata for the change can be obtained by calling the GetWinnerChange method of context (for managed code) or the GetWinnerChange method of pContext (for unmanaged code).

Propagating a Merged Item

Propagation of merged items from a collision constraint conflict differs from propagation of merged items from a concurrency conflict because conflicts can occur with the winning item ID, the losing item ID, or both. For example, replica X contains an item with an ID of id1 that was merged from items with IDs of id1 and id2. Replica Y contains an item with an ID of id2, and has made local changes to the item. When the merged item identified by id1 is sent from replica X to replica Y, a conflict arises because id1 now refers to the same item as id2.

The change applier helps the destination provider apply a merged item change by detecting concurrency conflicts that occur both with the winning item ID and the losing item ID, and by determining the correct action the destination provider must take to apply the merged item change to the destination replica. If the destination provider detects a constraint conflict when it applies a merged item change, it must report the constraint conflict the same any other constraint conflict.

The change applier also detects when the source replica and the destination replica disagree about the identity of an item. For example, replica X resolves a collision conflict between items with IDs id1 and id2 by merging the items and assigning id1 to the merged item. Replica Y resolves a collision conflict between items with IDs id1 and id2 by renaming the item identified by id1 and keeping both items. Replica X sends the merged item identified by id1 and a merge tombstone that indicates that id2 has been merged into id1. The conflict on id1 is detected and resolved as a concurrency conflict. The conflict on id2 is detected and reported to the synchronization application as an identity conflict by specifying a conflict reason of Identity (for managed code) or CCR_IDENTITY (for unmanaged code). The application determines whether to resolve the conflict by keeping the source change or the destination change.

Be aware that, when the source provider uses change unit filtering and the destination provider is unfiltered, an identity conflict can sometimes occur even when the item identified by the merge tombstone is to be removed from the destination replica. This occurs because of the way the filtered knowledge is handled by the change applier. To ensure proper synchronization and propagation of the merge tombstone, the destination provider must keep the merge tombstone and remove the conflicting item from the destination replica.

When a merged item change is sent by the source provider, the change applier determines the correct action the destination provider must take to apply the change to the destination replica. The following table lists the save actions the change applier can specify, and the actions the provider must take to apply the change.

Save action Provider actions

ChangeIdUpdateVersionAndSaveData (for managed code), SSA_CHANGE_ID_UPDATE_VERSION_AND_SAVE_DATA (for unmanaged code)

Store a merge tombstone for the losing item ID, by following the same steps outlined in Merging Conflicting Items, above. Apply the winning item change.

ChangeIdUpdateVersionOnly (for managed code), SSA_CHANGE_ID_UPDATE_VERSION_ONLY (for unmanaged code)

Store a merge tombstone for the losing item ID, by following the same steps outlined in Merging Conflicting Items, above. Apply only metadata for the winning item change.

ChangeIdUpdateVersionAndDeleteAndStoreTombstone (for managed code), SSA_CHANGE_ID_UPDATE_VERSION_AND_DELETE_AND_STORE_TOMBSTONE (for unmanaged code)

Store a merge tombstone for the losing item ID, by following the same steps outlined in Merging Conflicting Items, above. Delete the item identified by the winning item ID, and store a tombstone for it.

StoreMergeTombstone (for managed code), SSA_STORE_MERGE_TOMBSTONE (for unmanaged code)

Store a merge tombstone for the losing item ID, by following the same steps outlined in Merging Conflicting Items, above.

Note

All steps in a save action must be applied as an atomic action.

See Also

Reference

ISaveChangeContext2 Interface
ISaveChangeWithChangeUnitsContext2 Interface
ISynchronousNotifyingChangeApplier2 Interface
ISynchronousNotifyingChangeApplierTarget2 Interface
SaveChangeContext
SaveChangeWithChangeUnitsContext
NotifyingChangeApplier
INotifyingChangeApplierTarget2

Concepts

Handling Conflicts
Detecting and Resolving Concurrency Conflicts