/// <summary>
        /// Run migration for all databases
        /// </summary>
        /// <param name="runner"></param>
        /// <param name="forceApplyScripts">true : force apply scripts even if we have invaild scripts</para>
        public static void Process(this ExtMigrationRunner runner, bool forceApplyScripts)
        {
            if (!forceApplyScripts && runner.HasInvaildScripts)
            {
                Logger.Info("There is some invaild scripts, if you want to force apply them, you must config forceApplyScripts = true");
                return;
            }

            #region Check settings

            if (string.IsNullOrWhiteSpace(runner.RootNamespace))
            {
                throw new ArgumentNullException(nameof(runner.RootNamespace), "Value should not null or empty");
            }

            if (runner.DatabaseKeys == null)
            {
                throw new ArgumentNullException(nameof(runner.DatabaseKeys), "Value should not null");
            }

            #endregion

            var migrationDatabases = runner.DatabaseKeys.Select(dbKey => new MigrateDatabaseContext(runner.RootNamespace, dbKey.Value)).ToList();

            foreach (var dbContext in migrationDatabases)
            {
                Process(runner, dbContext, forceApplyScripts);
            }
        }
        public static ExtMigrationRunner ForRootNamespace(this ExtMigrationRunner runner, string rootNamespace)
        {
            if (string.IsNullOrWhiteSpace(rootNamespace))
            {
                throw new ArgumentNullException(nameof(rootNamespace), "<rootNamespace> should not null or empty");
            }

            runner.RootNamespace = rootNamespace;
            return(runner);
        }
        public static ExtMigrationRunner ForDatabaseLayers(this ExtMigrationRunner runner, SortedList <int, DatabaseScriptType> layers)
        {
            if (layers == null)
            {
                throw new ArgumentNullException(nameof(layers), "<layers> should not null");
            }

            runner.DatabaseScriptTypes = new SortedList <int, DatabaseScriptType>();

            if (layers.Any())
            {
                foreach (var item in layers)
                {
                    runner.DatabaseScriptTypes.Add(item.Key, item.Value);
                }
            }

            return(runner);
        }
        public static ExtMigrationRunner ForDatabases(this ExtMigrationRunner runner, SortedList <int, string> databaseKeys)
        {
            if (databaseKeys == null)
            {
                throw new ArgumentNullException(nameof(databaseKeys), "<databaseKeys> should not null");
            }

            runner.DatabaseKeys = new SortedList <int, string>();

            if (databaseKeys.Any())
            {
                foreach (var item in databaseKeys)
                {
                    if (!string.IsNullOrWhiteSpace(item.Value))
                    {
                        runner.DatabaseKeys.Add(item.Key, item.Value);
                    }
                }
            }

            return(runner);
        }
        /// <summary>
        /// Run migration for specific database
        /// </summary>
        /// <param name="runner"></param>
        /// <param name="dbContext"></param>
        public static void Process(this ExtMigrationRunner runner, MigrateDatabaseContext dbContext, bool forceApplyScripts)
        {
            if (!forceApplyScripts && runner.HasInvaildScripts)
            {
                Logger.Info("There is some invaild scripts, if you want to force apply them, you must config forceApplyScripts = true");
                return;
            }

            Logger.InfoFormat($"DATEBASE: {dbContext.DatabaseKey} ({dbContext.DatabaseName})");
            #region Check settings

            if (runner.DatabaseScriptTypes == null)
            {
                throw new ArgumentNullException(nameof(runner.DatabaseScriptTypes), "Value should not null");
            }

            if (runner.MigrationAssembly == null)
            {
                throw new ArgumentNullException(nameof(runner.MigrationAssembly), "Value should not null");
            }

            #endregion

            try
            {
                foreach (var scriptType in runner.DatabaseScriptTypes)
                {
                    ApplyMigrationUp(dbContext, scriptType.Value, runner.MigrationAssembly);
                }

                Logger.InfoFormat($"=> [{dbContext.DatabaseKey}] is done");
                Logger.InfoFormat(Environment.NewLine);
            }
            catch (Exception ex)
            {
                Logger.Error(ex);
                throw;
            }
        }
        /// <summary>
        /// Initialize default settings
        /// </summary>
        /// <returns></returns>
        public static ExtMigrationRunner Initialize()
        {
            var runner = new ExtMigrationRunner();

            #region Initialize RootNamespace

            var rootNamespaceFromAppSetting = ConfigurationManager.AppSettings["mgr:RootNamespace"];
            if (string.IsNullOrWhiteSpace(rootNamespaceFromAppSetting))
            {
                throw new ArgumentException("<rootNamespace> should not null or empty");
            }
            else
            {
                runner.RootNamespace = rootNamespaceFromAppSetting;
            }

            #endregion

            #region Initialize DatabaseKeys

            if (runner.DatabaseKeys == null)
            {
                runner.DatabaseKeys = new SortedList <int, string>();
            }
            var databaseKeys = ConfigurationManager.AppSettings["mgr:DatabaseKeys"];
            if (!string.IsNullOrWhiteSpace(databaseKeys))
            {
                var databaseKeyArr = databaseKeys.Split(',');
                if (databaseKeyArr.Any())
                {
                    for (var i = 0; i < databaseKeyArr.Length; i++)
                    {
                        if (!string.IsNullOrWhiteSpace(databaseKeyArr[i]))
                        {
                            runner.DatabaseKeys.Add(i, databaseKeyArr[i].Trim());
                        }
                    }
                }
            }

            #endregion

            #region Initialize DatabaseLayers

            if (runner.DatabaseScriptTypes == null)
            {
                runner.DatabaseScriptTypes = new SortedList <int, DatabaseScriptType>();
            }

            var layers = Enum.GetValues(typeof(DatabaseScriptType))
                         .OfType <DatabaseScriptType>().OrderBy(x => x);

            foreach (var layer in layers)
            {
                runner.DatabaseScriptTypes.Add((int)layer, layer);
            }

            #endregion

            #region Initialize MigrationAssembly

            var usedAttrAssemblies = AppDomain.CurrentDomain.GetAssemblies()
                                     .Where(assy => assy != typeof(ExtMigrationRunner).Assembly)
                                     .FirstOrDefault(assy => assy.GetTypes()
                                                     .Select(type => Attribute.IsDefined(type, typeof(BaseExtMgrAttribute)))
                                                     .Any(x => x));

            if (usedAttrAssemblies != null)
            {
                runner.MigrationAssembly = usedAttrAssemblies;
            }

            #endregion

            ShowValidateScripts(runner);
            return(runner);
        }
        static void ShowValidateScripts(ExtMigrationRunner runner)
        {
            runner.HasInvaildScripts = false;

            // warning invalid scripts
            var invalidScripts = new List <KeyValuePair <Type, string> >();

            var allScriptFiles = runner.MigrationAssembly.GetTypes()
                                 .Where(type => type.IsSubclassOf(typeof(MigrationBase)))
                                 .ToList();

            var validNamespaces = new Dictionary <string, string>();

            foreach (var dbKey in runner.DatabaseKeys)
            {
                if (!validNamespaces.ContainsKey(dbKey.Value))
                {
                    validNamespaces.Add(dbKey.Value, $"{runner.RootNamespace}.{dbKey.Value}");
                }
            }

            var duplicateVersionsTmp = new List <string>();

            foreach (var script in allScriptFiles)
            {
                if (!validNamespaces.Values.Contains(script.Namespace))
                {
                    invalidScripts.Add(new KeyValuePair <Type, string>(script, "[INCORRECT NAMESPACE]"));
                    continue;
                }
                else
                {
                    var dbKey = validNamespaces.First(x => x.Value == script.Namespace);

                    if (!script.IsPublic)
                    {
                        invalidScripts.Add(new KeyValuePair <Type, string>(script, $"[NOT PUBLIC - {{{dbKey.Key}}}]"));
                        continue;
                    }

                    var migAttr = script.GetCustomAttributes(typeof(BaseExtMgrAttribute), false).FirstOrDefault();

                    if (migAttr != null)
                    {
                        var versionLong = ((BaseExtMgrAttribute)migAttr).Version;
                        var key         = $"{dbKey.Key}.{versionLong}";

                        if (BaseExtMgrAttribute.VersionBank != null && BaseExtMgrAttribute.VersionBank.ContainsKey(versionLong))
                        {
                            var dtVersion = BaseExtMgrAttribute.VersionBank[versionLong];

                            var versionInvailMsg = ValidateMigrateVersion(dtVersion.Year, dtVersion.Month, dtVersion.Day, dtVersion.Hour, dtVersion.Minute, dtVersion.Second);
                            if (!string.IsNullOrWhiteSpace(versionInvailMsg))
                            {
                                invalidScripts.Add(new KeyValuePair <Type, string>(script, versionInvailMsg));
                                continue;
                            }
                        }

                        if (duplicateVersionsTmp.Contains(key))
                        {
                            invalidScripts.Add(new KeyValuePair <Type, string>(script, $"[DUPLICATE VERSION - {{{dbKey.Key}}}]"));
                            continue;
                        }
                        duplicateVersionsTmp.Add(key);
                        ValidScriptsStore.Add($"{versionLong}.{dbKey.Key}");
                    }
                    else
                    {
                        invalidScripts.Add(new KeyValuePair <Type, string>(script, $"[INCORRECT ATTRIBUTE - {{{dbKey.Key}}}]"));
                        continue;
                    }
                }
            }

            if (invalidScripts.Any())
            {
                runner.HasInvaildScripts = true;
                Logger.Info($"There are some invalid scripts:");
                foreach (var item in invalidScripts.OrderBy(x => x.Key.Name))
                {
                    Logger.Info($"  > {item.Key.Name} {item.Value}");
                }

                Logger.Info("");
            }
        }
 /// <summary>
 /// Run migration for all databases, stop if it see any invalid migration scripts
 /// </summary>
 /// <param name="runner"></param>
 public static void Process(this ExtMigrationRunner runner)
 {
     Process(runner, false);
 }
 public static ExtMigrationRunner ForMigrationAssembly(this ExtMigrationRunner runner, Assembly migrationAssembly)
 {
     runner.MigrationAssembly = migrationAssembly ?? throw new ArgumentNullException(nameof(migrationAssembly), "<migrationAssembly> should not null");
     return(runner);
 }