Поделиться через


Как осуществлять сообщение и разрешение конфликтов ограничений

В этом разделе описано, как с помощью управляемого языка предоставить стандартному пользовательскому поставщику возможность сообщать о конфликтах ограничений и устранять их. Конфликты ограничений нарушают ограничения, наложенные на элементы, такие как связь папок или расположение данных с идентичными именами в пределах файловой системы. В Sync Framework предусмотрен класс NotifyingChangeApplier, обеспечивающий поддержку обработки конфликтов ограничений.

Дополнительные сведения о конфликтах ограничений см. в разделе Обнаружение и разрешение конфликтов ограничений.

Материал этого раздела предполагает, что читатель владеет основными понятиями языка C# и платформы Microsoft .NET Framework.

В примерах, приведенных в этом разделе, рассматриваются в основном следующие классы и методы Sync Framework:

Основные сведения о конфликтах ограничений

Конфликты ограничений обнаруживаются поставщиком назначения во время синхронизации на этапе применения изменений, как правило, в методе SaveItemChange или SaveChangeWithChangeUnits. При обнаружении поставщиком назначения конфликта ограничений он сообщает о нем объекту NotifyingChangeApplier с помощью метода RecordConstraintConflictForChangeUnit или RecordConstraintConflictForItem. Объект применения изменений разрешает конфликт в соответствии с политикой устранения конфликтов совпадения, заданной для сеанса, либо с действием по разрешению конфликта, заданным приложением для указанного конфликта. Затем объект применения изменений направляет необходимые вызовы поставщику назначения, например 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

Поставщик назначения должен вызвать метод ApplyChanges во время выполнения своего метода ProcessChangeBatch. Следует учитывать, что вызванная форма метода 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

Объект применения изменений вызывает метод SaveConstraintConflict интерфейса INotifyingChangeApplierTarget2 для сохранения временных конфликтов. В этом примере интерфейс 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

Объект применения изменений должен быть способен возвращать версию элемента назначения, участвующего в конфликте. Для ее получения поставщик назначения реализует метод TryGetDestinationVersion интерфейса INotifyingChangeApplierTarget. Этот метод необязателен, если отсутствует сообщение о конфликте ограничений. В противном случае он является обязательным.

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

Выдача сообщения о конфликте ограничения

Конфликты ограничений обнаруживаются поставщиком назначения в процессе синхронизации на этапе применения изменений. В этом примере используются базовые единицы, поэтому сообщения о конфликтах ограничений формируются в методе SaveChangeWithChangeUnits путем вызова метода RecordConstraintConflictForItem, когда действие изменения Create привело к совпадению идентификаторов в хранилище контактов.

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

Полный код поставщика

Полный код поставщика, используемого в этом документе, см. в разделе Пример кода сообщения и разрешения конфликтов ограничений.

Следующие шаги

Затем может потребоваться реализация журнала конфликтов, позволяющего сохранять данные о конфликтах после завершения сеанса синхронизации. Дополнительные сведения о создании журнала конфликтов см. в разделе Ведение журнала конфликтов и управление конфликтами.

См. также

Основные положения

Программирование типовых задач стандартных пользовательских поставщиков
Обнаружение и разрешение конфликтов ограничений