I have been working a lot with SharePoint lately, struggling with all kinds of strange problems.
So it's about time to update this blog and share some of my findings.
Let's start with renaming a Term in the Taxanomy Term Store.
I found some glitches regarding renaming Terms, so let's look into this in detail.
Prerequisites
Let's say we have a Termset Named Organization, and a Term named Logistics, like this:
Then we have a List with the following Taxaonomy Fields:
Field |
Type |
Organization |
TaxanomyFieldType |
OrganizationTaxHt0 |
Note |
TaxCatchAll |
LookupMulti |
TaxCatchAllLabel |
LookupMulti |
One document is uploaded and tagged with Logistics like this:
Getting Field Information
Now let's write come code to Display the Taxonomy Fields for a given ListItem:
private void DisplayFields(SPListItem listItem)
{
foreach (SPField field in listItem.Fields)
{
if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
{
TaxonomyField taxanomyField = (TaxonomyField)field;
if (taxanomyField.AllowMultipleValues)
{
TaxonomyFieldValueCollection values = listItem[taxanomyField.Id] as TaxonomyFieldValueCollection;
if (values.Count == 0)
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
TaxonomyFieldValue is null", field.Id, field.TypeAsString, field.Title, field.InternalName));
}
else
{
foreach (TaxonomyFieldValue value in values)
{
DisplayField(field, value);
}
}
}
else
{
TaxonomyFieldValue value = listItem[taxanomyField.Id] as TaxonomyFieldValue;
DisplayField(field, value);
}
}
else if (field.TypeAsString == "LookupMulti")
{
if (field.InternalName == "TaxCatchAll" || field.InternalName == "TaxCatchAllLabel")
{
SPFieldLookupValueCollection lookupValues =(SPFieldLookupValueCollection)listItem[field.Id];
if (lookupValues.Count == 0)
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString,
field.Title, field.InternalName, "<empty>", "<empty>"));
}
else
{
foreach (SPFieldLookupValue lookupValue in lookupValues)
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName,
lookupValue.LookupValue, lookupValue.LookupId));
}
}
}
else
{
//Other multi
SPFieldLookupValueCollection lookupValues =(SPFieldLookupValueCollection)listItem[field.Id];
if (lookupValues.Count == 0)
{
WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName,
"<none>", "<none>"));
}
else
{
foreach (SPFieldLookupValue lookupValue in lookupValues)
{
WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
LookupValue: {4}, LookupId: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName,
lookupValue.LookupValue, lookupValue.LookupId));
}
}
}
}
else if (field.TypeAsString == "Note")
{
TaxonomyField taxField = GetTaxonomyTextField(listItem, field);
if (taxField != null)
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3}, Value: {4},
RelatedTaxFieldInternalName: {5}", field.Id, field.TypeAsString, field.Title, field.InternalName,
listItem[field.Id], taxField.InternalName));
}
else
{
WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3}, Value: {4}",
field.Id, field.TypeAsString, field.Title, field.InternalName, listItem[field.Id]));
}
}
else
{
WriteVerboseMessage(string.Format("OtherField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},Value: {4}",
field.Id, field.TypeAsString, field.Title, field.InternalName, listItem[field.Id]));
}
}
}
private TaxonomyField GetTaxonomyTextField(SPListItem listItem, SPField noteField)
{
foreach (SPField field in listItem.Fields)
{
if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
{
TaxonomyField taxanomyField = (TaxonomyField)field;
if (taxanomyField.TextField == noteField.Id)
{
return taxanomyField;
}
}
}
return null;
}
private void DisplayField(SPField field, TaxonomyFieldValue fieldValue)
{
if (fieldValue == null)
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
TaxonomyFieldValue is null", field.Id, field.TypeAsString, field.Title, field.InternalName));
}
else
{
WriteInformationMessage(string.Format("TaxField Id : {0}, Type: {1}, Title: {2}, InternalName: {3},
TermLabel: {4}, TermId: {5}, WssId: {6}", field.Id, field.TypeAsString, field.Title, field.InternalName,
fieldValue.Label, fieldValue.TermGuid, fieldValue.WssId));
}
}
Now let's view the fields on our listitem:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument
TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization
TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11
TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11
TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword
TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics#?|, LookupId: 11
TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
So far so good, Organization, OrganizationTaxHTField0, TaxCatchAllLabel all says Logistics.
Renaming the Term
Now go into the Taxanomy Term Store in Central Admin and rename Logistics to Logistics1 and hit save.
View the fields again:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument
TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization
TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11
TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11
TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword
TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics#?|, LookupId: 11
TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
No change of course because the list's are not updated immediatly, but through a job named Taxanomy Update Scheduler.
Go into Central Administration, Monitoring, Review Job Definitions, Find the Taxanomy Update Scheduler job, there will be one for each site collection.
So be sure to pick the right one.
Now run the job. (The Job is scheduled to run every hour).
Make sure it has executed properly by looking at the Timer Job Status, it should appear in the History.
Ok let's look at our fields again:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument
TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization
TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics1, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11
TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11
TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword
TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics1#?|, LookupId: 11
TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
The Note field, OrganizationTaxHTField0 is not updated properly.
But if we look at the list:
Seems like it's updated, viewing and editing the properties of this field also displays Logistics1.
If you edit the properties, then hit save, then the Note field is properly updated to Logistics1.
Fixing the Problem
Anyway, i don't like things that are not 100%, so let's see how we can refresh the Note Field from code.
I tried a lot of things, but found one solution that works, here is the code:
private void RefreshTaxanomyFieldsOnListItem(SPListItem listItem, string taxFieldInternalName, string taxFieldNewLabel)
{
bool changed = false;
for (int i = 0; i < listItem.Fields.Count; ++i) //Dont use foreach
{
SPField field = listItem.Fields[i];
if (field.TypeAsString == "TaxonomyFieldType" || field.TypeAsString == "TaxonomyFieldTypeMulti")
{
if (string.IsNullOrEmpty(taxFieldInternalName) || field.InternalName == taxFieldInternalName)
{
TaxonomyField taxanomyField = (TaxonomyField)field;
if (taxanomyField.AllowMultipleValues)
{
TaxonomyFieldValueCollection values = listItem[taxanomyField.Id] as TaxonomyFieldValueCollection;
if (values != null)
{
foreach (TaxonomyFieldValue value in values)
{
if (string.IsNullOrEmpty(taxFieldNewLabel) || value.Label == taxFieldNewLabel)
{
WriteVerboseMessage(string.Format("Refreshing Taxanomy Multi Field: {0}",
taxanomyField.InternalName));
taxanomyField.SetFieldValue(listItem, values);
changed = true;
break;
}
}
}
}
else
{
TaxonomyFieldValue value = listItem[taxanomyField.Id] as TaxonomyFieldValue;
if (value != null)
{
if (string.IsNullOrEmpty(taxFieldNewLabel) || value.Label == taxFieldNewLabel)
{
WriteVerboseMessage(string.Format("Refreshing Taxanomy Single Field: {0}",
taxanomyField.InternalName));
taxanomyField.SetFieldValue(listItem, value);
changed = true;
}
}
}
}
}
}
if (changed)
{
WriteInformationMessage(string.Format("Updated File: {0}, Id: {1}, Title: {2}", listItem.Url,
listItem.UniqueId.ToString(), listItem.Title));
listItem.SystemUpdate(false);
}
}
So let's run this code on our list, then look at our fields again:
Get-ListTaxanomyFieldReport command started.
List Id: f7ed8478-bd9a-463e-9637-bc43e02777ff, Name: Document Archive
File: DocumentArchive/TestDocument.docx, Id: f1bdb360-f269-4de8-a73a-baaf657c09bf, Title: TestDocument
TaxField Id : 0f4b3ac1-7947-4cbf-a4ad-963e1fb892bd, Type: Note, Title: Organization_0, InternalName: OrganizationTaxHTField0, Value: Logistics1|2293196a-2af6-4521-9aa0-1230099a9b61, RelatedTaxFieldInternalName: Organization
TaxField Id : 3b2705e1-8f4b-47e6-bb0f-cffb1958f7c7, Type: TaxonomyFieldType, Title: Organization, InternalName: Organization, TermLabel: Logistics1, TermId: 2293196a-2af6-4521-9aa0-1230099a9b61, WssId: 11
TaxField Id : f3b0adf9-c1a2-4b02-920d-943fba4b3611, Type: LookupMulti, Title: Taxonomy Catch All Column, InternalName: TaxCatchAll, LookupValue: /jjIJ5eNSk2OsJp9BdEzVw==|LARTuP6U9kuzdmFGr+H5LA==|ahmTIvYqIUWaoBIwCZqbYQ==, LookupId: 11
TaxField Id : 1390a86a-23da-45f0-8efe-ef36edadfb39, Type: Note, Title: TaxKeywordTaxHTField, InternalName: TaxKeywordTaxHTField, Value: , RelatedTaxFieldInternalName: TaxKeyword
TaxField Id : 8f6b6dd8-9357-4019-8172-966fcd502ed2, Type: LookupMulti, Title: Taxonomy Catch All Column1, InternalName: TaxCatchAllLabel, LookupValue: Logistics1#?|, LookupId: 11
TaxField Id : 23f27201-bee3-471e-b2e7-b64fd8b7ca38, Type: TaxonomyFieldTypeMulti, Title: Enterprise Keywords, InternalName: TaxKeyword, TaxonomyFieldValue is empty
Get-ListTaxanomyFieldReport command ended.
Finally everything is correct.
I wrote a Commandlet that did this refresh, but i didn't want to execute the Taxanomy Update Scheduler job manually every time
so i found this:
TaxonomySession.SyncHiddenList(site);
I thought that this was the command that the Taxanomy Update Scheduler job was executing, but thats not the case.
If i ran this command, no changes where made to the list, if i ran this command after running the Taxanomy Update Scheduler job
then the list item was reverted back to the old name in all fields ??
If anyone can tell me where i can find the source for the Taxanomy Update Scheduler job i would be really happy, didn't have time
to investigate this any further.
Anyway i decided to run the job before i executed the RefreshTaxanomyFieldsOnListItem like this:
private void RefreshHiddenList(SPSite site, int timeoutSeconds)
{
foreach (SPJobDefinition job in site.WebApplication.JobDefinitions)
{
if (job.Name.Equals("UpdateHiddenListJobDefinition") && job.Status == SPObjectStatus.Online)
{
DateTime dateStart = DateTime.Now;
job.RunNow();
while (job.LastRunTime < dateStart)
{
if (DateTime.Now > dateStart.AddSeconds(timeoutSeconds))
{
Console.WriteLine("Taxanomy Update Scheduler did not finish executing due to a timeout !");
break;
}
Thread.Sleep(1000);
}
break;
}
}
}
So the correct sequence are:
1. Rename the Term.
2. Run RefreshHiddenList() or run the Taxaonomy Update Scheduler.
3. Run RefreshTaxanomyFieldsOnListItem().
I'm not sure what the consequences are when the Note field isn't updated
as it should.
But if you are using the Note field in your custom code, webparts etc.
you should be aware of that it could be wrong.