다음을 통해 공유


방법: 제약 조건 충돌 보고 및 해결

이 항목에서는 관리되는 언어를 사용하여 표준 사용자 지정 공급자에서 제약 조건 충돌을 보고 및 해결할 수 있도록 설정하는 방법에 대해 설명합니다. 제약 조건 충돌은 항목에 적용되는 제약 조건(예: 파일 시스템 내에서 폴더의 관계 또는 이름이 같은 데이터의 위치 등)을 위반하는 충돌입니다. Sync Framework에서는 제약 조건 충돌을 처리하는 데 도움이 되는 NotifyingChangeApplier 클래스를 제공합니다.

제약 조건 충돌에 대한 자세한 내용은 제약 조건 충돌 검색 및 해결을 참조하십시오.

이 항목에서는 기본적인 C# 및 Microsoft .NET Framework 개념에 익숙하다고 가정합니다.

이 항목의 예제에서는 다음과 같은 Sync Framework 클래스 및 메서드를 중점적으로 설명합니다.

제약 조건 충돌 이해

제약 조건 충돌은 대상 공급자에서 동기화의 변경 내용 적용 단계 중에, 대개 SaveItemChange 또는 SaveChangeWithChangeUnits 메서드에서 검색됩니다. 대상 공급자가 제약 조건 충돌을 검색하면 RecordConstraintConflictForChangeUnit 또는 RecordConstraintConflictForItem 메서드를 사용하여 NotifyingChangeApplier 개체에 해당 충돌을 보고합니다. 변경 내용 적용자는 세션에 설정된 중복 충돌 해결 정책 또는 지정된 충돌에 대해 응용 프로그램에서 설정한 충돌 해결 동작에 따라 충돌을 해결합니다. 그런 다음 변경 내용 적용자는 대상 공급자가 해결된 충돌을 대상 복제본에 적용할 수 있도록 대상 공급자에 필요한 SaveItemChange 또는 SaveChangeWithChangeUnits 등의 호출을 디스패치합니다. 일반적인 제약 조건 충돌 해결 방법은 제약 조건 충돌이 더 이상 발생하지 않도록 원본 항목이나 대상 항목의 이름을 바꾸거나 두 항목의 내용을 하나의 항목으로 병합하는 것입니다.

빌드 요구 사항

예제

이 항목의 예제 코드에서는 제약 조건 충돌을 처리할 수 있도록 변경 내용 적용자를 설정하는 방법, 동기화의 변경 내용 적용 단계에서 제약 조건 충돌을 검색하고 보고하는 방법, 동기화 응용 프로그램에서 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 메서드를 호출하여 임시 충돌을 저장합니다. 이 예제에서는 KnowledgeSyncProvider를 구현하는 클래스에 INotifyingChangeApplierTarget2 인터페이스를 추가합니다.

class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget
    , INotifyingChangeApplierTarget2
    , IChangeDataRetriever

이 예제에서는 MemoryConflictLog 개체를 사용하여 임시 충돌을 저장하고 그 밖의 경우에는 예외를 throw하여 SaveConstraintConflict를 구현합니다.

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의 변경 내용 동작으로 인해 연락처 저장소의 ID 충돌이 발생할 때 RecordConstraintConflictForItem을 호출하여 SaveChangeWithChangeUnits 메서드에서 제약 조건 충돌이 보고됩니다.

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 메서드를 사용하여 만들려는 연락처에서 발생한 ID 충돌이 있는지 여부를 검색합니다. ID 충돌은 만들려는 연락처에 포함된 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;
}

전체 공급자 코드

이 문서에서 사용된 전체 공급자 코드는 제약 조건 충돌을 보고하고 해결하는 예제 코드를 참조하십시오.

다음 단계

다음으로, 동기화 세션이 끝난 후 충돌을 유지할 수 있는 충돌 로그를 구현할 수 있습니다. 충돌 로그를 만드는 방법에 대한 자세한 내용은 충돌 기록 및 관리를 참조하십시오.

참고 항목

개념

일반적인 표준 사용자 지정 공급자 태스크 프로그래밍
제약 조건 충돌 검색 및 해결