Código de exemplo para relatar e resolver conflitos de restrição
O programa de exemplo a seguir implementa um provedor que lida com conflitos de restrição. O provedor detecta conflitos de restrição de colisão quando um item é criado em seu método SaveChangeWithChangeUnits. Quando uma restrição de colisão é detectada, o provedor relata os conflitos ao aplicador de alterações usando RecordConstraintConflictForItem. O provedor também trata da resolução dos conflitos de restrição de colisão em seu método SaveChangeWithChangeUnits.
Este programa de exemplo é descrito detalhadamente em Como relatar e resolver conflitos de restrição.
Exemplo
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;
}