Notificar y resolver conflictos de restricción
En este tema se describe cómo utilizar un lenguaje administrado para que un proveedor personalizado estándar pueda notificar y resolver los conflictos de restricción. Los conflictos de restricción son los que infringen las restricciones aplicadas a los elementos, como la relación de las carpetas o la ubicación de los datos con un nombre idéntico dentro de un sistema de archivos. Sync Framework ofrece la clase NotifyingChangeApplier para ayudar a procesar los conflictos de restricción.
Para obtener más información sobre conflictos de restricción, vea Detectar y resolver conflictos de restricción.
En este tema se presupone que se dispone de un conocimiento básico de C# y conceptos de Microsoft .NET Framework.
Los ejemplos de este tema se centran en las siguientes clases y métodos de Sync Framework:
Clase NotifyingChangeApplier.
El evento ItemConstraint de la clase SyncCallbacks.
El método SaveChangeWithChangeUnits de la interfaz INotifyingChangeApplierTarget.
El método RecordConstraintConflictForItem de la clase SaveChangeWithChangeUnitsContext.
Entender los conflictos de restricción
El proveedor de destino detecta los conflictos de restricción durante la fase de aplicación de cambios de sincronización, normalmente en el método SaveChangeWithChangeUnits o SaveItemChange. Cuando el proveedor de destino detecta un conflicto de restricción, notifica el conflicto al objeto NotifyingChangeApplier mediante el método RecordConstraintConflictForItem o RecordConstraintConflictForChangeUnit. El aplicador de cambios resuelve el conflicto según la directiva de resolución de conflictos de colisión establecida para la sesión o bien en función de la acción para la resolución de conflictos que haya establecido la aplicación para el conflicto correspondiente. A continuación, el aplicador de cambios envía todas las llamadas necesarias al proveedor de destino, como por ejemplo, SaveItemChange o SaveChangeWithChangeUnits, para que el proveedor de destino pueda aplicar el conflicto resuelto a la réplica de destino. Las resoluciones típicas de conflictos de restricción incluyen cambiar el nombre del elemento de origen o el elemento de destino de forma que el conflicto de restricción no se vuelva a producir; o bien, combinar los contenidos de los dos elementos en un solo elemento.
Requisitos de la compilación
.NET Framework 2.0 o posterior.
Remítase a Microsoft.Synchronization.
Remítase a Microsoft.Synchronization.MetadataStorage.
Ejemplo
El código de ejemplo de este tema muestra cómo habilitar el aplicador de cambios para que administre los conflictos de restricción, cómo detectar y notificar un conflicto de restricción durante la fase de aplicación de cambios de la sincronización, cómo establecer la acción de resolución mediante la administración del evento ItemConstraint en la aplicación de sincronización y cómo resolver el conflicto a través de la aplicación de la resolución a la réplica de destino. La réplica de este ejemplo almacena los contactos en un archivo de texto como una lista de valores separados por comas. Los elementos a sincronizar son los contactos que incluye este archivo y las unidades de cambio de las que se realiza el seguimiento son los campos dentro de cada contacto. La identificación de los contactos en el almacén de contactos es única, mediante una combinación de los campos name y phone number. Cuando se crea localmente un contacto con el mismo name y phone number en dos réplicas diferentes, cuando se sincronizan posteriormente las réplicas se produce un conflicto de restricción de colisión.
Habilitar el objeto NotifyingChangeApplier para procesar conflictos de restricción
Se debe satisfacer la totalidad de los requisitos siguientes para que el objeto NotifyingChangeApplier pueda administrar correctamente los conflictos de restricción.
Crear un objeto IConflictLogAccess
El aplicador de cambios se sirve de un registro de conflictos para guardar los conflictos temporales que se producen durante la sincronización para que los conflictos se puedan procesar eficazmente durante la sesión. Para las réplicas que no almacenan conflictos de otra forma, Sync Framework ofrece la clase MemoryConflictLog, que funciona en memoria y se puede utilizar para almacenar los conflictos temporales durante la sesión de sincronización. Este ejemplo crea el registro del conflicto en memoria cuando la sesión comienza.
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);
}
Pasar el objeto IConflictLogAccess al objeto NotifyingChangeApplier
El proveedor de destino debe llamar al método ApplyChanges durante el procesamiento de su método ProcessChangeBatch. Tenga en cuenta que el formulario de ApplyChanges que se llama debe aceptar una directiva de resolución de conflictos de colisión y un objeto IConflictLogAccess. Este ejemplo llama a ApplyChanges, especifica una directiva de resolución de conflictos de colisión de ApplicationDefined y pasa el registro del conflicto en memoria que se creó en 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);
}
Implementar SaveConstraintConflict
El aplicador de cambios llama al método SaveConstraintConflict de la interfaz INotifyingChangeApplierTarget2 para guardar los conflictos temporales. En este ejemplo agrega la interfaz INotifyingChangeApplierTarget2 a la clase que implementa KnowledgeSyncProvider.
class ContactsProviderWithConstraintConflicts : KnowledgeSyncProvider
, INotifyingChangeApplierTarget
, INotifyingChangeApplierTarget2
, IChangeDataRetriever
En este ejemplo se implementa SaveConstraintConflict mediante el uso del objeto MemoryConflictLog para guardar los conflictos temporales, produciendo una excepción en caso contrario.
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);
}
}
Implementar TryGetDestinationVersion
El aplicador de cambios debe poder obtener la versión de un elemento de destino que esté en conflicto. Para proporcionar esto, el proveedor de destino implementa el método TryGetDestinationVersion de la interfaz INotifyingChangeApplierTarget. Este método es opcional cuando no se notifican conflictos de restricción; en caso contrario, es obligatorio.
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;
}
Notificar un conflicto de restricción
El proveedor de destino detecta los conflictos de restricción durante la fase de aplicación de cambios de la sincronización. Este ejemplo utiliza unidades de cambio, de forma que los conflictos de restricción se notifican en el método SaveChangeWithChangeUnits llamando a RecordConstraintConflictForItem cuando una acción de cambios de Create produce una colisión de identidad en el almacén de contactos.
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;
}
El método CanCreateContact
del almacén de contactos al que se ha llamado en el ejemplo anterior utiliza un método DetectIndexCollision
privado para detectar si hay una colisión de identidad provocada por el contacto a crear. Se produce una colisión de identidad cuando el contacto que se va a crear contiene los campos name y phone number iguales a los de un contacto ya existente.
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;
}
Establecer la acción de resolución para un conflicto de restricción
Cuando el proveedor de destino especifica una directiva de resolución de conflictos de colisión de ApplicationDefined, la aplicación debe registrar un controlador para el evento ItemConstraint antes de iniciar la sincronización.
// 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);
El controlador de eventos ItemConstraint determina cómo se resuelve el conflicto. Este ejemplo muestra los elementos en conflicto para el usuario y pregunta cómo se debería resolver el conflicto.
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);
}
}
Administrar las resoluciones de conflictos de restricción
Una vez haya establecido la aplicación la acción de resolución del conflicto de restricción, el aplicador de cambios efectúa los cambios necesarios en los metadatos asociados a la resolución y envía una llamada al proveedor de destino para que pueda aplicar el cambio a la réplica de destino. Este ejemplo utiliza unidades de cambio, de forma que el aplicador de cambios llama al método SaveChangeWithChangeUnits del proveedor de destino. Las tres resoluciones posibles que presentó la aplicación al usuario se administran en este método.
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;
}
Completar el código del proveedor
Para obtener una lista completa del código del proveedor que se ha utilizado en este documento, vea Código de ejemplo para notificar y resolver conflictos de restricción.
Pasos siguientes
A continuación, es posible que desee implementar un registro de conflictos que pueda continuar con los conflictos tras finalizar la sesión de sincronización. Para obtener más información acerca de cómo crear un registro de conflictos, vea Registrar y administrar conflictos.
Vea también
Conceptos
Programar tareas comunes de un proveedor personalizado estándar
Detectar y resolver conflictos de restricción