private void UpdateRegions(
            PageTemplateFile fileTemplate,
            PageTemplateFileInfo fileTemplateDetails,
            PageTemplate dbPageTemplate,
            IExecutionContext executionContext
            )
        {
            // De-dup region names
            var duplicateRegionName = fileTemplateDetails
                                      .Regions
                                      .GroupBy(s => s.Name.ToLowerInvariant())
                                      .FirstOrDefault(g => g.Count() > 1);

            if (duplicateRegionName != null)
            {
                throw new PageTemplateRegistrationException($"Dulpicate template region '{ duplicateRegionName.First().Name }' in template { fileTemplate.VirtualPath }");
            }

            // Deletions
            var regionsToDelete = dbPageTemplate
                                  .PageTemplateRegions
                                  .Where(ts => !fileTemplateDetails.Regions.Any(fs => ts.Name.Equals(fs.Name, StringComparison.OrdinalIgnoreCase)))
                                  .ToList();

            foreach (var regionToDelete in regionsToDelete)
            {
                _dbContext.PageTemplateRegions.Remove(regionToDelete);
            }

            // Updates/Additions
            foreach (var fileRegion in fileTemplateDetails.Regions)
            {
                var existing = dbPageTemplate
                               .PageTemplateRegions
                               .FirstOrDefault(s => s.Name.Equals(fileRegion.Name, StringComparison.OrdinalIgnoreCase));

                if (existing == null)
                {
                    existing = new PageTemplateRegion();
                    existing.PageTemplate = dbPageTemplate;
                    existing.CreateDate   = executionContext.ExecutionDate;
                    _dbContext.PageTemplateRegions.Add(existing);
                }

                // casing might have changed
                if (existing.Name != fileRegion.Name)
                {
                    existing.Name       = fileRegion.Name;
                    existing.UpdateDate = executionContext.ExecutionDate;
                }

                // this will detach region data but there's no migrating that...
                if (existing.IsCustomEntityRegion != fileRegion.IsCustomEntityRegion)
                {
                    existing.IsCustomEntityRegion = fileRegion.IsCustomEntityRegion;
                    existing.UpdateDate           = executionContext.ExecutionDate;
                }
            }
        }
        private async Task <PageTemplate> UpdateTemplate(
            IExecutionContext executionContext,
            PageTemplate dbPageTemplate,
            PageTemplateFile fileTemplate,
            PageTemplateFileInfo fileTemplateDetails
            )
        {
            bool isNew = false;

            if (dbPageTemplate == null)
            {
                isNew = true;

                dbPageTemplate            = new PageTemplate();
                dbPageTemplate.FileName   = fileTemplate.FileName;
                dbPageTemplate.CreateDate = executionContext.ExecutionDate;

                _dbContext.PageTemplates.Add(dbPageTemplate);
            }

            if (!isNew &&
                HasCustomEntityDefinitionChanged(dbPageTemplate, fileTemplateDetails) &&
                await IsTemplateInUse(dbPageTemplate))
            {
                var msg = "Cannot change the custom entity type associated with a page template once it is in use";
                throw new Exception(msg);
            }

            if (fileTemplateDetails.CustomEntityDefinition == null)
            {
                dbPageTemplate.PageTypeId = (int)PageType.Generic;
                dbPageTemplate.CustomEntityDefinitionCode = null;
                dbPageTemplate.CustomEntityModelType      = null;
            }
            else
            {
                dbPageTemplate.PageTypeId                 = (int)PageType.CustomEntityDetails;
                dbPageTemplate.CustomEntityModelType      = fileTemplateDetails.CustomEntityModelType;
                dbPageTemplate.CustomEntityDefinitionCode = fileTemplateDetails.CustomEntityDefinition.CustomEntityDefinitionCode;
            }

            if (dbPageTemplate.IsArchived)
            {
                // Re-add template that was previously deleted
                dbPageTemplate.IsArchived = false;
            }

            dbPageTemplate.Name        = TextFormatter.PascalCaseToSentence(fileTemplate.FileName);
            dbPageTemplate.Description = fileTemplateDetails.Description;
            dbPageTemplate.FullPath    = fileTemplate.VirtualPath;
            dbPageTemplate.UpdateDate  = executionContext.ExecutionDate;

            ValidateTemplateProperties(dbPageTemplate);

            return(dbPageTemplate);
        }
        /// <remarks>
        /// Here we have to check to see if the definition has changed because
        /// changing it would break any pages that are using that template. The
        /// best approach for the developer would be to create a new template under a
        /// different name
        /// </remarks>
        private bool HasCustomEntityDefinitionChanged(PageTemplate pageTemplate, PageTemplateFileInfo fileInfo)
        {
            // If generic page
            if (pageTemplate.CustomEntityDefinitionCode == null)
            {
                return(fileInfo.CustomEntityDefinition != null);
            }

            // If custom entity page: compare details.
            return(pageTemplate.CustomEntityDefinitionCode != fileInfo.CustomEntityDefinition?.CustomEntityDefinitionCode);
        }
        /// <summary>
        /// Checks that a custom entity definition exists if it is required by the tempate. This
        /// can cause a DbContext.SaveChanges to run.
        /// </summary>
        private Task EnsureCustomEntityDefinitionExistsAsync(
            PageTemplateFileInfo fileTemplateDetails,
            PageTemplate dbPageTemplate,
            IExecutionContext executionContext
            )
        {
            var definitionCode = fileTemplateDetails.CustomEntityDefinition?.CustomEntityDefinitionCode;

            // Only update/check the definition if it has changed to potentially save a query
            if (!string.IsNullOrEmpty(definitionCode) && (dbPageTemplate == null || definitionCode != dbPageTemplate.CustomEntityDefinitionCode))
            {
                var command = new EnsureCustomEntityDefinitionExistsCommand(fileTemplateDetails.CustomEntityDefinition.CustomEntityDefinitionCode);
                return(_commandExecutor.ExecuteAsync(command, executionContext));
            }

            return(Task.CompletedTask);
        }