This follows on from creating a catalog programmatically.

Once you have a catalog to work with, categories are fairly easy to create, as they don’t really have many attributes. The one thing that we need to get right is the parent-child relationships to give us the structure that we want. Below, I’ve created an inline example that includes a hierarchical category structure for a liquor store with top level categories leading to more specific ones.

InitializeCatalogBlock.cs

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sitecore.Commerce.Core;
using Sitecore.Commerce.Plugin.Catalog;
using Sitecore.Framework.Pipelines;

namespace MyProject.Plugin.Catalog.Pipelines.Blocks.SeedCatalog
{
    [PipelineDisplayName("MyProject.Block.InitializeCategoriesBlock")]
    public class InitializeCategoriesBlock : PipelineBlock<string, string, CommercePipelineExecutionContext>
    {
        private readonly CommerceCommander _commerceCommander;
        private const string _catalogName = "MyProjectAuthoring_Master";

        public InitializeCategoriesBlock(CommerceCommander commerceCommander)
        {
            _commerceCommander = commerceCommander;
        }

        public override async Task<string> Run(string arg, CommercePipelineExecutionContext context)
        {
            //Check the environment to make sure we're only running this where we want to.
            if (arg != "MyEnvironment")
            {
                return arg;
            }

            await BootstrapCategories(context);

            return arg;
        }

        private async Task BootstrapCategories(CommercePipelineExecutionContext context)
        {
            ///Sample categories represented in a hierarchical structure.
            var categories = new List<BootstrapCategory>() {
                new BootstrapCategory("Categories", new List<BootstrapCategory>()
                {
                    new BootstrapCategory("Red Wine", new List<BootstrapCategory>
                    {
                        new BootstrapCategory("Variety", new List<BootstrapCategory>() {
                            new BootstrapCategory("Cabernet Sauvignon"),
                            new BootstrapCategory("Merlot"),
                            new BootstrapCategory("Shiraz")
                        }),
                        new BootstrapCategory("Country", new List<BootstrapCategory>()
                        {
                            new BootstrapCategory("Australia"),
                            new BootstrapCategory("France"),
                            new BootstrapCategory("Chile")

                        }),
                        new BootstrapCategory("Region", new List<BootstrapCategory>()
                        {
                            new BootstrapCategory("Barossa"),
                            new BootstrapCategory("Clare Valley"),
                            new BootstrapCategory("Great Western"),
                            new BootstrapCategory("Mornington Peninsula"),
                        })
                    }),
                    new BootstrapCategory("Beer"),
                    new BootstrapCategory("Spirits")
                })
            };

            foreach (var dataCategory in categories)
            {
                await CreateCategoryRecursive(dataCategory, string.Empty, context);
            }
        }

        private async Task CreateCategoryRecursive(BootstrapCategory bootstrapCategory, string parentCategoryId, 
            CommercePipelineExecutionContext context)
        {
            //Create a new category and put the new info into newCategory.
            var newCategory = await _commerceCommander.Pipeline<CreateCategoryPipeline>().Run(
                new CreateCategoryArgument(
                    _catalogName,
                    bootstrapCategory.Name,
                    bootstrapCategory.Name,
                    ""), context);

            //If the parentCategoryId is set, then we want to make this category the child of another category
            //otherwise, make it a child of the catalog.
            var parentId = string.IsNullOrWhiteSpace(parentCategoryId)
                ? _catalogName.EnsurePrefix(CommerceEntity.IdPrefix<Sitecore.Commerce.Plugin.Catalog.Catalog>())
                : parentCategoryId.EnsurePrefix(CommerceEntity.IdPrefix<Category>());

            //Save the relationship of the new category to the parent
            await _commerceCommander.Pipeline<IAssociateCategoryToParentPipeline>()
                .Run(new CatalogReferenceArgument(
                    _catalogName.EnsurePrefix(CommerceEntity.IdPrefix<Sitecore.Commerce.Plugin.Catalog.Catalog>()),
                    parentId,
                    newCategory.Categories.FirstOrDefault().Id), context);

            //Recurse over any child categories to create those too.
            if (bootstrapCategory.ChildCategories.Count > 0)
            {
                foreach (var childCategory in bootstrapCategory.ChildCategories)
                {
                    await CreateCategoryRecursive(childCategory, newCategory.Categories.FirstOrDefault().Id, context);
                }
            }
        }
    }

    ///Simple hierarchical category structure.
    public class BootstrapCategory
    {
        public BootstrapCategory(string name)
            : this(name, new List<BootstrapCategory>())
        {
        }

        public BootstrapCategory(string name, List<BootstrapCategory> childCategories)
        {
            Name = name;

            ChildCategories = childCategories;
        }

        public string Name { get; set; }
        public List<BootstrapCategory> ChildCategories { get; set; }
    }
}

Then we need to hook it up to ConfigureSitecore so that it will be added to the initialize environment pipeline.

ConfigureSitecore.cs

using System.Reflection;
using Microsoft.Extensions.DependencyInjection;
using Sitecore.Commerce.Core;
using Sitecore.Framework.Configuration;
using Sitecore.Framework.Pipelines.Definitions.Extensions;

namespace MyProject.Plugin.Catalog
{
    public class ConfigureSitecore2 : IConfigureSitecore
    {
        public void ConfigureServices(IServiceCollection services)
        {
            var assembly = Assembly.GetExecutingAssembly();
            services.RegisterAllPipelineBlocks(assembly);

            services.Sitecore().Pipelines(x => x
                .ConfigurePipeline<IInitializeEnvironmentPipeline>(c => c
                    //.Add<InitializeCatalogBlock>() (create the catalog first)
                    .Add<InitializeCategoriesBlock>()
                )
            );
        }
    }
}

That’s it! Note that you’ll need a catalog to put the categories in (commented out above). See creating a Sitecore Commerce catalog programmatically for how to do this in code. Then run CleanEnvironment() and InitializeEnvironment() to execute it.

Thanks to Andrew Sutherland from Sitecore for help in getting the right pipelines and other things in order.