Синхронизация групп терминов, пример надстройки SharePoint
В примере Core.MMSSync показано, как использовать надстройку, размещенную у поставщика, для синхронизации исходной и целевой таксономии. Эта надстройка синхронизирует два хранилища терминов в службе управляемых метаданных — исходное и целевое хранилище терминов.
Для синхронизации групп терминов используются следующие объекты:
- Termstore
- ChangeInformation
Используйте это решение для следующих задач:
- Синхронизация двух таксономий. Например, вы можете использовать и SharePoint Online, и локальную среду SharePoint Server для разных наборов данных, но они используют одну таксономию.
- Синхронизируйте изменения, внесенные только в определенную группу терминов.
Подготовка к работе
Чтобы приступить к работе, скачайте пример надстройки Core.MMSSync из проекта шаблоны и методики разработчика Office 365 на сайте GitHub.
Примечание.
Код, приведенный в этой статье, предоставляется "как есть" без какой-либо явной или подразумеваемой гарантии, включая подразумеваемые гарантии пригодности для какой-либо цели, для продажи или гарантии отсутствия нарушения прав иных правообладателей.
Перед запуском этой надстройки вам потребуется разрешение на доступ к хранилищу терминов в службе управляемых метаданных. На приведенном ниже рисунке показан Центр администрирования Office 365, в котором назначены эти разрешения.
Чтобы назначить разрешения банку терминов, сделайте следующее:
В Центре администрирования Office 365 выберите пункт банк терминов.
В разделе БАНК ТЕРМИНОВ ТАКСОНОМИИ выберите набор терминов, которому необходимо назначить администратора.
В поле Администраторы банка терминов введите учетную запись организации, которой необходимы разрешения администратора банка терминов.
Использование примера надстройки Core.MMSSync
При запуске надстройки отображается консольное приложение Core.MMSSync, как показано на следующем рисунке. Вам будет предложено ввести следующие сведения:
- URL-адрес центра администрирования Office 365, содержащего исходное хранилище терминов (это URL-адрес службы управляемых метаданных источника). Например, можно ввести .
https://contososource-admin.sharepoint.com
- Имя пользователя и пароль администратора хранилища терминов в исходной службе управляемых метаданных.
- URL-адрес центра администрирования Office 365, содержащего целевое хранилище терминов (это URL-адрес целевого MMS). Например, можно ввести .
https://contosotarget-admin.sharepoint.com
- Имя пользователя и пароль администратора хранилища терминов в целевой службе управляемых метаданных.
- Тип операции, которую требуется выполнить. Вы можете:
- Перемещение группы терминов (сценарий 1) с помощью объекта TermStore .
- Обработка изменений (сценарий 2) с помощью объекта ChangeInformation .
Важно!
Этот пример надстройки работает как с SharePoint Online, так и с SharePoint Server в локальной среде.
Выбрав сценарий, введите имя группы терминов, которую вы хотите синхронизировать из источника с целевой службой управляемых метаданных, как показано на следующем рисунке. Например, можно ввести .Enterprise
Сценарий 1. Перемещение группы терминов
При выборе пункта Переместить группу терминов надстройка предлагает ввести группу терминов для синхронизации, а затем вызывает метод CopyNewTermGroups в MMSSyncManager.cs. Затем CopyNewTermGroups выполняет следующие действия, чтобы скопировать группу терминов из исходного хранилища терминов в целевое хранилище терминов:
Извлекает исходные и целевые объекты хранилища терминов.
Проверяет соответствие языков исходного и целевого хранилищ терминов.
Проверяет, не существует ли исходная группа терминов в целевом хранилище терминов, а затем копирует исходную группу терминов в целевое хранилище терминов с помощью команды CreateNewTargetTermGroup.
Можно задать параметры TermGroupExclusions, TermGroupToCopy и TermSetInclusions , чтобы отфильтровать обрабатываемые термины.
В следующем коде показаны методы CopyNewTermGroups и CreateNewTargetTermGroup в 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();
}
Сценарий 2. Обработка изменений
При выборе параметра Обработка изменений надстройка предлагает ввести группу терминов для синхронизации, а затем вызывает метод ProcessChanges в MMSSyncManager.cs. ProcessChanges использует метод GetChanges класса ChangedInformation для получения всех изменений, внесенных в группы, наборы терминов и термины в исходной управляемой службе метаданных. Затем изменения применяются к целевой управляемой службе метаданных.
Примечание.
Этот документ содержит только некоторые части метода ProcessChanges . Чтобы просмотреть весь метод, откройте решение Core.MMSSync в Visual Studio.
Метод ProcessChanges начинается с создания объекта 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();
Затем он извлекает изменения с помощью объекта ChangeInformation и задает дату начала для объекта ChangeInformation . В этом примере извлекаются все изменения, внесенные за последний год.
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();
Метод GetChanges возвращает объект ChangedItemCollection, который перечисляет все изменения, происходящие в хранилище терминов, как показано в следующем примере кода. Последняя строка примера проверяет, является ли ChangedItem группой терминов. ProcessChanges содержит код для выполнения аналогичных проверок в ChangedItem для наборов терминов и терминов.
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)
Измененный тип элемента может быть группой терминов, набором терминов или термином. Каждый измененный тип элемента имеет различные операции, которые можно выполнить с ним. В следующей таблице перечислены операции, которые можно выполнять с каждым измененным типом элементов.
Что изменилось? (ChangedItemType) | Операции, которые можно выполнять с измененным типом элемента (ChangedOperationType) |
---|---|
Группа | Delete group Добавление группы Изменить группу |
TermSet | Удаление набора терминов Переместить набор терминов Копирование набора терминов Добавление набора терминов Изменение набора терминов |
Термин | Удалить термин Переместить термин Копировать термин Термин изменения пути Термин слияния Добавить термин Изменение термина |
В следующем коде показано, как выполнить операцию удаления при удалении группы терминов в исходной управляемой службе метаданных.
#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