共用方式為


HOW TO:報告及解決條件約束衝突

本主題描述如何使用 Managed 語言來啟用標準自訂提供者,以報告及解決條件約束衝突。條件約束衝突是違反在項目上所設之條件約束 (例如資料夾的關聯性或檔案系統之中名稱完全相同之資料的位置) 的衝突。Sync Framework 提供 NotifyingChangeApplier 類別來協助處理條件約束衝突。

如需條件約束衝突的詳細資訊,請參閱偵測及解決條件約束衝突

本主題假設您對於 C# 和 Microsoft .NET Framework 概念有基本的了解。

本主題的範例將重點放在下列 Sync Framework 類別和方法:

了解條件約束衝突

條件約束衝突是由目的地提供者在變更套用同步處理階段所偵測,通常是在 SaveItemChangeSaveChangeWithChangeUnits 方法中。當目的地提供者偵測到條件約束衝突時,它會使用 RecordConstraintConflictForChangeUnitRecordConstraintConflictForItem 方法將衝突提報給 NotifyingChangeApplier 物件。變更套用者會根據針對此工作階段所設定的衝突解決原則,或是應用程式針對指定之衝突所設定的衝突解決動作,解決此衝突。然後,變更套用者就會分派任何必要的目的地提供者呼叫,例如 SaveItemChangeSaveChangeWithChangeUnits,好讓目的地提供者能夠將解決的衝突套用至目的地複寫。條件約束衝突的一般解決方案包括將來源項目或目的地項目重新命名,讓條件約束衝突不再發生,或是將兩個項目的內容合併成單一項目。

組建需求

範例

本主題的範例程式碼會示範如何讓變更套用者處理條件約束衝突、如何在變更套用同步處理階段偵測及報告條件約束衝突、如何在同步處理應用程式中處理 ItemConstraint 事件來設定解決動作,以及如何將解決方案套用到目的地複寫來解決衝突。這個範例的複寫會將連絡人當做以逗號分隔的值清單儲存在文字檔中。要同步處理的項目是這個檔案中所包含的連絡人,要追蹤的變更單位則是每一個連絡人中的欄位。連絡人存放區中會透過 name 和 phone number 欄位的組合來唯一識別連絡人。在本機的兩個不同複寫上建立具有相同 name 和 phone number 的連絡人時,然後當同步處理這些複寫時,就會發生條件約束衝突。

讓 NotifyingChangeApplier 物件處理條件約束衝突

必須滿足下列所有需求,NotifyingChangeApplier 物件才能夠順利處理條件約束衝突。

建立 IConflictLogAccess 物件

變更套用者會使用衝突記錄檔來儲存同步處理期間發生的暫時性衝突,好讓工作階段期間可以有效率地處理衝突。如果複寫無法以其他方式儲存衝突,Sync Framework 會提供 MemoryConflictLog 類別,此類別會在記憶體中操作,而且在同步處理工作階段期間可用來儲存暫時性衝突。當工作階段啟動時,此範例會建立記憶體中的衝突記錄檔。

public override void BeginSession(SyncProviderPosition position, SyncSessionContext syncSessionContext)
{
    _sessionContext = syncSessionContext;

    // Create an in-memory conflict log to store temporary conflicts during the session.
    _memConflictLog = new MemoryConflictLog(IdFormats);
}

將 IConflictLogAccess 物件傳遞給 NotifyingChangeApplier 物件

目的地提供者必須在處理其 ProcessChangeBatch 方法期間呼叫 ApplyChanges 方法。請注意所呼叫的 ApplyChanges 格式必須接受衝突解決原則和 IConflictLogAccess 物件。此範例會呼叫 ApplyChanges,指定 ApplicationDefined 的衝突解決原則,並傳遞在 BeginSession 中所建立的記憶體內衝突記錄檔。

public override void ProcessChangeBatch(ConflictResolutionPolicy resolutionPolicy, ChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
{
    // Use the metadata storage service to get the local versions of changes received from the source provider.
    IEnumerable<ItemChange> localVersions = _ContactStore.ContactReplicaMetadata.GetLocalVersions(sourceChanges);

    // Use a NotifyingChangeApplier object to process the changes. Specify a collision conflict resolution policy of 
    // ApplicationDefined and the conflict log that was created when the session started.
    NotifyingChangeApplier changeApplier = new NotifyingChangeApplier(ContactStore.ContactIdFormatGroup);
    changeApplier.ApplyChanges(resolutionPolicy, CollisionConflictResolutionPolicy.ApplicationDefined, sourceChanges, 
        (IChangeDataRetriever)changeDataRetriever, localVersions, _ContactStore.ContactReplicaMetadata.GetKnowledge(), 
        _ContactStore.ContactReplicaMetadata.GetForgottenKnowledge(), this, _memConflictLog, _sessionContext, syncCallbacks);
}

實作 SaveConstraintConflict

變更套用者會呼叫 INotifyingChangeApplierTarget2 介面的 SaveConstraintConflict 方法,以儲存暫時性衝突。此範例會將 INotifyingChangeApplierTarget2 介面加入至實作 KnowledgeSyncProvider 的類別。

class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget
    , INotifyingChangeApplierTarget2
    , IChangeDataRetriever

這個範例會實作 SaveConstraintConflict,透過的方式是使用 MemoryConflictLog 物件來儲存暫時性衝突以及在其他狀況下擲回例外狀況。

public void SaveConstraintConflict(ItemChange conflictingChange, SyncId conflictingItemId, 
    ConstraintConflictReason reason, object conflictingChangeData, SyncKnowledge conflictingChangeKnowledge, 
    bool temporary)
{
    if (!temporary)
    {
        // The in-memory conflict log is used, so if a non-temporary conflict is saved, it's
        // an error.
        throw new NotImplementedException("SaveConstraintConflict can only save temporary conflicts.");
    }
    else
    {
        // For temporary conflicts, just pass on the data and let the conflict log handle it.
        _memConflictLog.SaveConstraintConflict(conflictingChange, conflictingItemId, reason, 
            conflictingChangeData, conflictingChangeKnowledge, temporary);
    }
}

實作 TryGetDestinationVersion

變更套用者必須能夠取得發生衝突之目的地項目的版本。若要提供這個項目,目的地提供者會實作 INotifyingChangeApplierTarget 介面的 TryGetDestinationVersion 方法。如果未報告條件約束衝突,這個方法為選擇項,否則為必要項。

public bool TryGetDestinationVersion(ItemChange sourceChange, out ItemChange destinationVersion)
{
    bool found = false;
    // Get the item metadata from the metadata store.
    ItemMetadata itemMeta = _ContactStore.ContactReplicaMetadata.FindItemMetadataById(sourceChange.ItemId);
    if (null != itemMeta)
    {
        // The item metadata exists, so translate the change unit metadata to the proper format and
        // return the item change object.
        ChangeUnitChange cuChange;
        List<ChangeUnitChange> cuChanges = new List<ChangeUnitChange>();
        foreach (ChangeUnitMetadata cuMeta in itemMeta.GetChangeUnitEnumerator())
        {
            cuChange = new ChangeUnitChange(IdFormats, cuMeta.ChangeUnitId, cuMeta.ChangeUnitVersion);
            cuChanges.Add(cuChange);
        }
        destinationVersion = new ItemChange(IdFormats, _ContactStore.ContactReplicaMetadata.ReplicaId, sourceChange.ItemId,
            ChangeKind.Update, itemMeta.CreationVersion, cuChanges);

        found = true;
    }
    else
    {
        destinationVersion = null;
    }
    return found;
}

報告條件約束衝突

條件約束衝突是由目的地提供者在變更套用同步處理階段所偵測。此範例會使用變更單位,使得 Create 的變更動作造成連絡人存放區中的識別衝突時,透過呼叫 RecordConstraintConflictForItemSaveChangeWithChangeUnits 方法中報告條件約束衝突。

case SaveChangeAction.Create:
{
    // Create a new item. Report a constraint conflict if one occurs.
    try
    {
        ConstraintConflictReason constraintReason;
        SyncId conflictingItemId;
        // Check if the item can be created or if it causes a constraint conflict.
        if (_ContactStore.CanCreateContact(change, (string[])context.ChangeData, out constraintReason, out conflictingItemId))
        {
            // No conflict, so create the item.
            _ContactStore.CreateContactFromSync(change, (string[])context.ChangeData);
        }
        else
        {
            // A constraint conflict occurred, so report this to the change applier.
            context.RecordConstraintConflictForItem(conflictingItemId, constraintReason);
        }
    }
    catch (Exception ex)
    {
        // Some other error occurred, so exclude this item for the rest of the session.
        RecoverableErrorData errData = new RecoverableErrorData(ex);
        context.RecordRecoverableErrorForItem(errData);
    }
    break;
}

上述範例所呼叫之連絡人存放區的 CanCreateContact 方法會使用私用 DetectIndexCollision 方法,以偵測要建立的連絡人是否會造成識別衝突。當要建立的連絡人包含與現有連絡人相同的 name 和 phone number 欄位時,就會發生識別衝突。

public bool CanCreateContact(ItemChange itemChange, string[] changeData, out ConstraintConflictReason reason, out SyncId conflictingItemId)
{
    bool canCreate = true;

    // Create a temporary contact and see if its index values conflict with any item already in the contact store.
    Contact newContact = new Contact(changeData);
    canCreate = !DetectIndexCollision(newContact, out conflictingItemId);
    if (!canCreate)
    {
        // An index collision occurred, so report a collision conflict.
        reason = ConstraintConflictReason.Collision;
    }
    else
    {
        // This value won't be used because canCreate is set to true in this case.
        reason = ConstraintConflictReason.Other;
    }

    return canCreate;
}
private bool DetectIndexCollision(Contact newContact, out SyncId conflictingItemId)
{
    bool collision = false;
    conflictingItemId = null;

    // Enumerate the contacts in the list and determine whether any have the same name and phone number
    // as the contact to create.
    IEnumerator<KeyValuePair<SyncId, Contact>> contactEnum = _ContactList.GetEnumerator();
    while (contactEnum.MoveNext())
    {
        if (contactEnum.Current.Value.IsIndexEqual(newContact))
        {
            // A conflicting item exists, so return its item ID and a value that indicates the collision.
            conflictingItemId = contactEnum.Current.Key;
            collision = true;
            break;
        }
    }

    return collision;
}

為條件約束衝突設定解決動作

當目的地提供者指定 ApplicationDefined 的衝突解決原則時,應用程式必須在啟動同步處理之前,先讓處理常式註冊 ItemConstraint 事件。

// Register to receive the ItemConstraint event from both providers. Only the destination provider actually fires
// this event during synchronization.
((KnowledgeSyncProvider)localProvider).DestinationCallbacks.ItemConstraint += new EventHandler<ItemConstraintEventArgs>(HandleItemConstraint);
((KnowledgeSyncProvider)remoteProvider).DestinationCallbacks.ItemConstraint += new EventHandler<ItemConstraintEventArgs>(HandleItemConstraint);

ItemConstraint 事件處理常式會決定如何解決衝突。此範例會向使用者顯示衝突的項目,並詢問應該如何解決衝突。

void HandleItemConstraint(Object sender, ItemConstraintEventArgs args)
{
    if (ConstraintConflictReason.Collision == args.ConstraintConflictReason)
    {
        // Display the two items that are in conflict and solicit a resolution from the user.
        Contact srcContact = new Contact((string[])args.SourceChangeData);
        Contact destContact = new Contact((string[])args.DestinationChangeData);
        string msg = "Source change is " + srcContact.ToString() +
                   "\nDestination change is " + destContact.ToString() +
                   "\nClick Yes to rename the source change and apply it." +
                   "\nClick No to rename the destination item and apply the source change." +
                   "\nClick Cancel to delete the destination item and apply the source change.";
        ConstraintConflictDlg ccDlg = new ConstraintConflictDlg(msg);
        ccDlg.ShowDialog();

        // Set the resolution action based on the user's response.
        args.SetResolutionAction(ccDlg.Resolution);
    }
    else 
    {
        args.SetResolutionAction(ConstraintConflictResolutionAction.SaveConflict);
    }
}

處理條件約束衝突的解決方案

當應用程式已經設定條件約束衝突解決動作之後,變更套用者會針對與此解決方案有關的中繼資料進行任何必要的變更,並分派目的地提供者的呼叫,好讓它可以將變更套用到目的地複寫。此範例會使用變更單位,讓變更套用者呼叫目的地提供者的 SaveChangeWithChangeUnits 方法。這個方法中會處理由應用程式呈現給使用者的三個可能解決方案。

case SaveChangeAction.DeleteConflictingAndSaveSourceItem:
{
    // Delete the destination item that is in conflict and save the source item.

    // Make a new local version for the delete so it will propagate correctly throughout the synchronization community.
    SyncVersion version = new SyncVersion(0, _ContactStore.ContactReplicaMetadata.GetNextTickCount());
    _ContactStore.DeleteContactFromSync(context.ConflictingItemId, version);

    // Save the source change as a new item.
    _ContactStore.CreateContactFromSync(change, (string[])context.ChangeData);

    break;
}
case SaveChangeAction.RenameDestinationAndUpdateVersionData:
{
    // Rename the destination item so that it no longer conflicts with the source item and save the source item.

    // Rename the destination item. This is done by appending a value to the name and updating the metadata to treat
    // this as a local change, which will be propagated throughout the synchronization community.
    _ContactStore.RenameExistingContact(context.ConflictingItemId);

    // Save the source change as a new item.
    _ContactStore.CreateContactFromSync(change, (string[])context.ChangeData);

    break;
}
case SaveChangeAction.RenameSourceAndUpdateVersionAndData:
{
    // Rename the source item so that it no longer conflicts with the destination item and save the source item.
    // The destination item in conflict remains untouched.

    // Rename the source item. This is done by appending a value to the name.
    _ContactStore.FindNewNameForContact((string[])context.ChangeData);

    // Save the renamed source change as a new item.
    _ContactStore.CreateContactFromSync(change, (string[])context.ChangeData);

    break;
}

完整的提供者程式碼

如需此文件中使用之提供者程式碼的完整清單,請參閱報告及解決條件約束衝突的範例程式碼

後續的步驟

接下來,您可能會想要實作衝突記錄檔,以便可以保存超出同步處理工作階段結尾的衝突。如需建立衝突記錄檔的詳細資訊,請參閱記錄及管理衝突

請參閱

概念

程式設計一般標準的自訂提供者工作
偵測及解決條件約束衝突