3

I am working on some C#.NET code that loads the entire set of taxonomy terms for a site. The current code is structured like this:

using (ClientContext context = new ClientContext(url))
{
    TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(context);
    context.Load(taxonomySession, ts => ts.TermStores);
    context.ExecuteQuery();

    TermStore termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
    context.Load(termStore,
            store => store.Groups.Include(
                group => group.TermSets.Include(
                    termSet => termSet.Terms.Include(
                        term => term.Terms
                    )
                )
            )
    );
    context.ExecuteQuery();

    foreach (TermGroup group in termStore.Groups)
    {
        foreach (TermSet termSet in group.TermSets)
        {
            LoadTerms(termSet.Terms, context);
        }
    }
}

void LoadTerms(TermCollection terms, ClientContext context)
{
    foreach (Term term in terms)
    {
        TermCollection termCollection = term.Terms;
        context.Load(termCollection,
            tc => tc.Include(t => t.Terms)
        );
        context.ExecuteQuery();
        LoadTerms(termCollection, context);
    }
}

It makes two ExecuteQuery calls for GetTaxonomySession and GetDefaultSiteCollectionTermStore, then it makes a bunch more calls to recursively load all the terms.

I am trying to reduce the number of ExecuteQuery calls as much as possible. Is there any way to structure this code to recursively load the entire terms hierarchy in a single call?

Alternatively, is there any way to determine when a term has no children? A lot of the ExecuteQuery calls are being made on terms that have no child terms, and return an empty result.

4 Answers 4

6

The following example demonstrates how to reduce the number of server round trips till two for retrieving all terms in term store:

using System.Collections.Generic;
using System.Linq;
using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.Taxonomy;

namespace SharePoint.Client.Taxonomy.Extensions
{
    public static class TermStoreExtensions
    {
        public static IEnumerable<Term> GetAllTerms(this TermStore termStore)
        {
            var ctx = termStore.Context;
            ctx.Load(termStore,
                       store => store.Groups.Include(
                           group => group.TermSets
                       )
               );
            ctx.ExecuteQuery();
            var result = new Dictionary<TermSet, TermCollection>();
            foreach (var termGroup in termStore.Groups)
            {
                foreach (var termSet in termGroup.TermSets)
                {
                    var allTermsInTermSet = termSet.GetAllTerms();
                    ctx.Load(allTermsInTermSet);
                    result[termSet] = allTermsInTermSet;
                }
            }
            var allTerms = result.SelectMany(x => x.Value);
            return allTerms;
        }

    }
}

Key points:

Usage

using (var ctx = GetContext(webUri, userName, password))
{
     var taxonomySession = TaxonomySession.GetTaxonomySession(ctx);
     var termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
     var allTerms = termStore.GetAllTerms();
     ctx.ExecuteQuery(); 
     //print 
     foreach (var term in allTerms)
     {
        Console.WriteLine(term.Name);
     }
 }
1

The answer to:

Alternatively, is there any way to determine when a term has no children?

Is to use the TermsCount property of the Term class. So the original code becomes:

using (ClientContext context = new ClientContext(url))
{
    TaxonomySession taxonomySession = TaxonomySession.GetTaxonomySession(context);
    context.Load(taxonomySession, ts => ts.TermStores);
    context.ExecuteQuery();

    TermStore termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
    context.Load(termStore,
            store => store.Groups.Include(
                group => group.TermSets.Include(
                    termSet => termSet.Terms.Include(
                        term => term.TermsCount
                    )
                )
            )
    );
    context.ExecuteQuery();

    foreach (TermGroup group in termStore.Groups)
    {
        foreach (TermSet termSet in group.TermSets)
        {
            LoadTerms(termSet.Terms, context);
        }
    }
}

void LoadTerms(TermCollection terms, ClientContext context)
{
    foreach (Term term in terms)
    {
        if (term.TermsCount > 0)
        {
            TermCollection termCollection = term.Terms;
            context.Load(termCollection,
                tc => tc.Include(
                    t => t.TermsCount
                )
            );
            context.ExecuteQuery();
            LoadTerms(termCollection, context);
        }
    }
}
0

Gets everything from a named term set:

    [DataContract]
    public class TermInfo
    {
        [DataMember]
        public Guid Id { get; set; }
        [DataMember]
        public string Name { get; set; }
        [DataMember]
        public TermInfo Parent { get; set; }
        [DataMember]
        public TermInfo[] Children { get; set; }
        [DataMember]
        public string Url { get; set; }
    }

    [HttpGet]
    [AllowAnonymous]
    [Route("intranet/navigation")]
    public IEnumerable<TermInfo> GetNavigation()
    {
        //TermInfo
        List<TermInfo> results = new List<TermInfo>();

        using (var ctx = new ClientContext("http://yoursharepointsite"))
        {
            var taxonomySession = TaxonomySession.GetTaxonomySession(ctx);
            var termStore = taxonomySession.GetDefaultSiteCollectionTermStore();
            var allTerms = termStore.GetTermSetsByName("Intranet Navigation", 1033);
            var termSet = allTerms.GetByName("Intranet Navigation");

            var terms = termSet.GetAllTerms();
            ctx.Load(terms, includes => includes.Include(i => i.Id, i => i.Name, i => i.Parent, i => i.Parent.Id, i => i.Parent.Name, i => i.IsRoot, i => i.LocalCustomProperties));
            ctx.ExecuteQuery();
            foreach (var term in terms)
            {
                var terminfo = new TermInfo { Id = term.Id, Name = term.Name };
                if (term.LocalCustomProperties.Keys.Contains("_Sys_Nav_SimpleLinkUrl"))
                {
                    terminfo.Url = term.LocalCustomProperties["_Sys_Nav_SimpleLinkUrl"];
                }

                if (!term.IsRoot)
                {
                    var parent = terms.FirstOrDefault(t => t.Id == term.Parent.Id);
                    terminfo.Parent = new TermInfo{ Name = parent.Name, Id = parent.Id };
                }
                results.Add(terminfo);
            }
        }

        //Go back through and add children
        foreach(var item in results)
        {
            var children = results.Where(x => x.Parent != null && x.Parent.Id == item.Id);
            if (children.Count() > 0)
            {
                item.Children = children.ToArray();
            }
        }
        return results;
    }//navigation
0

My requirements specified that I know the full linage of each term. In this example clientContext and termStore are private members of the method's class. This returns all terms that are not in the System or Site Collection groups.

private IQueryable<TermGroup> getTerms()
    {
        clientContext.Load(termStore,
            store => store.Name,
            store => store.Groups.Where(g => g.IsSystemGroup == false && g.IsSiteCollectionGroup == false)
                .Include(
                group => group.Id,
                group => group.Name,
                group => group.Description,
                group => group.IsSiteCollectionGroup,
                group => group.IsSystemGroup,
                group => group.TermSets.Include(
                    termSet => termSet.Id,
                    termSet => termSet.Name,
                    termSet => termSet.Description,
                    termSet => termSet.CustomProperties,
                    termSet => termSet.Terms.Include(
                        t => t.Id,
                        t => t.Description,
                        t => t.Name,
                        t => t.IsDeprecated,
                        t => t.Parent,
                        t => t.Labels,
                        t => t.LocalCustomProperties,
                        t => t.IsSourceTerm,
                        t => t.IsRoot,
                        t => t.IsKeyword))));

        clientContext.ExecuteQuery();

        return termStore.Groups;
    }

One can then loop through the results as such.

var allGroups = this.getTerms();
foreach (var termGroup in allGroups)
{
    //TermGroup specific logic goes here
    foreach (var termSet in termGroup.TermSets)
    {
        //TermSet specific logic goes here
        foreach (var term in termSet.Terms)
        {
            //Thar be Unicorns!
        }
    }

}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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