0

I have the following CSOM running as an Azure Web Job which retrieves a list of the Taxonomic Terms for each SP List Item. It works but its very slow for list items with a lot of terms. This causes timeout issues and hangs my azure web job. Please help me to improve its efficiency and reduce the number of round trips to the server. This is for SharePoint Online.

public static List<Term> GetSPTerms(ListItem item, string fieldName, ClientContext context)

    {

        List<Term> returnList = new List<Term>();

        // Taxonomy field value
        TaxonomyFieldValueCollection taxFieldValueColl = item[fieldName] as TaxonomyFieldValueCollection;
        foreach (TaxonomyFieldValue value in taxFieldValueColl)
        {
            var guid = Guid.Parse(value.TermGuid);
            var taxonomy = TaxonomySession.GetTaxonomySession(context);
            var term = taxonomy.GetTerm(guid);
            //we need to load again to make the term and its parent available in the current context.
            context.Load(term);
            context.Load(term.Parent);
            //context.ExecuteQuery();
            //Send a heartbeat. Suspect that a lot of taxonomic values is causing a timeout to the webjob. 15/11/2021
            WriteToLog("Processing taxononmy value");
            //5 retries, with a base delay of 30 secs.
            context.ExecuteQueryWithIncrementalRetry(5, 30000);
            if (term != null)    //Check if the term is null before adding. It will be null if it is an orphaned term, not in the term store.
            {
                returnList.Add(term);
            }

        }


            return returnList;
    }

I have now revised the code based on feedback from @Dylan-Cristy:

public static List<Term> GetSPTermsNew(ListItem item, string fieldName, ClientContext context)
        {
            List<Term> returnList = new List<Term>();
            // Taxonomy field value
            TaxonomyFieldValueCollection taxFieldValueColl = item[fieldName] as TaxonomyFieldValueCollection;

            //Create an Array of Guids
            Guid[] termIds = new Guid[taxFieldValueColl.Count];
            

            for (int i = 0; i < taxFieldValueColl.Count; i++)
            {
                var guid = Guid.Parse(taxFieldValueColl[i].TermGuid);
                termIds[i] = guid;
            }


            var taxonomy = TaxonomySession.GetTaxonomySession(context);
            //Get da terms
            var terms = taxonomy.GetTermsById(termIds);
            //Load terms into the context
            context.Load(terms);
            
            //Execute the query
            context.ExecuteQuery();
            //Load term parents into the context
            foreach (var term in terms)
            {
                context.Load(term, t => t.Parent);
            }
            //Execute the Query
            context.ExecuteQuery();
            returnList = terms.ToList();
            return returnList;
        }

What I don't like is having to do a foreach loop on the terms to get the parent terms. See my comment below.

0

I see a couple of ways this could be improved. First, you are creating a new TaxonomySession for every single term. That seems unnecessary. Just moving the line

var taxonomy = TaxonomySession.GetTaxonomySession(context);

outside of the foreach loop is probably going to give you some improvement.

Second, there are a couple ways to get multiple terms in one call, you don't have to loop through the terms and call taxonomy.GetTerm() on every single one.

One way is by using TaxonomySession.GetTermsById(termIds) to get all of the terms that are tagged on your item. That would look something like:

TaxonomyFieldValueCollection taxFieldValueColl = item[fieldName] as TaxonomyFieldValueCollection;

Guid[] termIds = new Guid[];

foreach (TaxonomyFieldValue value in taxFieldValueColl)
{
    var guid = Guid.Parse(value.TermGuid);
    termIds.push(guid);
}

var taxonomy = TaxonomySession.GetTaxonomySession(context);
var terms = taxonomy.GetTermsById(termIds);
context.load(terms);
context.ExecuteQuery();

and then you would have all the terms in one call. I did notice that you are trying to ensure that the term.Parent is loaded by doing context.load(term.Parent), and I'm not sure this would definitely get the terms' parents.

The other thing you could do would be to call TaxonomySession.GetTerms() which the documentation says

Gets Term objects for the current TaxonomySession object.

so I suppose you could use it to get all the terms, and then you would have to figure out how to search through the results to find the terms that match the ones tagged on your item. But that seems like it would be more reliable to get the terms' parents because you would be getting everything. Also, that method takes a LabelMatchInformation object as an argument, and I'm not sure if you can configure a LabelMatchInformation object to say to SharePoint "give me everything".

var labelMatch = new LabelMatchInformation(context);

// figure out what you would have to set on the labelMatch to return all terms

var allTerms = taxonomy.GetTerms(labelMatch);
context.load(allTerms);
context.ExecuteQuery(); 

TaxonomyFieldValueCollection taxFieldValueColl = item[fieldName] as TaxonomyFieldValueCollection;

foreach (TaxonomyFieldValue value in taxFieldValueColl)
{
    var guid = Guid.Parse(value.TermGuid);
    
    // figure out how to find this term in allTerms
}
1
  • thanks for the response, it really helped. Please see my revisions above. I had to make some minor changes to your code because its CSOM, not JSOM. The adding of the term parent to the context is necessary. Without it some of my code further on will fail. Do you have any suggestions on how avoid having to do the foreach and a separate call to the ExecuteQuery to get the parents? Btw I checked and the parent is definitely loaded into the context in my previous (and revised) code. I just want a better way to get the parents now. Nov 25 at 17:26
0

I got the final code working. Here it is for anyone else looking. To add the parent terms, I used IncludeWithDefaultProperties and added the property there. This avoids any further calls executequery. Thanks @Dylan-Cristy for getting me most of the way there.

public static List<Term> GetSPTerms(ListItem item, string fieldName, ClientContext context)
        {
            //Create a List of Terms
            List<Term> returnList = new List<Term>();
            // Get Taxonomy field value
            TaxonomyFieldValueCollection taxFieldValueColl = item[fieldName] as TaxonomyFieldValueCollection;

            //Create an Array of Guids
            Guid[] termIds = new Guid[taxFieldValueColl.Count];
            
            //Loop the Taxonomic Field Value Collection and store the GUIDs in the GUID array.
            for (int i = 0; i < taxFieldValueColl.Count; i++)
            {
                var guid = Guid.Parse(taxFieldValueColl[i].TermGuid);
                termIds[i] = guid;
            }

            //Create a new taxonomic session. 
            var taxonomy = TaxonomySession.GetTaxonomySession(context);
            //Get all da terms for the list item in one go by querying the array of Term GUIDS
            var terms = taxonomy.GetTermsById(termIds);
            
            //Load all the terms into the context
            //Get the default properties for each term and then specify we also want the parent as this is not retrieved by default.
            //https://docs.microsoft.com/en-us/previous-versions/office/developer/sharepoint-2010/ee539350(v=office.14)
            context.Load(terms, allterms => allterms.IncludeWithDefaultProperties(term => term.Parent));

            //Execute the query
            context.ExecuteQuery();
            returnList = terms.ToList();
            return returnList;
        }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.