private List <string> ApplyChangesToDatabase_Insert(List <ConceptApplication> toBeInserted, List <ConceptApplication> newApplications)
        {
            var newScripts = new List <string>();

            Graph.TopologicalSort(toBeInserted, ConceptApplication.GetDependencyPairs(newApplications));

            int insertedCACount = 0;

            foreach (var ca in toBeInserted)
            {
                string[] createSqlScripts = SplitSqlScript(ca.CreateQuery);
                if (createSqlScripts.Length > 0)
                {
                    LogDatabaseChanges(ca, "Creating");
                    insertedCACount++;
                }

                newScripts.AddRange(createSqlScripts);
                newScripts.AddRange(_sqlTransactionBatches.JoinScripts(_conceptApplicationRepository.InsertMetadataSql(ca)));
                newScripts.AddRange(MaybeCommitMetadataAfterDdl(createSqlScripts));
            }

            _logger.Info($"Creating {insertedCACount} concept applications.");
            return(newScripts);
        }
        private List <string> ApplyChangesToDatabase_Remove(List <ConceptApplication> toBeRemoved, List <ConceptApplication> oldApplications)
        {
            var newScripts = new List <string>();

            toBeRemoved = toBeRemoved.OrderBy(ca => ca.OldCreationOrder).ToList();                      // TopologicalSort is stable sort, so it will keep this (original) order unless current dependencies direct otherwise.
            Graph.TopologicalSort(toBeRemoved, ConceptApplication.GetDependencyPairs(oldApplications)); // Concept's dependencies might have changed, without dropping and recreating the concept. It is important to compute up-to-date remove order, otherwise FK constraint FK_AppliedConceptDependsOn_DependsOn might fail.
            toBeRemoved.Reverse();

            int removedCACount = 0;

            foreach (var ca in toBeRemoved)
            {
                string[] removeSqlScripts = SplitSqlScript(ca.RemoveQuery);
                if (removeSqlScripts.Length > 0)
                {
                    LogDatabaseChanges(ca, "Removing");
                    removedCACount++;
                }

                newScripts.AddRange(removeSqlScripts);
                newScripts.AddRange(_sqlTransactionBatches.JoinScripts(_conceptApplicationRepository.DeleteMetadataSql(ca)));
                newScripts.AddRange(MaybeCommitMetadataAfterDdl(removeSqlScripts));
            }

            _logger.Info($"Removing {removedCACount} concept applications.");
            return(newScripts);
        }
        private List <SqlBatchScript> ApplyChangesToDatabase_Insert(List <ConceptApplication> toBeInserted, List <ConceptApplication> newApplications)
        {
            var newScripts = new List <SqlBatchScript>();

            Graph.TopologicalSort(toBeInserted, ConceptApplication.GetDependencyPairs(newApplications));

            int insertedCACount = 0;

            foreach (var ca in toBeInserted)
            {
                if (!string.IsNullOrWhiteSpace(ca.CreateQuery))
                {
                    LogDatabaseChanges(ca, "Creating");
                    insertedCACount++;
                }

                newScripts.Add(new SqlBatchScript {
                    Sql = ca.CreateQuery, IsBatch = true, Name = $"Creating {ca}."
                });
                newScripts.AddRange(_sqlTransactionBatches.JoinScripts(_conceptApplicationRepository.InsertMetadataSql(ca))
                                    .Select((sql, x) => new SqlBatchScript {
                    Sql = sql, IsBatch = false, Name = $"Creating {ca} metadata ({x + 1})."
                }));
                newScripts.AddRange(MaybeCommitMetadataAfterDdl(ca.CreateQuery));
            }

            _logger.Info($"Creating {insertedCACount} database objects.");
            return(newScripts);
        }
        private List <SqlBatchScript> ApplyChangesToDatabase_Remove(List <ConceptApplication> toBeRemoved, List <ConceptApplication> oldApplications)
        {
            var newScripts = new List <SqlBatchScript>();

            toBeRemoved = toBeRemoved.OrderBy(ca => ca.OldCreationOrder).ToList();                      // TopologicalSort is stable sort, so it will keep this (original) order unless current dependencies direct otherwise.
            Graph.TopologicalSort(toBeRemoved, ConceptApplication.GetDependencyPairs(oldApplications)); // Concept's dependencies might have changed, without dropping and recreating the concept. It is important to compute up-to-date remove order, otherwise FK constraint FK_AppliedConceptDependsOn_DependsOn might fail.
            toBeRemoved.Reverse();

            int removedCACount = 0;

            foreach (var ca in toBeRemoved)
            {
                if (!string.IsNullOrWhiteSpace(ca.RemoveQuery))
                {
                    LogDatabaseChanges(ca, "Removing");
                    removedCACount++;
                }

                newScripts.Add(new SqlBatchScript {
                    Sql = ca.RemoveQuery, IsBatch = true, Name = $"Removing {ca}."
                });
                newScripts.AddRange(_sqlTransactionBatches.JoinScripts(_conceptApplicationRepository.DeleteMetadataSql(ca))
                                    .Select((sql, x) => new SqlBatchScript {
                    Sql = sql, IsBatch = false, Name = $"Removing {ca} metadata ({x + 1})."
                }));
                newScripts.AddRange(MaybeCommitMetadataAfterDdl(ca.RemoveQuery));
            }

            _logger.Info($"Removing {removedCACount} database objects.");
            return(newScripts);
        }
        private List <ConceptApplication> TrimEmptyApplications(List <ConceptApplication> newApplications)
        {
            var emptyCreateQuery     = newApplications.Where(ca => string.IsNullOrWhiteSpace(ca.CreateQuery)).ToList();
            var emptyCreateHasRemove = emptyCreateQuery.FirstOrDefault(ca => !string.IsNullOrWhiteSpace(ca.RemoveQuery));

            if (emptyCreateHasRemove != null)
            {
                throw new FrameworkException("A concept that does not create database objects (CreateDatabaseStructure) cannot remove them (RemoveDatabaseStructure): "
                                             + emptyCreateHasRemove.GetConceptApplicationKey() + ".");
            }

            var removeLeaves = Graph.RemovableLeaves(emptyCreateQuery, ConceptApplication.GetDependencyPairs(newApplications));

            _logger.Trace(() => $"Removing {removeLeaves.Count} empty leaf concept applications:{string.Concat(removeLeaves.Select(l => "\r\n-" + l))}");

            return(newApplications.Except(removeLeaves).ToList());
        }
        private DatabaseDiff CalculateApplicationsToBeRemovedAndInserted(List <ConceptApplication> oldApplications, List <ConceptApplication> newApplications)
        {
            var oldApplicationsByKey = oldApplications.ToDictionary(a => a.GetConceptApplicationKey());
            var newApplicationsByKey = newApplications.ToDictionary(a => a.GetConceptApplicationKey());

            // Find directly inserted and removed concept applications:

            var directlyRemoved  = oldApplicationsByKey.Keys.Except(newApplicationsByKey.Keys).ToList();
            var directlyInserted = newApplicationsByKey.Keys.Except(oldApplicationsByKey.Keys).ToList();

            foreach (string ca in directlyRemoved)
            {
                _logger.Trace("Directly removed concept application: " + ca);
            }
            foreach (string ca in directlyInserted)
            {
                _logger.Trace("Directly inserted concept application: " + ca);
            }

            // Find changed concept applications (different create SQL query):

            var existingApplications = oldApplicationsByKey.Keys.Intersect(newApplicationsByKey.Keys).ToList();
            var changedApplications  = existingApplications
                                       .Where(appKey => !string.Equals(
                                                  oldApplicationsByKey[appKey].CreateQuery,
                                                  newApplicationsByKey[appKey].CreateQuery,
                                                  StringComparison.Ordinal))
                                       .ToList();

            // Find dependent concepts applications to be regenerated:

            var toBeRemovedKeys = directlyRemoved.Union(changedApplications).ToList();
            var oldDependencies = ConceptApplication.GetDependencyPairs(oldApplications).Select(dep => Tuple.Create(dep.Item1.GetConceptApplicationKey(), dep.Item2.GetConceptApplicationKey()));
            var dependentRemovedApplications = Graph.IncludeDependents(toBeRemovedKeys, oldDependencies).Except(toBeRemovedKeys);

            var toBeInsertedKeys = directlyInserted.Union(changedApplications).ToList();
            var newDependencies  = ConceptApplication.GetDependencyPairs(newApplications).Select(dep => Tuple.Create(dep.Item1.GetConceptApplicationKey(), dep.Item2.GetConceptApplicationKey()));
            var dependentInsertedApplications = Graph.IncludeDependents(toBeInsertedKeys, newDependencies).Except(toBeInsertedKeys);

            var refreshDependents = dependentRemovedApplications.Union(dependentInsertedApplications).ToList();

            toBeRemovedKeys.AddRange(refreshDependents.Intersect(oldApplicationsByKey.Keys));
            toBeInsertedKeys.AddRange(refreshDependents.Intersect(newApplicationsByKey.Keys));

            // Report dependencies for items that need to be refreshed, for logging and debugging only:

            var newDependenciesByDependent = newDependencies.GroupBy(dep => dep.Item2, dep => dep.Item1).ToDictionary(group => group.Key, group => group.ToList());
            var oldDependenciesByDependent = oldDependencies.GroupBy(dep => dep.Item2, dep => dep.Item1).ToDictionary(group => group.Key, group => group.ToList());
            var toBeInsertedIndex          = new HashSet <string>(toBeInsertedKeys);
            var toBeRemovedIndex           = new HashSet <string>(toBeRemovedKeys);
            var changedApplicationsIndex   = new HashSet <string>(changedApplications);
            var refreshes = new List <(ConceptApplication RefreshedConcept, ConceptApplication Dependency, RefreshDependencyStatus DependencyStatus)>();

            foreach (string ca in refreshDependents.Intersect(newApplicationsByKey.Keys))
            {
                var refreshedConcept  = newApplicationsByKey[ca];
                var refreshBecauseNew = new HashSet <string>(newDependenciesByDependent.GetValueOrEmpty(ca).Intersect(toBeInsertedIndex));
                var refreshBecauseOld = new HashSet <string>(oldDependenciesByDependent.GetValueOrEmpty(ca).Intersect(toBeRemovedIndex));
                var dependsOnExisting = refreshBecauseNew.Intersect(refreshBecauseOld);

                refreshes.AddRange(refreshBecauseNew.Except(refreshBecauseOld)
                                   .Select(dependency => (refreshedConcept, newApplicationsByKey[dependency], RefreshDependencyStatus.New)));