检测和解决约束冲突
约束冲突指违反有关项的约束(如文件夹的关系或文件系统中同名数据的位置)的冲突。Sync Framework 提供了简化约束冲突解决的变更应用方对象。
约束冲突是在同步的变更应用阶段由目标提供程序检测到的。当目标提供程序检测到约束冲突时,它将该冲突报告给变更应用方。变更应用方根据为会话设置的冲突解决策略或应用程序为指定的冲突设置的冲突解决操作来解决冲突。然后,变更应用方对目标提供程序调度任何必要的调用,以便目标提供程序可以将已解决的冲突应用到目标副本。
当提供程序报告约束冲突并使用变更应用方时,它还必须提供冲突日志,变更应用方使用该日志来处理和记录冲突。Sync Framework 为不实现自己的冲突日志的提供程序提供了冲突日志的内存中实现。有关更多信息,请参见记录和管理冲突。
请注意,使用自定义筛选器或变更应用服务的提供程序无法使用约束冲突,否则可能出现意外结果。
约束冲突的类型
约束冲突取决于由目标副本使用的数据存储区,因此可能采用多种形式。Sync Framework 将约束冲突划分为以下三种类型。
“抵触冲突”:当某一项与目标存储区中的另一项发生冲突而无法保存该项时(例如,当源提供程序发送的文件与目标副本中已存在的文件具有相同的名称和位置时),发生此类冲突。
“缺少父项冲突”:当某个项所需的父项不存在而无法在分层数据存储区中保存该项时(例如,当源提供程序发送一个文件以保存在某个目录中,但该目录在目标副本中不存在时),将发生此类冲突。
其他约束冲突:当要保存的项违反了目标副本的约束时(例如,源提供程序发送的文件过大而无法保存在目标副本上,或者变更违反了目标副本上的某些业务逻辑),发生此类冲突。作为业务逻辑冲突的示例,请考虑存储两个变更单位(name 和 country)的低保真副本。另请考虑存储三个变更单位(name、state/province 和 country)的高保真副本。高保真副本包含的业务逻辑针对 country 字段检查 state/province 字段,不会存储未通过检查的变更。低保真副本充当源并发送一个 country 设置为“USA”的项。目标提供程序尝试将变更应用到高保真副本,但在高保真副本上,该项的 state/province 字段中包含“British Columbia”。因此,变更违反了业务逻辑并引发了约束冲突。
目标提供程序从以下值中进行选择来指定约束冲突的原因。
约束冲突原因 | 说明 |
---|---|
Collision(对于托管代码),CCR_COLLISION(对于非托管代码) |
该项无法保存,原因是它与存储区中的其他项冲突,例如该项具有与现有项相同的名称。提供程序必须将目标项的 ID 指定为冲突项 ID。 |
NoParent(对于托管代码),CCR_NOPARENT(对于非托管代码) |
该项无法保存到分层数据存储区中,原因是该项需要的父项在存储区中不存在。提供程序可以选择将缺失父项的 ID 指定为冲突项 ID。 |
Other(对于托管代码),CCR_OTHER(对于非托管代码) |
该项或变更单位违反了目标副本的某个其他约束。提供程序可以选择将发生冲突的项的 ID 指定为冲突项 ID。 |
检测和报告约束冲突
约束冲突的检测取决于由副本使用的数据存储区。因此,必须由目标提供程序检测约束冲突。例如,表示分层文件系统的提供程序必须能够检测特定于存储区的有关持久化数据(如位置、命名、大小等)的约束。
约束冲突是在同步的变更应用阶段由目标提供程序检测到的。
托管代码 约束冲突通常是由目标提供程序在其 SaveItemChange 或 SaveChangeWithChangeUnits 方法中检测到的,并通过调用 RecordConstraintConflictForItem 或 RecordConstraintConflictForChangeUnit 来报告。
非托管代码 约束冲突通常是由目标提供程序在其 ISynchronousNotifyingChangeApplierTarget::SaveChange 或 ISynchronousNotifyingChangeApplierTarget::SaveChangeWithChangeUnits 方法中检测到的,并通过调用 ISaveChangeContext2::SetConstraintConflictOnChange 或 ISaveChangeWithChangeUnitsContext2::SetConstraintConflictOnChangeUnit 来报告。
当变更应用方收到约束冲突报告时,它将执行多项操作:
如果约束冲突是抵触冲突,则变更应用方将确定冲突是新冲突、临时冲突还是合并解决传播。在处理冲突的过程中,变更应用方可能通过调用 SaveConstraintConflict(对于托管代码)或 ISynchronousNotifyingChangeApplierTarget2::SaveConstraintConflict(对于非托管代码)临时将冲突存储在冲突日志中。变更应用方在同步会话结束时从日志中删除临时冲突。
应根据为会话设置的抵触冲突解决策略或根据由应用程序确定的冲突解决操作来解决抵触冲突。当抵触冲突解决策略设置为 ApplicationDefined(对于托管代码)或 CCRP_NONE(对于非托管代码)时,将调用应用程序来确定冲突解决操作。
根据应用程序确定的冲突解决操作来解决非抵触约束冲突。不能为非抵触约束冲突设置冲突解决策略。
解决约束冲突
变更应用方通过对由目标提供程序指定的变更应用方目标对象调度调用,帮助提供程序解决约束冲突。当指定抵触冲突解决策略后,变更应用方将使用该策略来确定要解决所发生的每个抵触冲突应采取的正确冲突解决操作。当指定自定义抵触冲突解决办法时,变更应用方将向同步应用程序通知此抵触冲突,然后应用程序指定冲突解决操作。无法为非抵触约束冲突指定冲突解决策略,因此,当报告了非抵触约束冲突时,变更应用方始终通知应用程序,以便应用程序可以指定冲突解决操作。在所有情况下,变更应用方都将调用适当的变更应用方目标方法,并且变更应用方目标对象将执行操作,例如,将变更保存到副本或记录冲突供以后处理。
指定抵触冲突解决策略
在启动同步之前,同步应用程序通常会指定抵触冲突解决策略。
托管代码 应用程序通过将目标提供程序的 CollisionConflictResolutionPolicy 属性设置为所需的值来指定策略。
非托管代码 应用程序通过使用自定义机制(例如,应用程序通过对目标提供程序对象调用 QueryInterface 所获得的自定义接口)来指定策略。
目标提供程序将抵触冲突解决策略传递到变更应用方的 ApplyChanges(对于托管代码)或 ISynchronousNotifyingChangeApplier2::ApplyChanges(对于非托管代码)方法,以便变更应用方可以正确地将方法调度到变更应用方目标。变更应用方目标由 INotifyingChangeApplierTarget2(对于托管代码)或 ISynchronousNotifyingChangeApplierTarget2(对于非托管代码)对象表示。
托管代码的抵触冲突解决策略
Sync Framework 为托管代码定义以下抵触冲突解决策略。
冲突解决策略 | 说明 |
---|---|
对源副本所做的变更始终入选。Sync Framework 指定冲突解决操作 SourceWins。 |
|
对目标副本所做的变更始终入选。Sync Framework 指定冲突解决操作 DestinationWins。 |
|
对从源提供程序发送的变更进行重命名,以使其不再与目标副本上的冲突项发生冲突,并且将源变更应用到目标副本。Sync Framework 将指定冲突解决操作 RenameSource。 |
|
目标副本上的冲突项已重命名,这样,它不再与从源提供程序发送的变更冲突,并且源变更应用于目标副本。Sync Framework 指定 RenameDestination 的冲突解决操作。 |
|
来自源项的数据与目标项结合。Sync Framework 指定冲突解决操作 Merge。 |
|
对于发生的每个抵触冲突,变更应用方都将使用 ItemConstraint 事件向同步应用程序进行通知。该应用程序检查冲突项,并通过调用 SetResolutionAction 指定冲突解决操作。 |
非托管代码的抵触冲突解决策略
Sync Framework 为非托管代码定义以下抵触冲突解决策略。
冲突解决策略 | 说明 |
---|---|
CCRP_SOURCE_PROVIDER_WINS |
对源副本的变更始终入选。Sync Framework 指定冲突解决操作 SCRA_ACCEPT_SOURCE_PROVIDER。 |
CCRP_DESTINATION_PROVIDER_WINS |
对目标副本所做的变更始终入选。Sync Framework 指定冲突解决操作 SCRA_ACCEPT_DESTINATION_PROVIDER。 |
CCRP_RENAME_SOURCE |
从源提供程序发送的变更已重命名,这样,它不再与目标副本上的冲突项发生冲突,并且源变更应用于目标副本。Sync Framework 指定 SCRA_RENAME_SOURCE 的一个冲突解决操作。 |
CCRP_RENAME_DESTINATION |
目标副本上的冲突项已重命名,这样,它不再与从源提供程序发送的变更冲突,并且源变更应用于目标副本。Sync Framework 指定 SCRA_RENAME_DESTINATION 的一个冲突解决操作。 |
CCRP_MERGE |
来自源项的数据与目标项结合。Sync Framework 指定冲突解决操作 SCRA_MERGE。 |
CCRP_NONE |
对于发生的每个抵触冲突,变更应用方都将使用 ISyncConstraintCallback::OnConstraintConflict 事件向同步应用程序进行通知。该应用程序检查冲突项,并通过调用 IConstraintConflict::SetConstraintResolveActionForChange 或 IConstraintConflict::GetConstraintResolveActionForChangeUnit 指定冲突解决操作。 |
指定自定义冲突解决办法
应用程序可以指示它将为发生的每个约束冲突指定冲突解决操作。要实现此目的,应用程序应进行注册以接收约束冲突通知。当报告约束冲突时,Sync Framework 通知应用程序。然后,应用程序可以分析冲突,并设置所需的冲突解决操作。
使用托管代码指定自定义冲突解决方法
若要接收抵触冲突通知,应用程序在启动同步之前应执行下列操作。
注册事件处理程序以处理目标提供程序的 ItemConstraint 事件。
将目标提供程序的 CollisionConflictResolutionPolicy 属性设置为 ApplicationDefined。
如果应用程序已执行了这些步骤,则变更应用方对于在同步期间报告的每个抵触约束冲突都将引发一次 ItemConstraint 事件。
由于无法为非抵触约束冲突指定冲突解决策略,因此,变更应用方对于报告的每个非抵触约束冲突,也将引发一次 ItemConstraint 事件。
ItemConstraint 事件的事件处理程序收到一个 ItemConstraintEventArgs 对象,该对象包含两个相互冲突的变更的元数据和项数据。事件处理程序可以通过使用 SetResolutionAction 方法检查两个相互冲突的变更、对元数据或项数据进行变更以及为冲突设置解决操作。然后,变更应用方处理此冲突并对变更应用方目标对象调度适当的调用。请注意,当约束冲突不是抵触冲突时,仅有的有效冲突解决操作是 SaveConflict 和 SkipChange。
Sync Framework 提供了以下一系列约束冲突解决操作,对于这些操作,变更应用方将执行大部分处理工作。
冲突解决操作 | 说明 | 适用的冲突类型 |
---|---|---|
SourceWins |
对源副本的变更入选。变更应用方将变更传递给 SaveItemChange 方法,并指定保存操作 DeleteConflictingAndSaveSourceItem。将源变更应用到目标副本并从目标副本中删除冲突的目标项。 |
仅限抵触冲突。 |
DestinationWins |
对目标副本进行的变更入选。变更应用方将源变更传递给 SaveItemChange 方法,并指定保存操作 DeleteAndStoreTombstone。目标提供程序将为源变更创建逻辑删除。当目标在后续的同步过程中作为源时,目标会枚举一个表示源项删除的变更,这样就会从同步社区中删除它。 |
仅限抵触冲突。 |
RenameSource |
对从源提供程序发送的变更进行重命名,以使其不再与目标副本上的冲突项发生冲突,并且将源变更应用到目标副本。变更应用方将变更传递给 SaveItemChange 方法,并指定保存操作 RenameSourceAndUpdateVersionAndData。 |
仅限抵触冲突。 |
RenameDestination |
对目标副本上的冲突项进行重命名,以使其不再与从源提供程序发送的变更发生冲突,并将源变更应用到目标副本。变更应用方将变更传递给 SaveItemChange 方法,并指定保存操作 RenameDestinationAndUpdateVersionData。 |
仅限抵触冲突。 |
Merge |
源项中的数据与目标项合并。变更应用方将源副本的变更数据传递给 SaveItemChange 方法,并指定保存操作 ChangeIdUpdateVersionAndMergeData。有关详细信息,请参阅下面的合并冲突项。 |
仅限抵触冲突。 |
SaveConflict |
记录冲突,而不应用变更。变更应用方将冲突数据传递给 SaveConstraintConflict 方法,该方法可将冲突保存在冲突日志中。有关冲突记录的更多信息,请参阅记录和管理冲突。 |
所有约束冲突。 |
SkipChange |
忽略冲突,而不应用变更。变更应用方不将冲突数据传递到目标提供程序。 |
所有约束冲突。 |
使用非托管代码指定自定义冲突解决方法
若要接收抵触冲突通知,应用程序在启动同步之前应执行下列操作。
实现 ISyncConstraintCallback::OnConstraintConflict 方法,并通过调用 ISyncSession::RegisterCallback 注册 ISyncConstraintCallback 对象。
通过使用由目标提供程序提供的自定义机制将 CCRP_NONE 指定为抵触冲突解决策略。
如果应用程序已执行了这些步骤,则变更应用方对于在同步期间报告的每个抵触约束冲突都将调用一次 OnConstraintConflict 方法。
由于无法为非抵触约束冲突指定冲突解决策略,因此变更应用方还会为报告的每个非抵触约束冲突调用一次 OnConstraintConflict 方法。
OnConstraintConflict 方法收到一个 IConstraintConflict 对象,该对象包含两个相互冲突的变更的元数据和项数据。该方法可以通过使用 IConstraintConflict::SetConstraintResolveActionForChange 或 IConstraintConflict::GetConstraintResolveActionForChangeUnit 方法检查两个相互冲突的变更、对元数据或项数据进行变更以及为冲突设置解决操作。然后,变更应用方处理此冲突并对变更应用方目标对象调度适当的调用。请注意,当约束冲突不是抵触冲突时,仅有的有效冲突解决操作是 SCRA_TRANSFER_AND_DEFER 和 SCRA_DEFER。
Sync Framework 提供了以下一系列约束冲突解决操作,对于这些操作,变更应用方将执行大部分处理工作。
冲突解决策略 | 说明 | 适用的内容类型 |
---|---|---|
SCRA_ACCEPT_SOURCE_PROVIDER |
对源副本的变更始终入选。变更应用方将变更传递给 ISynchronousNotifyingChangeApplierTarget::SaveChange 方法,并指定保存操作 SSA_DELETE_CONFLICTING_AND_SAVE_SOURCE_ITEM。将源变更应用到目标副本并从目标副本中删除冲突的目标项。 |
仅限抵触冲突。 |
SCRA_ACCEPT_DESTINATION_PROVIDER |
对目标副本的变更始终入选。变更应用方将源变更传递给 SaveChange 方法,并指定保存操作 SSA_DELETE_AND_STORE_TOMBSTONE。目标提供程序将为源变更创建逻辑删除。当目标在后续的同步过程中作为源时,目标会枚举一个表示源项删除的变更,这样就会从同步社区中删除它。 |
仅限抵触冲突。 |
SCRA_RENAME_SOURCE |
对从源提供程序发送的变更进行重命名,以使其不再与目标副本上的冲突项发生冲突,并且将源变更应用到目标副本。变更应用方将变更传递给 SaveChange 方法,并指定保存操作 SSA_RENAME_SOURCE_AND_UPDATE_VERSION_AND_DATA。 |
仅限抵触冲突。 |
SCRA_RENAME_DESTINATION |
对目标副本上的冲突项进行重命名,以使其不再与从源提供程序发送的变更发生冲突,并将源变更应用到目标副本。变更应用方将变更传递给 SaveChange 方法,并指定保存操作 SSA_RENAME_DESTINATION_AND_UPDATE_VERSION_AND_DATA。 |
仅限抵触冲突。 |
SCRA_MERGE |
源项中的数据与目标项合并。变更应用方将源副本的变更数据传递给 SaveChange 方法,并指定保存操作 SSA_CHANGE_ID_UPDATE_VERSION_AND_MERGE_DATA。有关详细信息,请参阅下面的合并冲突项。 |
仅限抵触冲突。 |
SCRA_TRANSFER_AND_DEFER |
记录冲突,而不应用变更。变更应用方将冲突数据传递给 ISynchronousNotifyingChangeApplierTarget2::SaveConstraintConflict 方法,该方法可将冲突保存在冲突日志中。有关冲突记录的更多信息,请参阅记录和管理冲突。 |
所有约束冲突。 |
SCRA_DEFER |
忽略冲突,而不应用变更。变更应用方不将冲突数据传递到目标提供程序。 |
所有约束冲突。 |
合并冲突项
合并抵触约束冲突中的两个项不同于合并并发冲突中的项,因为抵触冲突涉及的两个项具有不同的项 ID。例如,在一个副本上创建一个名为“FavoriteBooks.txt”的文件并给定项 ID 为 id1。同时在另一个副本上创建名为“FavoriteBooks.txt”的文件并给定项 ID 为 id2。当同步副本时,Sync Framework 将这两个项视为不同的项,因为它们具有不同的项 ID;但目标副本将它们视为同一个项,因为它们具有相同的名称。因此,目标提供程序报告一个抵触冲突,并指定应合并这两个项的内容。变更应用方向合并后的项分配的 ID 为 id1,并指定目标副本必须为 id2 存储合并逻辑删除。
当通过合并解决抵触冲突时,务必选择其中一个项 ID 作为入选项 ID(分配给合并项),并务必正确地跟踪已合并了落选项 ID。变更应用方通过比较两个项 ID 并选择较小的 ID 作为入选 ID 来选择入选项 ID。入选项 ID 用于标识目标副本上的合并项。“合并逻辑删除” 在目标副本中创建并存储。合并逻辑删除跟踪同步社区中落选项 ID 与入选项 ID 标识相同项的情况。合并逻辑删除的元数据与删除的项逻辑删除的元数据相同,只是增加了入选项 ID。
托管代码 当变更解决操作为 Merge 时,变更应用方调用 SaveItemChange 并指定保存操作 ChangeIdUpdateVersionAndMergeData。变更应用方将落选项 ID 作为 change 参数来传递变更。可以通过调用在 context 参数中传递的 SaveChangeContext 对象的 GetWinnerChange 方法来获取具有入选项 ID 的变更。
非托管代码当变更解决操作为 SCRA_MERGE 时,变更应用方调用 ISynchronousNotifyingChangeApplierTarget::SaveChange 并指定保存操作 SSA_CHANGE_ID_UPDATE_VERSION_AND_MERGE_DATA。变更应用方将落选项 ID 作为 pChange 参数来传递变更。可以通过调用在 pSaveContext 参数中传递的 ISaveChangeContext2 对象的 ISaveChangeContext2::GetWinnerChange 方法来获取具有入选项 ID 的变更。
目标提供程序必须执行几个步骤来正确处理合并操作。考虑一个合并操作,它将 id1 指定为落选项 ID,将 id2 指定为入选项 ID。目标提供程序必须在一个事务中执行以下步骤。
将合并逻辑删除保存到目标元数据中。合并逻辑删除将 id1 包含为落选项 ID,将 id2 包含为入选项 ID。如果目标副本中已存在将 id1 包含为落选项 ID,将另一个项 ID id3 包含为入选项 ID 的合并逻辑删除,则提供程序执行以下步骤:
如果 id2 大于 id3,则提供程序创建并存储两个合并逻辑删除。一个合并逻辑删除将 id1 包含为落选项 ID,将 id2 包含为入选项 ID。另一个合并逻辑删除将 id2 包含为落选项 ID,将 id3 包含为入选项 ID。第二个合并逻辑删除可能已存在,在这种情况下无需再处理它。以这种方式即可创建按项 ID 的降序排列的合并逻辑删除链。
如果 id3 大于 id2,提供程序将返回错误。
将目标副本中该项的数据与源提供程序中该项的数据合并。目标项可以通过 id1 或 id2 标识。
使用入选项 ID id2 作为合并变更的项 ID,将变更的元数据应用到目标元数据,并将变更的合并数据应用到目标项存储区。可以通过调用 context 的 GetWinnerChange 方法(对于托管代码)或 pContext 的 GetWinnerChange 方法(对于非托管代码)来获取变更的元数据。
传播合并项
传播抵触约束冲突中的合并项不同于传播并发冲突中的合并项,因为发生冲突时可能具有入选项 ID、落选项 ID 或这两者。例如,副本 X 包含一个项,该项的 ID 为 id1,它是由 ID 分别为 id1 和 id2 的项合并得到的。副本 Y 包含一个项(该项的 ID 为 id2)并对该项进行了本地变更。当将由 id1 标识的合并项从副本 X 发送到副本 Y 时,将引发冲突,因为 id1 现在与 id2 指同一个项。
变更应用方通过检测所发生的同时具有入选项 ID 和落选项 ID 的并发冲突,并确定目标提供程序将合并项变更应用于目标副本所必须采取的正确操作,来帮助目标提供程序应用合并项变更。如果目标提供程序在应用合并项变更时检测到约束冲突,它必须报告与任何其他约束冲突相同的约束冲突。
变更应用方还检测源副本和目标副本何时就项的标识产生不一致。例如,副本 X 通过将 ID 为 id1 和 id2 的两个项合并,并将 id1 分配给合并后的项,从而解决了二者之间的抵触冲突。副本 Y 通过将 ID 分别为 id1 和 id2 的两个项中由 id1 标识的项进行重命名,并保留这两个项,从而解决了二者之间的抵触冲突。副本 X 发送由 id1 标识的合并项,以及一个指示 id2 已经合并入 id1 的合并删除逻辑。系统会检测到 id1 冲突,并作为并发冲突解决。系统检测到有关 id2 的冲突,并通过指定冲突原因 Identity(对于托管代码)或 CCR_IDENTITY(对于非托管代码),将其作为标识冲突报告给同步应用程序。同步应用程序确定是通过保留源变更,还是保留目标变更来解决冲突。
请注意,当源提供程序使用变更单位筛选,但不筛选源提供程序时,有时可能会发生标识冲突,甚至当从目标副本中删除由合并逻辑删除标识的项时也是如此。这是由于变更应用方处理筛选知识的方式所致。为了确保正确地同步和传播合并逻辑删除,目标提供程序必须保留合并逻辑删除,并从目标副本中删除冲突项。
当源提供程序发送合并项变更时,变更应用方确定目标提供程序将变更应用于目标副本所必须采取的正确操作。下表列出了变更应用方可以指定的保存选项以及提供程序应用变更所必须采取的操作。
保存操作 | 提供程序操作 |
---|---|
ChangeIdUpdateVersionAndSaveData(对于托管代码),SSA_CHANGE_ID_UPDATE_VERSION_AND_SAVE_DATA(对于非托管代码) |
按照合并冲突项中所述的相同步骤,存储落选项 ID 的合并逻辑删除。应用入选项变更。 |
ChangeIdUpdateVersionOnly(对于托管代码),SSA_CHANGE_ID_UPDATE_VERSION_ONLY(对于非托管代码) |
按照合并冲突项中所述的相同步骤,存储落选项 ID 的合并逻辑删除。仅应用入选项变更的元数据。 |
ChangeIdUpdateVersionAndDeleteAndStoreTombstone(对于托管代码),SSA_CHANGE_ID_UPDATE_VERSION_AND_DELETE_AND_STORE_TOMBSTONE(对于非托管代码) |
按照合并冲突项中所述的相同步骤,存储落选项 ID 的合并逻辑删除。删除入选项 ID 所标识的项,并存储它的逻辑删除。 |
StoreMergeTombstone(对于托管代码),SSA_STORE_MERGE_TOMBSTONE(对于非托管代码) |
按照合并冲突项中所述的相同步骤,存储落选项 ID 的合并逻辑删除。 |
备注
保存操作中的所有步骤都必须作为一个原子操作应用。
请参阅
参考
ISaveChangeContext2 接口
ISaveChangeWithChangeUnitsContext2 接口
ISynchronousNotifyingChangeApplier2 接口
ISynchronousNotifyingChangeApplierTarget2 接口
SaveChangeContext
SaveChangeWithChangeUnitsContext
NotifyingChangeApplier
INotifyingChangeApplierTarget2