用語グループの同期サンプル アドイン (SharePoint)
Core.MMSSync サンプルは、プロバイダー ホスト型アドインを使用して、ソースとターゲットの分類を同期させる方法を示しています。 このアドインは、マネージド メタデータ サービス内の 2 つの用語ストア (ソースとターゲット用語ストア) を同期します。
用語グループを同期させるには、次のオブジェクトを使用します。
- TermStore
- ChangeInformation
このソリューションは、以下の操作を行う場合に使用します。
- 2 つの分類を同期させます。 たとえば、別々のデータのセットに対して SharePoint Online と SharePoint Server オンプレミスの両方が使用された場合でも、同じ分類が使用されます。
- 特定の用語グループにのみ加えられた変更を同期します。
はじめに
開始するには、Core.MMSSync サンプル アドインを GitHub 上の Office 365 Developer パターンおよびプラクティス プロジェクトからダウンロードします。
注:
この記事で提供されるコードは、明示または黙示のいかなる種類の保証なしに現状のまま提供されるものであり、特定目的への適合性、商品性、権利侵害の不存在についての暗黙的な保証は一切ありません。
このアドインを実行するには、管理されたメタデータ サービスの用語ストアへのアクセス許可が必要です。 次の図は、これらのアクセス許可を割り当てる際に使用する Office 365 管理センターを示しています。
用語ストアにアクセス許可を割り当てるには、次のようにします。
Office 365 管理センターで、[用語ストア] を選択します。
[分類用語ストア] で、管理者を割り当てる用語セットを選択します。
[用語ストア管理者] に、用語ストア管理者のアクセス許可を必要とする組織アカウントを入力します。
Core.MMSSync サンプル アドインを使用する
アドインを起動すると、次の図に示すように、Core.MMSSync コンソール アプリケーションが表示されます。 次に示す情報の入力を促すメッセージが表示されます。
- ソースの用語ストアを含む Office 365 管理センターの URL (これはソース管理メタデータ サービスの URL です)。 たとえば、「
https://contososource-admin.sharepoint.com
」と入力します。 - ソース Managed Metadata Service における用語ストア管理者のユーザー名とパスワード。
- ターゲット用語ストアを含む Office 365 管理センターの URL (これはターゲット MMS の URL です)。 たとえば、「
https://contosotarget-admin.sharepoint.com
」と入力します。 - ターゲット Managed Metadata Service における用語ストア管理者のユーザー名とパスワード。
- 実行する操作の種類を指定します。 以下のどちらかの方法で実行できます。
- TermStore オブジェクトを使用して、用語グループを移動する (シナリオ 1)。
- ChangeInformation オブジェクトを使用して、変更を処理する (シナリオ 2)。
重要
このサンプル アドインは SharePoint Online と SharePoint Server オンプレミスの両方で機能します。
シナリオを選択したら、次の図に示すように、ソース Managed Metadata Service からターゲット Managed Metadata Service に同期する用語グループの名前を入力します。 たとえば、「Enterprise
」と入力します。
シナリオ 1: 用語グループの移動
[用語グループの移動] を選択すると、アドインが、同期させる用語グループを入力するように要求してから、MMSSyncManager.cs 内の CopyNewTermGroups メソッドを呼び出します。 その後で、CopyNewTermGroups が、次の手順に従って、用語グループをソース用語ストアからターゲット用語ストアにコピーします。
ソースとターゲットの用語ストア オブジェクトを取得します。
ソースとターゲットの用語ストアの言語が一致することを確認します。
ソース用語グループがターゲット用語ストア内に存在しないことを確認してから、CreateNewTargetTermGroup を使用してソース用語グループをターゲット用語ストアにコピーします。
TermGroupExclusions、TermGroupToCopy、TermSetInclusions パラメーターを設定して、処理される用語をフィルター処理できます。
次のコードは、MMSSyncManager.cs 内の CopyNewTermGroups メソッドと CreateNewTargetTermGroup メソッドを示しています。
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 - 変更の処理
[変更の処理] を選択すると、アドインが、同期させる用語グループを入力するように要求してから、MMSSyncManager.cs 内の ProcessChanges メソッドを呼び出します。 ProcessChanges が、ChangedInformation クラスの GetChanges メソッドを使用して、ソース Managed Metadata Service 内のグループ、用語セット、および用語に加えられたすべての変更を取得します。 その後で、変更内容がターゲット Managed Metadata Service に反映されます。
注:
このドキュメントに含まれているのは ProcessChanges メソッドの一部のみです。 メソッド全体を確認するには、Visual Studio で Core.MMSSync ソリューションを開いてください。
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) |
---|---|
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