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


Пример кода сообщения и разрешения конфликтов ограничений

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

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

Пример

class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
    , INotifyingChangeApplierTarget
    , INotifyingChangeApplierTarget2
    , IChangeDataRetriever
{
    public ContactsProviderWithConstraintConflicts(ContactStore store)
    {
        _ContactStore = store;
    }

    // Stores the session context and creates an in-memory conflict log.
    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);
    }

    // Releases the session context and conflict log.
    public override void EndSession(SyncSessionContext syncSessionContext)
    {
        _sessionContext = null;
        _memConflictLog = null;
    }

    // Uses the metadata storage service implementation of GetChangeBatch to retrieve a change batch.
    public override ChangeBatch GetChangeBatch(uint batchSize, SyncKnowledge destinationKnowledge, out object changeDataRetriever)
    {
        // Return this object as the IChangeDataRetriever object that is called to retrieve item data.
        changeDataRetriever = this;

        // Use metadata storage service to get a batch of changes.
        return _ContactStore.ContactReplicaMetadata.GetChangeBatch(batchSize, destinationKnowledge);
    }

    public override FullEnumerationChangeBatch GetFullEnumerationChangeBatch(uint batchSize, SyncId lowerEnumerationBound, SyncKnowledge knowledgeForDataRetrieval, out object changeDataRetriever)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    // Gets the parameters for synchronization.
    public override void GetSyncBatchParameters(out uint batchSize, out SyncKnowledge knowledge)
    {
        // Set a batch size of 10.
        batchSize = 10;

        // Return the current knowledge of the replica that is stored in the metadata store.
        knowledge = _ContactStore.ContactReplicaMetadata.GetKnowledge();
    }

    // Gets the ID format schema that is defined for this replica.
    public override SyncIdFormatGroup IdFormats
    {
        get 
        {
            SyncIdFormatGroup FormatGroup = new SyncIdFormatGroup();

            // Change unit IDs are an enumeration, so they are fixed length and contain one byte.
            FormatGroup.ChangeUnitIdFormat.IsVariableLength = false;
            FormatGroup.ChangeUnitIdFormat.Length = sizeof(byte);

            // Item IDs are of SyncGlobalId type, so they are fixed length and contain a ulong prefix plus a Guid.
            FormatGroup.ItemIdFormat.IsVariableLength = false;
            FormatGroup.ItemIdFormat.Length = (ushort)(sizeof(ulong) + Marshal.SizeOf(typeof(Guid)));

            // Replica IDs are the absolute path to the item store, so they are variable length with maximum
            // length equal to the maximum length of a path.
            FormatGroup.ReplicaIdFormat.IsVariableLength = true;
            FormatGroup.ReplicaIdFormat.Length = 260 * sizeof(char);

            return FormatGroup;
        }
    }

    // Uses the metadata storage service to get the local versions of changes received from the source provider.
    // Uses a NotifyingChangeApplier object to process the changes.
    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);
    }

    public override void ProcessFullEnumerationChangeBatch(ConflictResolutionPolicy resolutionPolicy, FullEnumerationChangeBatch sourceChanges, object changeDataRetriever, SyncCallbacks syncCallbacks, SyncSessionStatistics sessionStatistics)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    #region IChangeDataRetriever Members

    // Returns data for the specified change. Changes are represented by a sparsely-populated array of strings. The
    // array is indexed by change unit ID.
    public object LoadChangeData(LoadChangeContext loadChangeContext)
    {
        // Sanity check to ensure the data array is not overrun.
        if (Contact.ChangeUnitFieldCount < loadChangeContext.ItemChange.ChangeUnitChanges.Count)
        {
            throw new ArgumentOutOfRangeException("LoadChangeData received too many change unit changes.");
        }

        // Get the ID of the item to return.
        SyncId itemId = loadChangeContext.ItemChange.ItemId;

        string[] contactData;
        if (loadChangeContext.ItemChange.ChangeUnitChanges.Count == 0)
        {
            // Change unit count is 0, so return the entire item.
            // Create a string array that contains all of the change units.
            contactData = _ContactStore.ContactList[itemId].ToParts();
        }
        else
        {
            // Create a string array to hold the data for each change unit. Some of the elements of this array
            // may be empty.
            contactData = new string[Contact.ChangeUnitFieldCount];

            // Enumerate the change units to retrieve.
            for (int iChange = 0; iChange < loadChangeContext.ItemChange.ChangeUnitChanges.Count; iChange++)
            {
                // Retrieve data for the specified change unit and put the data into the appropriate
                // place in the string array.
                int icu = loadChangeContext.ItemChange.ChangeUnitChanges[iChange].ChangeUnitId.GetByteId();
                contactData[icu] = _ContactStore.GetContactData(itemId,
                    loadChangeContext.ItemChange.ChangeUnitChanges[iChange].ChangeUnitId);
            }
        }

        return contactData;
    }

    #endregion

    #region INotifyingChangeApplierTarget Members

    // Return this object as the IChangeDataRetriever implementation.
    public IChangeDataRetriever GetDataRetriever()
    {
        return this;
    }

    // Use the metadata storage service to get the next tick count.
    public ulong GetNextTickCount()
    {
        return _ContactStore.ContactReplicaMetadata.GetNextTickCount();
    }

    // Saves the specfied change to the item store and metadata store.
    public void SaveChangeWithChangeUnits(ItemChange change, SaveChangeWithChangeUnitsContext context)
    {
        // Actions are stored per change unit, but some actions affect the entire item, whereas some
        // actions affect only individual change units. These two types of actions are handled differently in
        // the following code.

        SaveChangeAction action = context.GetActionForChangeUnit(change.ChangeUnitChanges[0]);

        // Handle changes that affect the entire item.
        if (SaveChangeAction.Create == action ||
            SaveChangeAction.DeleteConflictingAndSaveSourceItem == action ||
            SaveChangeAction.RenameDestinationAndUpdateVersionData == action ||
            SaveChangeAction.RenameSourceAndUpdateVersionAndData == action)
        {
            // Verify that all change unit changes are the same action.
            foreach (ChangeUnitChange cuChange in change.ChangeUnitChanges)
            {
                if (context.GetActionForChangeUnit(cuChange) != action)
                {
                    throw new SyncInvalidOperationException("More than one kind of item-wide action received in SaveChangeWithChangeUnits.");
                }
            }

            switch (action)
            {
                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;
                }
                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;
                }
            }
        }
        else
        {
            // Enumerate the change units for changes that affect individual change units and apply 
            // them by using the specified action.
            foreach (ChangeUnitChange cuChange in change.ChangeUnitChanges)
            {
                switch (context.GetActionForChangeUnit(cuChange))
                {
                    case SaveChangeAction.Create:
                    case SaveChangeAction.DeleteConflictingAndSaveSourceItem:
                    case SaveChangeAction.RenameDestinationAndUpdateVersionData:
                    case SaveChangeAction.RenameSourceAndUpdateVersionAndData:
                    {
                        throw new SyncInvalidOperationException("Mix of item-wide changes and change unit changes received in SaveChangeWithChangeUnits.");
                    }
                    case SaveChangeAction.UpdateVersionAndData:
                    {
                        // Update the item store and metadata store for the specified change unit.
                        try
                        {
                            string cuData = ((string[])context.ChangeData)[cuChange.ChangeUnitId.GetByteId()];

                            if (_ContactStore.CanUpdateContact(change.ItemId, cuChange.ChangeUnitId, cuData))
                            {
                                _ContactStore.UpdateContactFromSync(change, cuChange, cuData);
                            }
                            else
                            {
                                context.RecordConstraintConflictForChangeUnit(cuChange);
                            }

                        }
                        catch (Exception ex)
                        {
                            RecoverableErrorData errData = new RecoverableErrorData(ex);
                            context.RecordRecoverableErrorForChangeUnit(cuChange, errData);
                        }
                        break;
                    }
                    case SaveChangeAction.UpdateVersionAndMergeData:
                    {
                        // Merge actions are not supported by this implementation.
                        throw new NotImplementedException("UpdateVersionAndMergeData is not supported.");
                    }
                    case SaveChangeAction.UpdateVersionOnly:
                    {
                        // Update only the version of this change unit in the metadata store.
                        try
                        {
                            _ContactStore.UpdateContactVersion(change.ItemId, cuChange.ChangeUnitId, cuChange.ChangeUnitVersion);
                        }
                        catch (Exception ex)
                        {
                            RecoverableErrorData errData = new RecoverableErrorData(ex);
                            context.RecordRecoverableErrorForChangeUnit(cuChange, errData);
                        }
                        break;
                    }
                    case SaveChangeAction.DeleteAndRemoveTombstone:
                    case SaveChangeAction.DeleteAndStoreTombstone:
                    {
                        // Delete actions are handled in SaveItemChange, so throw an exception.
                        throw new InvalidOperationException("SaveChangeWithChangeUnits received a delete action.");
                    }
                    default:
                    {
                        throw new ArgumentOutOfRangeException("SaveChangeWithChangeUnits received an out-of-range action.");
                    }
                }
            }
        }

        // Use the metadata storage service to save the knowledge as each change is applied. Saving knowledge as each change is applied is 
        // not required. It is more robust than saving the knowledge only after each change batch, because if synchronization is interrupted 
        // before the end of a change batch, the knowledge will still reflect all of the changes applied. However, it is less efficient because 
        // knowledge must be stored more frequently.
        SyncKnowledge updatedKnowledge;
        ForgottenKnowledge updatedForgottenKnowledge;
        context.GetUpdatedDestinationKnowledge(out updatedKnowledge, out updatedForgottenKnowledge);
        _ContactStore.ContactReplicaMetadata.SetKnowledge(updatedKnowledge);
    }

    // This implementation does not support saving concurrency conflicts.
    public void SaveConflict(ItemChange conflictingChange, object conflictingChangeData, SyncKnowledge conflictingChangeKnowledge)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    // Saves the specfied change to the item store and metadata store.
    public void SaveItemChange(SaveChangeAction saveChangeAction, ItemChange change, SaveChangeContext context)
    {
        // This provider uses change units, so SaveChangeWithChangeUnits is called for all actions except delete actions.
        if (SaveChangeAction.DeleteAndStoreTombstone == saveChangeAction)
        {
            // Delete the item from the item store and store a tombstone for it in the metadata store.
            try
            {
                _ContactStore.DeleteContactFromSync(change.ItemId, change.ChangeVersion);
            }
            catch (Exception ex)
            {
                RecoverableErrorData errData = new RecoverableErrorData(ex);
                context.RecordRecoverableErrorForItem(errData);
            }
        }
        else 
        {
            // SaveChangeWithChangeUnits should be called for all actions other than delete actions.
            throw new NotImplementedException("SaveItemChange only handles deletes!");
        }

        // Use the metadata storage service to save the knowledge as each change is applied. Saving knowledge as each change is applied is 
        // not required. It is more robust than saving the knowledge only after each change batch, because if synchronization is interrupted 
        // before the end of a change batch, the knowledge will still reflect all of the changes applied. However, it is less efficient because 
        // knowledge must be stored more frequently.
        SyncKnowledge updatedKnowledge;
        ForgottenKnowledge updatedForgottenKnowledge;
        context.GetUpdatedDestinationKnowledge(out updatedKnowledge, out updatedForgottenKnowledge);
        _ContactStore.ContactReplicaMetadata.SetKnowledge(updatedKnowledge);
    }

    // Use the metadata storage service to save the knowledge after the change batch is applied.
    // Also, commit changes for the batch to the files on disk.
    public void StoreKnowledgeForScope(SyncKnowledge knowledge, ForgottenKnowledge forgottenKnowledge)
    {
        // Use the metadata storage service to save the knowledge and forgotten knowledge.
        _ContactStore.ContactReplicaMetadata.SetKnowledge(knowledge);
        _ContactStore.ContactReplicaMetadata.SetForgottenKnowledge(forgottenKnowledge);

        // Commit changes made to the in-memory item store to the file on disk.
        _ContactStore.SaveContactChanges();

         // Commit changes made to the in-memory metadata store to the file on disk.
        _ContactStore.SaveMetadataChanges();
    }

    // When constraint conflicts are reported, this method must be implemented or synchronization
    // will fail.
    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;
    }

    #endregion

    #region INotifyingChangeApplierTarget2 Members

    // Save temporary constraint conflicts in the in-memory conflict log.
    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);
        }
    }

    #endregion

    protected ContactStore _ContactStore;
    private SyncSessionContext _sessionContext;
    private MemoryConflictLog _memConflictLog;
}

См. также

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

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