Freigeben über


Vorgehensweise: Melden und Auflösen von Einschränkungskonflikten

In diesem Thema wird beschrieben, wie ein benutzerdefinierter Standardanbieter Einschränkungskonflikte mit einer verwalteten Sprache melden und auflösen kann. Einschränkungskonflikte sind Konflikte, bei denen Elementen auferlegte Einschränkungen (z. B. die Beziehung zwischen Ordnern oder der Speicherort identisch benannter Daten innerhalb eines Dateisystems) verletzt werden. Sync Framework stellt die NotifyingChangeApplier-Klasse zur Unterstützung der Verarbeitung von Einschränkungskonflikten bereit.

Weitere Informationen zu Einschränkungskonflikten finden Sie unter Erkennen und Auflösen von Einschränkungskonflikten.

Dieses Thema setzt grundlegende Kenntnisse über C#- und Microsoft .NET Framework-Konzepte voraus.

In den Beispielen dieses Themas liegt der Schwerpunkt auf den folgenden Sync Framework-Klassen und -Methoden:

Grundlegendes zu Einschränkungskonflikten

Einschränkungskonflikte werden vom Zielanbieter in der Regel in der SaveItemChange-Methode oder der SaveChangeWithChangeUnits-Methode während der Änderungsanwendungsphase der Synchronisierung erkannt. Wenn der Zielanbieter einen Einschränkungskonflikt erkennt, meldet er dem NotifyingChangeApplier-Objekt den Konflikt mit der RecordConstraintConflictForChangeUnit-Methode oder der RecordConstraintConflictForItem-Methode. Der Änderungsanwender löst den Konflikt entweder anhand der für die Sitzung festgelegten Richtlinie für die Kollisionskonfliktauflösung auf, oder mit der festgelegten Konfliktauflösungsaktion für den von der Anwendung angegebenen Konflikt. Der Änderungsanwender leitet dann alle notwendigen Aufrufe an den Zielanbieter weiter, z. B. SaveItemChange oder SaveChangeWithChangeUnits, damit dieser den aufgelösten Konflikt auf das Zielreplikat anwenden kann. Typische Lösungen von Einschränkungskonflikten sind das Umbenennen des Quellelements oder Zielelements, damit der Einschränkungskonflikt nicht mehr auftritt, oder das Zusammenführen des Inhalts der zwei Elemente mit einem einzelnen Element.

Erstellungsanforderungen

Beispiel

Im Beispielcode in diesem Thema wird gezeigt, wie der Änderungsanwender zur Behandlung von Einschränkungskonflikten aktiviert wird, wie ein Einschränkungskonflikt während der Änderungsanwendungsphase der Synchronisierung erkannt und gemeldet wird, wie eine Auflösungsaktion durch Behandlung des ItemConstraint-Ereignisses in der Synchronisierungsanwendung festgelegt wird und wie der Konflikt durch Anwendung der Auflösung für das Zielreplikat aufgelöst wird. Das Replikat in diesem Beispiel speichert Kontakte als Liste von durch Trennzeichen getrennten Werten in einer Textdatei. Die zu synchronisierenden Elemente sind die in dieser Datei enthaltenen Kontakte, und die Felder innerhalb der einzelnen Kontakte sind die Änderungseinheiten, die nachverfolgt werden. Kontakte werden durch eine Kombination der Felder name und phone number im Kontaktspeicher eindeutig identifiziert. Wenn ein Kontakt lokal auf zwei verschiedenen Replikaten mit den gleichen Werten für name und phone number erstellt wird, tritt bei der Synchronisierung der Replikate ein Kollisionseinschränkungskonflikt auf.

Aktivieren des NotifyingChangeApplier-Objekts zur Verarbeitung von Einschränkungskonflikten

Alle folgenden Anforderungen müssen erfüllt werden, damit Einschränkungskonflikte vom NotifyingChangeApplier-Objekt erfolgreich behandelt werden können.

Erstellen eines IConflictLogAccess-Objekts

Der Änderungsanwender speichert temporäre Konflikte während der Synchronisierung mithilfe eines Konfliktprotokolls, damit die Konflikte während der Sitzung effizient verarbeitet werden können. Für Replikate, die ansonsten keine Konflikte speichern, stellt Sync Framework die MemoryConflictLog-Klasse bereit. Sie operiert im Arbeitsspeicher und kann zur Speicherung temporärer Konflikte während der Synchronisierungssitzung verwendet werden. In diesem Beispiel wird das Konfliktprotokoll beim Start der Sitzung im Arbeitsspeicher erstellt.

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);
}

Übergeben des IConflictLogAccess-Objekts an das NotifyingChangeApplier-Objekt

Der Zielanbieter muss während Verarbeitung seiner ProcessChangeBatch-Methode die ApplyChanges-Methode aufrufen. Beachten Sie, dass das von ApplyChanges aufgerufene Formular eine Richtlinie zur Kollisionskonfliktauflösung und ein IConflictLogAccess-Objekt akzeptieren muss. In diesem Beispiel wird ApplyChanges aufgerufen. Dabei wird eine Richtlinie zur Kollisionskonfliktauflösung für ApplicationDefined festlegt und das Konfliktprotokoll im Arbeitsspeicher übergeben, das in BeginSession erstellt wurde.

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);
}

Implementieren von SaveConstraintConflict

Der Änderungsanwender ruft die SaveConstraintConflict-Methode der INotifyingChangeApplierTarget2-Schnittstelle zur Speicherung temporärer Konflikte auf. In diesem Beispiel wird die INotifyingChangeApplierTarget2-Schnittstelle zur Klasse hinzugefügt, die KnowledgeSyncProvider implementiert.

class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget
    , INotifyingChangeApplierTarget2
    , IChangeDataRetriever

In diesem Beispiel wird SaveConstraintConflict mit dem MemoryConflictLog-Objekt zum Speichern von temporären Konflikten bzw. zum Auslösen einer Ausnahme implementiert.

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);
    }
}

Implementieren von TryGetDestinationVersion

Der Änderungsanwender muss die Version eines Zielelements abrufen können, bei dem ein Konflikt vorliegt. Dazu implementiert der Zielanbieter die TryGetDestinationVersion-Methode der INotifyingChangeApplierTarget-Schnittstelle. Wenn Einschränkungskonflikte nicht gemeldet werden, ist diese Methode optional. Andernfalls ist sie erforderlich.

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;
}

Melden eines Einschränkungskonflikts

Einschränkungskonflikte werden vom Zielanbieter während der Änderungsanwendungsphase der Synchronisierung erkannt. In diesem Beispiel werden Änderungseinheiten verwendet, damit Einschränkungskonflikte in der SaveChangeWithChangeUnits-Methode durch Aufrufen von RecordConstraintConflictForItem gemeldet werden, wenn eine Änderungsaktion von Create eine Identitätskollision im Kontaktspeicher verursacht.

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;
}

Die im oben erwähnten Beispiel aufgerufene CanCreateContact-Methode des Kontaktspeichers verwendet eine private DetectIndexCollision-Methode, um zu ermitteln, ob der zu erstellende Kontakt eine Identitätskollision verursacht. Eine Identitätskollision tritt auf, wenn der zu erstellende Kontakt die gleichen name- und phone number-Felder wie ein bereits vorhandener Kontakt enthält.

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;
}

Festlegen einer Auflösungsaktion für einen Einschränkungskonflikt

Wenn der Zielanbieter eine Richtlinie zur Kollisionskonfliktauflösung für ApplicationDefined angibt, muss die Anwendung vor dem Start der Synchronisierung einen Handler für das ItemConstraint-Ereignis registrieren.

// 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);

Der ItemConstraint-Ereignishandler bestimmt, wie der Konflikt aufgelöst wird. In diesem Beispiel werden dem Benutzer die Konflikt verursachenden Elemente angezeigt, und es wird gefragt, wie der Konflikt aufgelöst werden soll.

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);
    }
}

Behandeln von Einschränkungskonfliktauflösungen

Nachdem die Aktion zur Einschränkungskonfliktauflösung von der Anwendung festgelegt wurde, nimmt der Änderungsanwender alle notwendigen Änderungen an den der Auflösung zugeordneten Metadaten vor und leitet einen Aufruf an den Zielanbieter weiter, damit dieser die Änderung auf das Zielreplikat anwenden kann. In diesem Beispiel werden Änderungseinheiten verwendet, damit der Änderungsanwender die SaveChangeWithChangeUnits-Methode des Zielanbieters aufruft. In dieser Methode werden die drei möglichen Auflösungen behandelt, die dem Benutzer von der Anwendung angezeigt wurden.

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;
}

Vollständiger Anbietercode

Eine vollständige Auflistung des in diesem Dokument verwendeten Anbietercodes finden Sie unter Beispielcode für das Melden und Auflösen von Einschränkungskonflikten.

Nächste Schritte

Als nächstes sollten Sie ein Konfliktprotokoll implementieren, das Konflikte nach Beendigung der Synchronisierungssitzung beibehalten kann. Weitere Informationen zum Erstellen eines Konfliktprotokolls finden Sie unter Protokollieren und Verwalten von Konflikten.

Siehe auch

Konzepte

Programmieren allgemeiner benutzerdefinierter Standardanbietertasks
Erkennen und Auflösen von Einschränkungskonflikten