/// <summary>
        /// Returns a template-generated text file that is evaluated based on the given <paramref name="templateId"/>.
        /// The text generation will implicitly contain a variable $ that evaluates to the results of the query specified in <paramref name="args"/>.
        /// </summary>
        public async Task <FileResult> PrintEntities(int templateId, PrintEntitiesArguments <TKey> args, CancellationToken cancellation)
        {
            await Initialize(cancellation);

            var collection = typeof(TEntity).Name;
            var defId      = DefinitionId;

            // (1) The Template Plan
            var template = await FactBehavior.GetPrintingTemplate(templateId, cancellation);

            var nameP     = new TemplatePlanLeaf(template.DownloadName, TemplateLanguage.Text);
            var bodyP     = new TemplatePlanLeaf(template.Body, TemplateLanguage.Html);
            var printoutP = new TemplatePlanTuple(nameP, bodyP);

            TemplatePlan plan;

            if (string.IsNullOrWhiteSpace(template.Context))
            {
                plan = printoutP;
            }
            else
            {
                plan = new TemplatePlanDefine("$", template.Context, printoutP);
            }

            QueryInfo contextQuery = BaseUtil.EntitiesPreloadedQuery(args, collection, defId);

            plan = new TemplatePlanDefineQuery("$", contextQuery, plan);

            // (2) Functions + Variables
            var globalFunctions = new Dictionary <string, EvaluationFunction>();
            var localFunctions  = new Dictionary <string, EvaluationFunction>();
            var globalVariables = new Dictionary <string, EvaluationVariable>();
            var localVariables  = BaseUtil.EntitiesLocalVariables(args, collection, defId);

            await FactBehavior.SetPrintingFunctions(localFunctions, globalFunctions, cancellation);

            await FactBehavior.SetPrintingVariables(localVariables, globalVariables, cancellation);

            // (3)  Generate the output
            CultureInfo culture = GetCulture(args.Culture);
            var         genArgs = new TemplateArguments(globalFunctions, globalVariables, localFunctions, localVariables, culture);
            await _templateService.GenerateFromPlan(plan : plan, args : genArgs, cancellation : cancellation);

            var downloadName = nameP.Outputs[0];
            var body         = bodyP.Outputs[0];

            // Change the body to bytes
            var bodyBytes = Encoding.UTF8.GetBytes(body);

            // Use a default download name if none is provided
            if (string.IsNullOrWhiteSpace(downloadName))
            {
                var meta = await GetMetadata(cancellation);

                var titlePlural = meta.PluralDisplay();
                if (args.I != null && args.I.Count > 0)
                {
                    downloadName = $"{titlePlural} ({args.I.Count})";
                }
                else
                {
                    int from = args.Skip + 1;
                    int to   = Math.Max(from, args.Skip + args.Top);
                    downloadName = $"{titlePlural} {from}-{to}";
                }
            }

            if (!downloadName.ToLower().EndsWith(".html"))
            {
                downloadName += ".html";
            }

            // Return as a file
            return(new FileResult(bodyBytes, downloadName));
        }
        /// <summary>
        /// Returns a template-generated text file that is evaluated based on the given <paramref name="templateId"/>.
        /// The text generation will implicitly contain a variable $ that evaluates to the entity whose id matches <paramref name="id"/>.
        /// </summary>
        public async Task <(byte[] FileBytes, string FileName)> PrintEntity(TKey id, int templateId, PrintEntityByIdArguments args, CancellationToken cancellation)
        {
            await Initialize(cancellation);

            // (1) Collection & DefId
            var collection = typeof(TEntity).Name;
            var defId      = DefinitionId;

            // (2) The Template Plan
            var template = await FactBehavior.GetPrintingTemplate(templateId, cancellation);

            var nameP     = new TemplatePlanLeaf(template.DownloadName, TemplateLanguage.Text);
            var bodyP     = new TemplatePlanLeaf(template.Body, TemplateLanguage.Html);
            var printoutP = new TemplatePlanTuple(nameP, bodyP);

            TemplatePlan plan;

            if (string.IsNullOrWhiteSpace(template.Context))
            {
                plan = printoutP;
            }
            else
            {
                plan = new TemplatePlanDefine("$", template.Context, printoutP);
            }

            QueryInfo contextQuery = BaseUtil.EntityPreloadedQuery(id, args, collection, defId);

            plan = new TemplatePlanDefineQuery("$", contextQuery, plan);

            // (3) Functions + Variables
            var globalFunctions = new Dictionary <string, EvaluationFunction>();
            var localFunctions  = new Dictionary <string, EvaluationFunction>();
            var globalVariables = new Dictionary <string, EvaluationVariable>();
            var localVariables  = BaseUtil.EntityLocalVariables(id, args, collection, defId);

            await FactBehavior.SetPrintingFunctions(localFunctions, globalFunctions, cancellation);

            await FactBehavior.SetPrintingVariables(localVariables, globalVariables, cancellation);

            // (4) Generate the output
            CultureInfo culture = GetCulture(args.Culture);
            var         genArgs = new TemplateArguments(globalFunctions, globalVariables, localFunctions, localVariables, culture);
            await _templateService.GenerateFromPlan(plan, genArgs, cancellation);

            var downloadName = nameP.Outputs[0];
            var body         = bodyP.Outputs[0];

            // Change the body to bytes
            var bodyBytes = Encoding.UTF8.GetBytes(body);

            // Do some sanitization of the downloadName
            if (string.IsNullOrWhiteSpace(downloadName))
            {
                downloadName = id.ToString();
            }

            if (!downloadName.ToLower().EndsWith(".html"))
            {
                downloadName += ".html";
            }

            // Return as a file
            return(bodyBytes, downloadName);
        }