Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
In questo argomento viene illustrato come utilizzare un linguaggio gestito per consentire a un provider personalizzato standard di segnalare e risolvere conflitti di vincoli. I conflitti di vincoli violano i vincoli imposti sugli elementi, ad esempio la relazione delle cartelle o il percorso di dati denominati in modo identico all'interno di un file system. In Sync Framework è disponibile la classe NotifyingChangeApplier per l'elaborazione dei conflitti di vincoli.
Per ulteriori informazioni sui conflitti di vincoli, vedere Rilevamento e risoluzione dei conflitti di vincoli.
Questo argomento presuppone una conoscenza di base dei concetti relativi a C# e Microsoft .NET Framework.
Gli esempi riportati in questo argomento riguardano i metodi e le classi di Sync Framework seguenti:
La classe NotifyingChangeApplier.
L'evento ItemConstraint della classe SyncCallbacks.
Il metodo SaveChangeWithChangeUnits dell'interfaccia INotifyingChangeApplierTarget.
Il metodo RecordConstraintConflictForItem della classe SaveChangeWithChangeUnitsContext.
Informazioni sui conflitti di vincoli
I conflitti di vincoli vengono rilevati dal provider di destinazione durante la fase di applicazione modifiche della sincronizzazione, in genere nel metodo SaveItemChange o SaveChangeWithChangeUnits. Quando il provider di destinazione rileva un conflitto di vincoli, segnala il conflitto all'oggetto NotifyingChangeApplier tramite il metodo RecordConstraintConflictForChangeUnit o RecordConstraintConflictForItem. L'oggetto di applicazione modifiche risolve il conflitto in base ai criteri di risoluzione dei conflitti di collisione impostati per la sessione o in base all'azione di risoluzione dei conflitti impostata dall'applicazione per il conflitto specificato. L'oggetto di applicazione modifiche invia tutte le chiamate necessarie al provider di destinazione, ad esempio SaveItemChange o SaveChangeWithChangeUnits, affinché quest'ultimo possa applicare il conflitto risolto alla replica di destinazione. Risoluzioni tipiche di conflitti di vincoli includono la ridenominazione dell'elemento di origine o dell'elemento di destinazione in modo che il conflitto di vincoli non si verifichi più oppure l'unione del contenuto dei due elementi in uno unico.
Requisiti di compilazione
.NET Framework 2.0 o versioni successive.
Riferimento a Microsoft.Synchronization.
Riferimento a Microsoft.Synchronization.MetadataStorage.
Esempio
Nel codice di esempio riportato in questo argomento viene illustrato come consentire all'oggetto di applicazione modifiche di gestire i conflitti di vincoli, come rilevare e segnalare un conflitto di vincoli durante la fase di applicazione modifiche della sincronizzazione, come impostare l'azione di risoluzione gestendo l'evento ItemConstraint nell'applicazione di sincronizzazione e come risolvere il conflitto applicando la risoluzione alla replica di destinazione. La replica di questo esempio archivia i contatti in un file di testo come un elenco di valori separati da virgola. Gli elementi da sincronizzare sono i contatti contenuti in questo file e le unità di modifica rilevate sono i campi all'interno di ciascun contatto. I contatti sono identificati in modo univoco nell'archivio contatti da una combinazione dei campi name e phone number. Quando un contatto con name e phone number uguali viene creato in locale in due repliche diverse, quando le repliche vengono sincronizzate si verifica un conflitto di vincoli di collisione.
Consentire all'oggetto NotifyingChangeApplier di elaborare i conflitti di vincoli
I requisiti seguenti devono essere tutti soddisfatti per consentire all'oggetto NotifyingChangeApplier di gestire correttamente i conflitti di vincoli.
Creare un oggetto IConflictLogAccess
L'oggetto di applicazione modifiche utilizza un log dei conflitti per salvare i conflitti temporanei durante la sincronizzazione in modo che i conflitti possano essere elaborati in modo efficiente durante la sessione. Per le repliche che non archiviano i conflitti, Sync Framework fornisce la classe MemoryConflictLog che opera in memoria e può essere utilizzata per archiviare i conflitti temporanei durante la sessione di sincronizzazione. In questo esempio viene creato il log dei conflitti in memoria all'avvio della sessione.
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);
}
Passare l'oggetto IConflictLogAccess all'oggetto NotifyingChangeApplier
Il provider di destinazione deve chiamare il metodo ApplyChanges durante l'elaborazione del relativo metodo ProcessChangeBatch. Si consideri che il form di ApplyChanges chiamato deve accettare i criteri di risoluzione dei conflitti di collisione e un oggetto IConflictLogAccess. In questo esempio viene chiamato ApplyChanges, specificando i criteri di risoluzione dei conflitti di collisione di ApplicationDefined e passando il log dei conflitti in memoria creato in 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);
}
Implement SaveConstraintConflict
L'oggetto di applicazione modifiche chiama il metodo SaveConstraintConflict dell'interfaccia INotifyingChangeApplierTarget2 per salvare i conflitti temporanei. In questo esempio viene aggiunta l'interfaccia INotifyingChangeApplierTarget2 alla classe che implementa KnowledgeSyncProvider.
class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
, INotifyingChangeApplierTarget
, INotifyingChangeApplierTarget2
, IChangeDataRetriever
In questo esempio viene implementato SaveConstraintConflict tramite l'oggetto MemoryConflictLog per salvare i conflitti temporanei e in caso contrario generando un'eccezione.
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);
}
}
Implementare TryGetDestinationVersion
L'oggetto di applicazione modifiche devono essere in grado di ottenere la versione di un elemento di destinazione in conflitto. A tale scopo, il provider di destinazione implementa il metodo TryGetDestinationVersion dell'interfaccia INotifyingChangeApplierTarget. Questo metodo è facoltativo se non vengono segnalati conflitti di vincoli. In caso contrario è obbligatorio.
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;
}
Segnalazione di un conflitto di vincoli
I conflitti di vincoli vengono rilevati dal provider di destinazione durante la fase di applicazione modifiche della sincronizzazione. In questo esempio vengono utilizzate unità di modifica, pertanto i conflitti di vincoli vengono segnalati nel metodo SaveChangeWithChangeUnits chiamando RecordConstraintConflictForItem quando un'azione di modifica di Create causa un conflitto di identità nell'archivio dei contatti.
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;
}
Il metodo CanCreateContact dell'archivio dei contatti chiamato nell'esempio precedente utilizza un metodo DetectIndexCollision privato per rilevare se si è verificato un conflitto di identità causato dal contatto da creare. Si verifica un conflitto di identità quando il contatto da creare contiene gli stessi campi name e phone number di un contatto esistente.
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;
}
Impostazione di un'azione di risoluzione per un conflitto di vincoli
Quando il provider di destinazione specifica criteri di risoluzione dei conflitti di collisione di ApplicationDefined, l'applicazione deve registrare un gestore per l'evento ItemConstraint prima che venga avviata la sincronizzazione.
// 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);
Il gestore dell'evento ItemConstraint determina la modalità di risoluzione del conflitto. In questo esempio vengono visualizzati gli elementi in conflitto e viene richiesto come risolvere il conflitto.
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);
}
}
Gestione delle risoluzioni dei conflitti di vincoli
Dopo che l'azione di risoluzione dei conflitti di vincoli è stata impostata dall'applicazione, l'oggetto di applicazione modifiche apporta le modifiche necessarie ai metadati associati alla risoluzione e invia una chiamata al provider di destinazione affinché possa applicare la modifica alla replica di destinazione. In questo esempio vengono utilizzate unità di modifiche, pertanto l'oggetto di applicazione modifiche chiama il metodo SaveChangeWithChangeUnits del provider di destinazione. Le tre possibili risoluzioni presentate all'utente dall'applicazione vengono gestite in questo metodo.
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;
}
Codice del provider completo
Per un elenco completo del codice del provider utilizzato in questo documento, vedere Codice di esempio per la segnalazione e la risoluzione di conflitti di vincoli.
Passaggi successivi
A questo punto, è necessario implementare un log dei conflitti in cui conservare i conflitti oltre la fine della sessione di sincronizzazione. Per ulteriori informazioni sulla creazione di un log dei conflitti, vedere Registrazione e gestione di conflitti.
Vedere anche
Concetti
Programmazione di attività comuni del provider standard personalizzato
Rilevamento e risoluzione dei conflitti di vincoli