Compartir a través de


El complemento de ejemplo de SharePoint para sincronizar grupos de términos

El ejemplo Core.MMSSync muestra cómo usar un complemento hospedado por el proveedor para sincronizar una taxonomía de origen y destino. Este complemento sincroniza dos almacenes de términos en el servicio de metadatos administrados: un almacén de términos de origen y un almacén de términos de destino.

Para sincronizar los grupos de términos se usan los siguientes objetos:

  • TermStore
  • ChangeInformation

Use esta solución si quiere:

  • Sincronizar dos taxonomías. Por ejemplo, puede usar SharePoint Online y SharePoint Server local para conjuntos de datos distintos, pero que usan la misma taxonomía.
  • Sincronizar los cambios realizados en solo un grupo de términos específico.

Antes de empezar

Para empezar, descargue el complemento de ejemplo Core.MMSSync desde el proyecto modelos y prácticas de desarrollo de Office 365 en GitHub.

Nota:

El código de este artículo se proporciona tal cual, sin garantía de ningún tipo, expresa o implícita, incluidas las garantías implícitas de aptitud para un propósito particular, comerciabilidad o ausencia de infracción.

Antes de ejecutar este complemento, debe tener el permiso para acceder al almacén de términos en el servicio de metadatos administrados. La figura siguiente muestra el Centro de administración de Office 365 dónde se asignan los permisos.

Captura de pantalla que muestra el Centro de administración de SharePoint, con el almacén de términos, el cuadro de búsqueda del almacén de términos de taxonomía y los cuadros de los administradores del almacén de términos resaltados.

Asignar permisos al almacén de términos:

  1. En el Centro de administración de Office 365, seleccione Almacén de términos.

  2. En almacén de términos de taxonomía, seleccione el conjunto de términos que quiere asignar a un administrador.

  3. En Administradores del almacén de términos, escriba la cuenta de la organización que necesita permisos de administrador del almacén de términos.

Usar el complemento de ejemplo Core.MMSSync

Al iniciar el complemento, verá una aplicación de consola Core.MMSSync, como se muestra en la figura siguiente. Tendrá que especificar la información siguiente:

  • La dirección URL del Centro de administración de Office 365 que contiene el almacén de términos de origen (esta es la dirección URL del servicio de metadatos administrados de origen). Por ejemplo, puede escribir https://contososource-admin.sharepoint.com.
  • El nombre de usuario y contraseña de administrador del almacén de términos en el servicio de metadatos administrados de origen.
  • La dirección URL del Centro de administración de Office 365 que contiene el almacén de términos de destino (esta es la dirección URL del servicio de metadatos administrados de destino). Por ejemplo, puede escribir https://contosotarget-admin.sharepoint.com.
  • El nombre de usuario y contraseña del administrador del almacén de términos en el servicio de metadatos administrados de destino.
  • Tipo de operación que se va a realizar. Puede:
    • Mover un grupo de términos (escenario 1) con el objeto TermStore.
    • Procesar cambios (escenario 2) con el objeto ChangeInformation.

Importante

Este complemento de ejemplo funciona con SharePoint Online y SharePoint Server local.

Captura de pantalla de la aplicación de consola que solicita que se introduzca la información.

Después de seleccionar el escenario, escriba el nombre del grupo de términos que quiere sincronizar desde el origen con su servicio de metadatos administrados de destino, como se muestra en la siguiente ilustración. Por ejemplo, puede escribir Enterprise.

Captura de pantalla de una lista desplegable del almacén de términos de taxonomía

Escenario 1: Mover grupo de términos

Al seleccionar Mover grupo de términos, el complemento le pedirá que escriba el grupo de términos que quiere sincronizar y luego llama al método CopyNewTermGroups de MMSSyncManager.cs. Luego, CopyNewTermGroups para copiar un grupo de términos del almacén de términos de origen al almacén de términos de destino hace lo siguiente:

  1. Recupera los objetos del almacén de términos de origen y destino.

  2. Comprueba que los lenguajes de los almacenes de términos de origen y destino coincidan.

  3. Comprueba que el grupo de términos de origen no exista en el almacén de términos de destino y luego, copia el grupo de términos de origen en el almacén de términos de destino con CreateNewTargetTermGroup.

Puede establecer los parámetros TermGroupExclusions, TermGroupToCopy y TermSetInclusions para filtrar qué términos se procesan.

El código siguiente muestra los métodos CopyNewTermGroups y CreateNewTargetTermGroup de 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 &amp;&amp; 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 &amp;&amp; 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();
        }

Escenario 2: Procesar cambios

Al seleccionar Procesar cambios, el complemento le pedirá que escriba el grupo de términos que quiere sincronizar y luego llama al método ProcessChanges de MMSSyncManager.cs. ProcessChanges usa el método GetChanges de la clase ChangedInformation para recuperar todos los cambios realizados en grupos, conjuntos de términos y términos en el servicio de metadatos administrados de origen. Luego, se aplican los cambios en el servicio de metadatos administrados de destino.

Nota:

Este documento incluye solo algunas partes del método ProcessChanges. Para revisar todo el método, abra la solución Core.MMSSync en Visual Studio.


El método ProcessChanges comienza creando un objeto 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();


Luego, recupera cambios con el objeto ChangeInformation y establece la fecha de inicio en el objeto ChangeInformation. En este ejemplo se recuperan todos los cambios realizados en el último año.

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();


El método GetChanges devuelve una ChangedItemCollection, que enumera todos los cambios que se producen en el almacén de términos, como se muestra en el ejemplo de código siguiente. La última línea del ejemplo comprueba si ChangedItem es un grupo de términos. ProcessChanges incluye el código para realizar comprobaciones similares en ChangedItem para conjuntos de términos y términos.

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)


El tipo de elemento modificado podría ser un grupo de términos, un conjunto de términos o un término. Cada tipo de elemento modificado tiene diferentes operaciones que puede realizarse en él. La siguiente tabla enumera las operaciones que pueden realizarse en cada tipo de elemento modificado.


¿Qué ha cambiado? (ChangedItemType) Operaciones que pueden realizarse en el tipo de elemento modificado (ChangedOperationType)
Group

Eliminar grupo

Agregar grupo

Editar grupo

TermSet

Eliminar conjunto de términos

Mover conjunto de términos

Copiar conjunto de términos

Añadir conjunto de términos

Editar conjunto de términos

Term

Eliminar término

Mover término

Copiar término

Cambiar ruta del término

Combinar término

Añadir término

Editar término

El código siguiente muestra cómo realizar una operación de eliminación, cuando se ha eliminado un grupo de términos en el servicio de metadatos administrados de origen.

#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

Vea también