Exemple de synchronisation des groupes SharePoint de termes
L’exemple Core.MMSSync vous montre comment utiliser un add-in hébergé par un fournisseur pour synchroniser une taxonomie source et cible. Ce add-in synchronise deux magasins de termes dans le service de métadonnées gérées — une source et un magasin de termes cible.
Les objets suivants sont utilisés pour synchroniser des groupes de termes :
- TermStore
- ChangeInformation
Utilisez cette solution si vous souhaitez :
- Synchronisez deux taxonomies. Par exemple, vous pouvez utiliser SharePoint Online et SharePoint Server local pour différents ensembles de données, mais ils utilisent la même taxonomie.
- Synchroniser les modifications apportées à un groupe de termes spécifique uniquement.
Avant de commencer
Pour commencer, téléchargez l’exemple de add-in Core.MMSSync à partir du projet Pratiques et modèles de développement Office 365 sur GitHub.
Notes
Le code dans cet article est fourni tel quel, sans garantie d’aucune sorte, expresse ou implicite, y compris mais sans s’y limiter, aucune garantie implicite d’adéquation à un usage particulier, à une qualité marchande ou une absence de contrefaçon.
Avant d’exécuter ce module, vous devez être autorisé à accéder au magasin de termes dans le service de métadonnées gérées. La figure suivante illustre le centre d’administration Office 365 où ces autorisations sont assignées.

Pour attribuer des autorisations au magasin de termes :
Dans le Centre d’administration Office 365, choisissez Magasin de termes.
Dans MAGASIN DE TERMES DE TAXONOMIE, sélectionnez le jeu de termes auquel vous voulez attribuer un administrateur.
Dans Administrateurs de magasin de termes, entrez le compte d’organisation nécessitant des autorisations d’administrateur de magasin de termes.
Utilisation de l’exemple de add-in Core.MMSSync
Lorsque vous démarrez le module, une application console Core.MMSSync s’affiche, comme illustré dans la figure suivante. Vous êtes invité à entrer les informations suivantes :
- URL du centre d Office 365 qui contient le magasin de termes source (il s’agit de l’URL du service de métadonnées gérées source). Par exemple, vous pouvez entrer
https://contososource-admin.sharepoint.com. - Nom d’utilisateur et mot de passe d’un administrateur de magasin de termes sur votre service de métadonnées gérées source.
- URL du centre d Office 365 qui contient le magasin de termes cible (il s’agit de l’URL du MMS cible). Par exemple, vous pouvez entrer
https://contosotarget-admin.sharepoint.com. - Nom d’utilisateur et mot de passe d’un administrateur de magasin de termes sur votre service de métadonnées gérées cible.
- Type d’opération que vous souhaitez effectuer. Vous pouvez :
- Déplacer un groupe de termes (scénario 1) à l’aide de l’objet TermStore.
- Modifications de processus (scénario 2) à l’aide de l’objet ChangeInformation.
Important
Cet exemple de add-in fonctionne avec SharePoint Online et SharePoint Server local.

Après avoir sélectionné votre scénario, entrez le nom du groupe de termes que vous souhaitez synchroniser à partir de votre source avec votre service de métadonnées gérées cible, comme illustré dans la figure suivante. Par exemple, vous pouvez entrer Enterprise .

Scénario 1 : déplacer un groupe de termes
Lorsque vous sélectionnez Move Term Group, le add-in vous invite à entrer un groupe de termes à synchroniser, puis appelle la méthode CopyNewTermGroups dans MMSSyncManager.cs. CopyNewTermGroups fait ensuite ce qui suit pour copier un groupe de termes du magasin de termes source vers le magasin de termes cible :
Extrait les objets du magasin de termes source et cible.
Vérifie que les langues des magasins de termes source et cible correspondent.
Vérifie que le groupe de termes source n’existe pas dans le magasin de termes cible, puis copie le groupe de termes source dans le magasin de termes cible à l’aide de CreateNewTargetTermGroup.
Vous pouvez définir les paramètres TermGroupExclusions, TermGroupToCopy et TermSetInclusions pour filtrer les termes qui sont traitées.
Le code suivant montre les méthodes CopyNewTermGroups et CreateNewTargetTermGroup dans MMSSyncManager.cs.
public bool CopyNewTermGroups(ClientContext sourceContext, ClientContext targetContext, List<string> termGroupExclusions = null, string termGroupToCopy = null)
{
TermStore sourceTermStore = GetTermStoreObject(sourceContext);
TermStore targetTermStore = GetTermStoreObject(targetContext);
List<int> languagesToProcess = null;
if (!ValidTermStoreLanguages(sourceTermStore, targetTermStore, out languagesToProcess))
{
Log.Internal.TraceError((int)EventId.LanguageMismatch, "The target termstore default language is not available as language in the source term store, syncing cannot proceed.");
return false;
}
// Get a list of term groups to process. Exclude site collection-scoped groups and system groups.
IEnumerable<TermGroup> termGroups = sourceContext.LoadQuery(sourceTermStore.Groups.Include(g => g.Name,
g => g.Id,
g => g.IsSiteCollectionGroup,
g => g.IsSystemGroup))
.Where(g => g.IsSystemGroup == false && g.IsSiteCollectionGroup == false);
sourceContext.ExecuteQuery();
foreach (TermGroup termGroup in termGroups)
{
// Skip term group if you only want to copy one particular term group.
if (!String.IsNullOrEmpty(termGroupToCopy))
{
if (!termGroup.Name.Equals(termGroupToCopy, StringComparison.InvariantCultureIgnoreCase))
{
continue;
}
}
// Skip term groups that you do not want to copy.
if (termGroupExclusions != null && termGroupExclusions.Contains(termGroup.Name, StringComparer.InvariantCultureIgnoreCase))
{
Log.Internal.TraceInformation((int)EventId.CopyTermGroup_Skip, "Skipping {0} as this is a system termgroup", termGroup.Name);
continue;
}
// About to start copying a term group.
TermGroup sourceTermGroup = GetTermGroup(sourceContext, sourceTermStore, termGroup.Name);
TermGroup targetTermGroup = GetTermGroup(targetContext, targetTermStore, termGroup.Name);
if (sourceTermGroup == null)
{
continue;
}
if (targetTermGroup != null)
{
if (sourceTermGroup.Id != targetTermGroup.Id)
{
// Term group exists with a different ID, unable to sync.
Log.Internal.TraceWarning((int)EventId.CopyTermGroup_IDMismatch, "The term groups have different ID's. I don't know how to work it.");
}
else
{
// Do nothing as this term group was previously copied. Term group changes need to be
// picked up by the change log processing.
Log.Internal.TraceInformation((int)EventId.CopyTermGroup_AlreadyCopied, "Termgroup {0} was already copied...changes to it will need to come from changelog processing.", termGroup.Name);
}
}
else
{
Log.Internal.TraceInformation((int)EventId.CopyTermGroup_Copying, "Copying termgroup {0}...", termGroup.Name);
this.CreateNewTargetTermGroup(sourceContext, targetContext, sourceTermGroup, targetTermStore, languagesToProcess);
}
}
return true;
}
private void CreateNewTargetTermGroup(ClientContext sourceClientContext, ClientContext targetClientContext, TermGroup sourceTermGroup, TermStore targetTermStore, List<int> languagesToProcess)
{
TermGroup destinationTermGroup = targetTermStore.CreateGroup(sourceTermGroup.Name, sourceTermGroup.Id);
if (!string.IsNullOrEmpty(sourceTermGroup.Description))
{
destinationTermGroup.Description = sourceTermGroup.Description;
}
TermSetCollection sourceTermSetCollection = sourceTermGroup.TermSets;
if (sourceTermSetCollection.Count > 0)
{
foreach (TermSet sourceTermSet in sourceTermSetCollection)
{
sourceClientContext.Load(sourceTermSet,
set => set.Name,
set => set.Description,
set => set.Id,
set => set.Contact,
set => set.CustomProperties,
set => set.IsAvailableForTagging,
set => set.IsOpenForTermCreation,
set => set.CustomProperties,
set => set.Terms.Include(
term => term.Name,
term => term.Description,
term => term.Id,
term => term.IsAvailableForTagging,
term => term.LocalCustomProperties,
term => term.CustomProperties,
term => term.IsDeprecated,
term => term.Labels.Include(label => label.Value, label => label.Language, label => label.IsDefaultForLanguage)));
sourceClientContext.ExecuteQuery();
TermSet targetTermSet = destinationTermGroup.CreateTermSet(sourceTermSet.Name, sourceTermSet.Id, targetTermStore.DefaultLanguage);
targetClientContext.Load(targetTermSet, set => set.CustomProperties);
targetClientContext.ExecuteQuery();
UpdateTermSet(sourceClientContext, targetClientContext, sourceTermSet, targetTermSet);
foreach (Term sourceTerm in sourceTermSet.Terms)
{
Term reusedTerm = targetTermStore.GetTerm(sourceTerm.Id);
targetClientContext.Load(reusedTerm);
targetClientContext.ExecuteQuery();
Term targetTerm;
if (reusedTerm.ServerObjectIsNull.Value)
{
try
{
targetTerm = targetTermSet.CreateTerm(sourceTerm.Name, targetTermStore.DefaultLanguage, sourceTerm.Id);
targetClientContext.Load(targetTerm, term => term.IsDeprecated,
term => term.CustomProperties,
term => term.LocalCustomProperties);
targetClientContext.ExecuteQuery();
UpdateTerm(sourceClientContext, targetClientContext, sourceTerm, targetTerm, languagesToProcess);
}
catch (ServerException ex)
{
if (ex.Message.IndexOf("Failed to read from or write to database. Refresh and try again.") > -1)
{
// This exception was due to caching issues and generally is thrown when terms are reused across groups.
targetTerm = targetTermSet.ReuseTerm(reusedTerm, false);
}
else
{
throw ex;
}
}
}
else
{
targetTerm = targetTermSet.ReuseTerm(reusedTerm, false);
}
targetClientContext.Load(targetTerm);
targetClientContext.ExecuteQuery();
targetTermStore.UpdateCache();
// Refresh session and term store references to force reload of the term just added. You need
// to do this because there can be an update change event following next, and if you don't,
// the newly created term set cannot be obtained from the server.
targetTermStore = GetTermStoreObject(targetClientContext);
// Recursively add the other terms.
ProcessSubTerms(sourceClientContext, targetClientContext, targetTermSet, targetTerm, sourceTerm, languagesToProcess, targetTermStore.DefaultLanguage);
}
}
}
targetClientContext.ExecuteQuery();
}
Scénario 2 : modifications de processus
Lorsque vous sélectionnez Modifications des processus, le add-in vous invite à entrer un groupe de termes à synchroniser, puis appelle la méthode ProcessChanges dans MMSSyncManager.cs. ProcessChanges utilise la méthode GetChanges de la classe ChangedInformation pour récupérer toutes les modifications apportées aux groupes, ensembles de termes et termes dans le service de métadonnées gérées source. Les modifications sont ensuite appliquées au service de métadonnées gérées cible.
Notes
Ce document inclut uniquement certaines parties de la méthode ProcessChanges. Pour passer en revue l’intégralité de la méthode, ouvrez la solution Core.MMSSync Visual Studio.
La méthode ProcessChanges commence par créer un objet TaxonomySession.
Log.Internal.TraceInformation((int)EventId.TaxonomySession_Open, "Opening the taxonomy session");
TaxonomySession sourceTaxonomySession = TaxonomySession.GetTaxonomySession(sourceClientContext);
TermStore sourceTermStore = sourceTaxonomySession.GetDefaultKeywordsTermStore();
sourceClientContext.Load(sourceTermStore,
store => store.Name,
store => store.DefaultLanguage,
store => store.Languages,
store => store.Groups.Include(group => group.Name, group => group.Id));
sourceClientContext.ExecuteQuery();
Ensuite, il récupère les modifications à l’aide de l’objet ChangeInformation et la définition de la date de début sur l’objet ChangeInformation. Cet exemple récupère toutes les modifications apportées au cours de l’année précédente.
Log.Internal.TraceInformation((int)EventId.TermStore_GetChangeLog, "Reading the changes");
ChangeInformation changeInformation = new ChangeInformation(sourceClientContext);
changeInformation.StartTime = startFrom;
ChangedItemCollection termStoreChanges = sourceTermStore.GetChanges(changeInformation);
sourceClientContext.Load(termStoreChanges);
sourceClientContext.ExecuteQuery();
La méthode GetChanges renvoie un ChangedItemCollection, qui édectionne toutes les modifications qui se produisent dans le magasin de termes, comme illustré dans l’exemple de code suivant. La dernière ligne de l’exemple vérifie si changedItem était un groupe de termes. ProcessChanges inclut du code pour effectuer des vérifications similaires sur ChangedItem pour les ensembles de termes et les termes.
foreach (ChangedItem _changeItem in termStoreChanges)
{
if (_changeItem.ChangedTime < startFrom)
{
Log.Internal.TraceVerbose((int)EventId.TermStore_SkipChangeLogEntry, "Skipping item {1} changed at {0}", _changeItem.ChangedTime, _changeItem.Id);
continue;
}
Log.Internal.TraceVerbose((int)EventId.TermStore_ProcessChangeLogEntry, "Processing item {1} changed at {0}. Operation = {2}, ItemType = {3}", _changeItem.ChangedTime, _changeItem.Id, _changeItem.Operation, _changeItem.ItemType);
#region Group changes
if (_changeItem.ItemType == ChangedItemType.Group)
Le type d’élément modifié peut être un groupe de termes, un ensemble de termes ou un terme. Chaque type d’élément modifié possède différentes opérations que vous pouvez effectuer sur celui-ci. Le tableau suivant répertorie les opérations que vous pouvez effectuer sur chaque type d’élément modifié.
| Qu’est-ce qui a changé ? (ChangedItemType) | Opérations que vous pouvez effectuer sur le type d’élément modifié (ChangedOperationType) |
|---|---|
| Groupe | Delete group Ajout d’un groupe Modifier un groupe |
| TermSet | Supprimer un ensemble de termes Déplacer l’ensemble de termes Copier l’ensemble de termes Ajouter un ensemble de termes Modifier l’ensemble de termes |
| Terme | Supprimer un terme Terme de déplacement Terme de copie Terme de modification du chemin d’accès Terme de fusion Ajouter un terme Modifier un terme |
Le code suivant montre comment effectuer une opération de suppression lorsqu’un groupe de termes a été supprimé dans le service de métadonnées gérées source.
#region Delete group
if (_changeItem.Operation == ChangedOperationType.DeleteObject)
{
TermGroup targetTermGroup = targetTermStore.GetGroup(_changeItem.Id);
targetClientContext.Load(targetTermGroup, group => group.Name);
targetClientContext.ExecuteQuery();
if (!targetTermGroup.ServerObjectIsNull.Value)
{
if (termGroupExclusions == null || !termGroupExclusions.Contains(targetTermGroup.Name, StringComparer.InvariantCultureIgnoreCase))
{
Log.Internal.TraceInformation((int)EventId.TermGroup_Delete, "Deleting group: {0}", targetTermGroup.Name);
targetTermGroup.DeleteObject();
targetClientContext.ExecuteQuery();
}
}
}
#endregion