diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e782bd..3e2b257 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ Represents the **NuGet** versions. +## v2.4.0 +- *Enhancement:* Added `MigrationAssemblyArgs` to allow for the specification of zero or more `Data` folder names. +- *Fixed:* Updated `CoreEx` to version `3.9.0`. + + ## v2.3.15 - *Fixed:* Updated `CoreEx` to version `3.8.0`. - *Fixed:* Updated `OnRamp` to version `2.0.0` which necessitated internal change from `Newtonsoft.Json` (now deprecated) to `System.Text.Json`; additionally, the `DataParser` was refactored accordingly. diff --git a/Common.targets b/Common.targets index aad69b9..948c28e 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - 2.3.15 + 2.4.0 preview Avanade Avanade diff --git a/src/DbEx.MySql/DbEx.MySql.csproj b/src/DbEx.MySql/DbEx.MySql.csproj index dfe9a9d..ff43fcf 100644 --- a/src/DbEx.MySql/DbEx.MySql.csproj +++ b/src/DbEx.MySql/DbEx.MySql.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/DbEx.MySql/Migration/MySqlMigration.cs b/src/DbEx.MySql/Migration/MySqlMigration.cs index 2a34f9e..81662de 100644 --- a/src/DbEx.MySql/Migration/MySqlMigration.cs +++ b/src/DbEx.MySql/Migration/MySqlMigration.cs @@ -25,7 +25,7 @@ public class MySqlMigration : DatabaseMigrationBase private readonly string _databaseName; private readonly IDatabase _database; private readonly IDatabase _masterDatabase; - private readonly List _resetBypass = new(); + private readonly List _resetBypass = []; /// /// Initializes an instance of the class. @@ -48,7 +48,7 @@ public MySqlMigration(MigrationArgsBase args) : base(args) // Defaults the schema object types unless already specified. if (SchemaObjectTypes.Length == 0) - SchemaObjectTypes = new string[] { "FUNCTION", "VIEW", "PROCEDURE" }; + SchemaObjectTypes = ["FUNCTION", "VIEW", "PROCEDURE"]; // Add/set standard parameters. Args.Parameter(MigrationArgsBase.DatabaseNameParamName, _databaseName, true); diff --git a/src/DbEx.SqlServer/DbEx.SqlServer.csproj b/src/DbEx.SqlServer/DbEx.SqlServer.csproj index 7084a7e..8e05bde 100644 --- a/src/DbEx.SqlServer/DbEx.SqlServer.csproj +++ b/src/DbEx.SqlServer/DbEx.SqlServer.csproj @@ -30,7 +30,7 @@ - + diff --git a/src/DbEx.SqlServer/Migration/SqlServerMigration.cs b/src/DbEx.SqlServer/Migration/SqlServerMigration.cs index f95e16a..b45ac36 100644 --- a/src/DbEx.SqlServer/Migration/SqlServerMigration.cs +++ b/src/DbEx.SqlServer/Migration/SqlServerMigration.cs @@ -27,7 +27,7 @@ public class SqlServerMigration : DatabaseMigrationBase private readonly string _databaseName; private readonly IDatabase _database; private readonly IDatabase _masterDatabase; - private readonly List _resetBypass = new(); + private readonly List _resetBypass = []; /// /// Initializes an instance of the class. @@ -50,7 +50,7 @@ public SqlServerMigration(MigrationArgsBase args) : base(args) // Defaults the schema object types unless already specified. if (SchemaObjectTypes.Length == 0) - SchemaObjectTypes = new string[] { "TYPE", "FUNCTION", "VIEW", "PROCEDURE", "PROC" }; + SchemaObjectTypes = ["TYPE", "FUNCTION", "VIEW", "PROCEDURE", "PROC"]; // Always add the dbo schema _first_ unless already specified. if (!Args.SchemaOrder.Contains("dbo")) diff --git a/src/DbEx/Console/AssemblyValidator.cs b/src/DbEx/Console/AssemblyValidator.cs index bfbf0b4..679de1f 100644 --- a/src/DbEx/Console/AssemblyValidator.cs +++ b/src/DbEx/Console/AssemblyValidator.cs @@ -1,5 +1,6 @@ // Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx +using CoreEx; using DbEx.Migration; using McMaster.Extensions.CommandLineUtils; using McMaster.Extensions.CommandLineUtils.Validation; @@ -15,15 +16,10 @@ namespace DbEx.Console /// /// Validates the assembly name(s). /// - public class AssemblyValidator : IOptionValidator + /// The to update. + public class AssemblyValidator(MigrationArgsBase args) : IOptionValidator { - private readonly MigrationArgsBase _args; - - /// - /// Initilizes a new instance of the class. - /// - /// The to update. - public AssemblyValidator(MigrationArgsBase args) => _args = args ?? throw new ArgumentNullException(nameof(args)); + private readonly MigrationArgsBase _args = args.ThrowIfNull(nameof(args)); /// /// Performs the validation. @@ -45,7 +41,7 @@ public ValidationResult GetValidationResult(CommandOption option, ValidationCont try { // Load from the specified file on the file system or by using its long form name. - list.Add(File.Exists(name) ? Assembly.LoadFrom(name!) : Assembly.Load(name!)); + _args.AddAssembly(File.Exists(name) ? Assembly.LoadFrom(name!) : Assembly.Load(name!)); } catch (Exception ex) { @@ -53,7 +49,6 @@ public ValidationResult GetValidationResult(CommandOption option, ValidationCont } } - _args.Assemblies.InsertRange(0, list.ToArray()); return ValidationResult.Success!; } } diff --git a/src/DbEx/Console/MigrationConsoleBase.cs b/src/DbEx/Console/MigrationConsoleBase.cs index d4bd033..a4b5b93 100644 --- a/src/DbEx/Console/MigrationConsoleBase.cs +++ b/src/DbEx/Console/MigrationConsoleBase.cs @@ -153,7 +153,7 @@ public async Task RunAsync(string[] args, CancellationToken cancellationTok UpdateBooleanOption(EntryAssemblyOnlyOptionName, () => { - Args.Assemblies.Clear(); + Args.ClearAssemblies(); Args.AddAssembly(Assembly.GetEntryAssembly()!); }); @@ -416,10 +416,10 @@ public static void WriteStandardizedArgs(DatabaseMigrationBase migrator, Action< migrator.Args.Logger.LogInformation("{Content}", $" {p.Key} = {p.Value}"); } - migrator.Args.Logger.LogInformation("{Content}", $"Assemblies{(migrator.Args.Assemblies.Count == 0 ? " = none" : ":")}"); + migrator.Args.Logger.LogInformation("{Content}", $"Assemblies{(!migrator.Args.Assemblies.Any() ? " = none" : ":")}"); foreach (var a in migrator.Args.Assemblies) { - migrator.Args.Logger.LogInformation("{Content}", $" {a.FullName}"); + migrator.Args.Logger.LogInformation("{Content}", $" {a.Assembly.FullName}"); } } diff --git a/src/DbEx/DbEx.csproj b/src/DbEx/DbEx.csproj index b8897f5..474c427 100644 --- a/src/DbEx/DbEx.csproj +++ b/src/DbEx/DbEx.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/DbEx/Migration/DatabaseMigrationBase.cs b/src/DbEx/Migration/DatabaseMigrationBase.cs index 91dfb71..f9189be 100644 --- a/src/DbEx/Migration/DatabaseMigrationBase.cs +++ b/src/DbEx/Migration/DatabaseMigrationBase.cs @@ -25,6 +25,7 @@ public abstract class DatabaseMigrationBase : IDisposable { private const string NothingFoundText = " ** Nothing found. **"; private const string OnDatabaseCreateName = "post.database.create"; + private static readonly string[] _splitters = ["\r\n", "\r", "\n"]; private HandlebarsCodeGenerator? _dataCodeGen; private bool _hasInitialized = false; @@ -119,11 +120,6 @@ protected DatabaseMigrationBase(MigrationArgsBase args) /// public string SchemaNamespace { get; set; } = "Schema"; - /// - /// Gets or sets the Migrations scripts namespace part name. - /// - public string DataNamespace { get; set; } = "Data"; - /// /// Gets or sets the list of supported schema object types in the order of precedence. /// @@ -235,7 +231,7 @@ private void PreExecutionInitialization() _hasInitialized = true; var list = (List)Namespaces; - Args.ProbeAssemblies.ForEach(x => list.Add(x.GetName().Name!)); + Args.ProbeAssemblies.ForEach(x => list.Add(x.Assembly.GetName().Name!)); // Walk the assembly hierarchy. var alist = new List(); @@ -441,9 +437,9 @@ protected virtual async Task DatabaseCreateAsync(CancellationToken cancell var scripts = new List(); foreach (var ass in Args.ProbeAssemblies) { - foreach (var name in ass.GetManifestResourceNames().Where(rn => Namespaces.Any(ns => rn.StartsWith($"{ns}.{MigrationsNamespace}.", StringComparison.InvariantCulture) && rn.EndsWith($".{OnDatabaseCreateName}.sql", StringComparison.InvariantCultureIgnoreCase))).OrderBy(x => x)) + foreach (var name in ass.Assembly.GetManifestResourceNames().Where(rn => Namespaces.Any(ns => rn.StartsWith($"{ns}.{MigrationsNamespace}.", StringComparison.InvariantCulture) && rn.EndsWith($".{OnDatabaseCreateName}.sql", StringComparison.InvariantCultureIgnoreCase))).OrderBy(x => x)) { - scripts.Add(new DatabaseMigrationScript(ass, name) { RunAlways = true }); + scripts.Add(new DatabaseMigrationScript(ass.Assembly, name) { RunAlways = true }); } } @@ -467,7 +463,7 @@ private async Task DatabaseMigrateAsync(CancellationToken cancellationToke var scripts = new List(); foreach (var ass in Args.ProbeAssemblies) { - foreach (var name in ass.GetManifestResourceNames().Where(rn => Namespaces.Any(ns => rn.StartsWith($"{ns}.{MigrationsNamespace}.", StringComparison.InvariantCulture))).OrderBy(x => x)) + foreach (var name in ass.Assembly.GetManifestResourceNames().Where(rn => Namespaces.Any(ns => rn.StartsWith($"{ns}.{MigrationsNamespace}.", StringComparison.InvariantCulture))).OrderBy(x => x)) { // Ignore any/all database create scripts. if (name.EndsWith($".{OnDatabaseCreateName}.sql", StringComparison.InvariantCultureIgnoreCase)) @@ -477,7 +473,7 @@ private async Task DatabaseMigrateAsync(CancellationToken cancellationToke var order = name.EndsWith(".pre.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 1 : name.EndsWith(".post.deploy.sql", StringComparison.InvariantCultureIgnoreCase) ? 3 : 2; - scripts.Add(new DatabaseMigrationScript(ass, name) { GroupOrder = order, RunAlways = order != 2 }); + scripts.Add(new DatabaseMigrationScript(ass.Assembly, name) { GroupOrder = order, RunAlways = order != 2 }); } } @@ -534,7 +530,7 @@ private async Task DatabaseSchemaAsync(CancellationToken cancellationToken Logger.LogInformation("{Content}", $" Probing for embedded resources: {string.Join(", ", GetNamespacesWithSuffix($"{SchemaNamespace}.*.sql"))}"); foreach (var ass in Args.ProbeAssemblies) { - foreach (var rn in ass.GetManifestResourceNames().OrderBy(x => x)) + foreach (var rn in ass.Assembly.GetManifestResourceNames().OrderBy(x => x)) { // Filter on schema namespace prefix and suffix of '.sql'. if (!(Namespaces.Any(x => rn.StartsWith($"{x}.{SchemaNamespace}.", StringComparison.InvariantCulture) && rn.EndsWith(".sql", StringComparison.InvariantCultureIgnoreCase)))) @@ -544,7 +540,7 @@ private async Task DatabaseSchemaAsync(CancellationToken cancellationToken if (scripts.Any(x => x.Name == rn)) continue; - scripts.Add(new DatabaseMigrationScript(ass, rn)); + scripts.Add(new DatabaseMigrationScript(ass.Assembly, rn)); } } @@ -695,20 +691,32 @@ protected virtual async Task DatabaseResetAsync(CancellationToken cancella /// private async Task DatabaseDataAsync(CancellationToken cancellationToken) { - Logger.LogInformation("{Content}", $" Probing for embedded resources: {string.Join(", ", GetNamespacesWithSuffix($"{DataNamespace}.*", true))}"); + var names = new List(); + foreach (var ass in Args.Assemblies) + { + foreach (var dns in ass.DataNamespaces) + { + names.Add($"{ass.Assembly.GetName().Name}.{dns}.*"); + } + } + + Logger.LogInformation("{Content}", $" Probing for embedded resources: {string.Join(", ", names)}"); var list = new List<(Assembly Assembly, string ResourceName)>(); - foreach (var ass in Args.Assemblies.Distinct()) // Assumed data builds on top of earlier (do not use ProbeAssemblies as this is reversed). + foreach (var ass in Args.Assemblies) { - foreach (var rn in ass.GetManifestResourceNames().OrderBy(x => x)) + foreach (var rn in ass.Assembly.GetManifestResourceNames().OrderBy(x => x)) { - // Filter on schema namespace prefix and supported suffixes. - if (!Namespaces.Any(x => rn.StartsWith($"{x}.{DataNamespace}.", StringComparison.InvariantCulture) && (rn.EndsWith(".sql", StringComparison.InvariantCultureIgnoreCase) - || rn.EndsWith(".yaml", StringComparison.InvariantCultureIgnoreCase) || rn.EndsWith(".yml", StringComparison.InvariantCultureIgnoreCase) - || rn.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase) || rn.EndsWith(".jsn", StringComparison.InvariantCultureIgnoreCase)))) - continue; + foreach (var dns in ass.DataNamespaces) + { + // Filter on schema namespace prefix and supported suffixes. + if (!Namespaces.Any(x => rn.StartsWith($"{x}.{dns}.", StringComparison.InvariantCulture) && (rn.EndsWith(".sql", StringComparison.InvariantCultureIgnoreCase) + || rn.EndsWith(".yaml", StringComparison.InvariantCultureIgnoreCase) || rn.EndsWith(".yml", StringComparison.InvariantCultureIgnoreCase) + || rn.EndsWith(".json", StringComparison.InvariantCultureIgnoreCase) || rn.EndsWith(".jsn", StringComparison.InvariantCultureIgnoreCase)))) + continue; - list.Add((ass, rn)); + list.Add((ass.Assembly, rn)); + } } } @@ -818,7 +826,7 @@ protected virtual async Task DatabaseDataAsync(List dataTables, /// /// Gets the with the specified namespace suffix applied. /// - private IEnumerable GetNamespacesWithSuffix(string suffix, bool reverse = false) + private string[] GetNamespacesWithSuffix(string suffix, bool reverse = false) { if (suffix == null) throw new ArgumentNullException(nameof(suffix)); @@ -829,7 +837,7 @@ private IEnumerable GetNamespacesWithSuffix(string suffix, bool reverse list.Add($"{ns}.{suffix}"); } - return list.Count == 0 ? new string[] { "(none)" } : [.. list]; + return list.Count == 0 ? ["(none)"] : [.. list]; } /// @@ -850,6 +858,7 @@ public async Task CreateScriptAsync(string? name = null, IDictionary private async Task CreateScriptInternalAsync(string? name, IDictionary? parameters, CancellationToken cancellationToken) { + name ??= "Default"; var rn = $"Script{name}_sql"; @@ -867,7 +876,7 @@ private async Task CreateScriptInternalAsync(string? name, IDictionary() }; - var lines = txt.Split(new string[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + var lines = txt.Split(_splitters, StringSplitOptions.None); string fn = "new-script"; foreach (var line in lines) { diff --git a/src/DbEx/Migration/MigrationArgsBase.cs b/src/DbEx/Migration/MigrationArgsBase.cs index 9bc7f0c..b8d9fd3 100644 --- a/src/DbEx/Migration/MigrationArgsBase.cs +++ b/src/DbEx/Migration/MigrationArgsBase.cs @@ -15,6 +15,8 @@ namespace DbEx.Migration /// public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase { + private readonly List _assemblies = [new MigrationAssemblyArgs(typeof(MigrationArgs).Assembly)]; + /// /// Gets the name. /// @@ -43,12 +45,12 @@ public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase /// /// Gets the list to use to probe for assembly resource (in defined sequence); will automatically add this (DbEx) assembly also (therefore no need to explicitly specify). /// - public List Assemblies { get; } = [typeof(MigrationArgs).Assembly]; + public IEnumerable Assemblies => _assemblies; /// /// Gets the reversed in order for probe-based sequencing. /// - public IEnumerable ProbeAssemblies => Assemblies.Distinct().Reverse(); + public IEnumerable ProbeAssemblies => Assemblies.Reverse(); /// /// Gets the runtime parameters. @@ -93,17 +95,29 @@ public abstract class MigrationArgsBase : OnRamp.CodeGeneratorDbArgsBase /// This is additional to any pre-configured database provider specified . public Func? DataResetFilterPredicate { get; set; } + /// + /// Clears the by removing all existing items. + /// + public void ClearAssemblies() => _assemblies.Clear(); + /// /// Adds one or more to the . /// /// The assemblies to add. /// Where a specified item already exists within the it will not be added again. - public void AddAssembly(params Assembly[] assemblies) + public void AddAssembly(params Assembly[] assemblies) => AddAssembly(assemblies.Select(x => new MigrationAssemblyArgs(x)).ToArray()); + + /// + /// Adds one or more to the . + /// + /// The assemblies to add. + /// Where a specified item already exists within the it will not be added again. + public void AddAssembly(params MigrationAssemblyArgs[] assemblies) { foreach (var assembly in assemblies) { - if (!Assemblies.Contains(assembly)) - Assemblies.Add(assembly); + if (!_assemblies.Any(x => x.Assembly == assembly.Assembly)) + _assemblies.Add(assembly); } } @@ -113,28 +127,36 @@ public void AddAssembly(params Assembly[] assemblies) /// The to find within the existing . /// The assemblies to add /// Where a specified item already exists within the it will not be added again. - public void AddAssemblyAfter(Assembly assemblyToFind, params Assembly[] assemblies) + public void AddAssemblyAfter(Assembly assemblyToFind, params Assembly[] assemblies) => AddAssemblyAfter(assemblyToFind, assemblies.Select(x => new MigrationAssemblyArgs(x)).ToArray()); + + /// + /// Adds one or more to the after the specified ; where not found, will be added to the end. + /// + /// The to find within the existing . + /// The assemblies to add + /// Where a specified item already exists within the it will not be added again. + public void AddAssemblyAfter(Assembly assemblyToFind, params MigrationAssemblyArgs[] assemblies) { - var index = Assemblies.IndexOf(assemblyToFind ?? throw new ArgumentNullException(nameof(assemblyToFind))); + var index = _assemblies.FindIndex(x => x.Assembly == (assemblyToFind ?? throw new ArgumentNullException(nameof(assemblyToFind)))); if (index < 0) { AddAssembly(assemblies); return; } - var newAssemblies = new List(); + var newAssemblies = new List(); foreach (var assembly in assemblies) { - if (!Assemblies.Contains(assembly)) + if (!_assemblies.Any(x => x.Assembly == assembly.Assembly) && !newAssemblies.Any(x => x.Assembly == assembly.Assembly)) newAssemblies.Add(assembly); } - Assemblies.InsertRange(index + 1, newAssemblies); + _assemblies.InsertRange(index + 1, newAssemblies); } /// - /// Adds a parameter to the where it does not already exist; unless is selected then it will add or override. + /// Adds a parameter to the where it does not already exist; unless is selected then it will add or override. /// /// The parameter key. /// The parameter value. @@ -155,8 +177,8 @@ protected void CopyFrom(MigrationArgsBase args) base.CopyFrom(args ?? throw new ArgumentNullException(nameof(args))); MigrationCommand = args.MigrationCommand; - Assemblies.Clear(); - Assemblies.AddRange(args.Assemblies); + _assemblies.Clear(); + _assemblies.AddRange(args.Assemblies); Parameters.Clear(); args.Parameters.ForEach(x => Parameters.Add(x.Key, x.Value)); Logger = args.Logger; diff --git a/src/DbEx/Migration/MigrationArgsBaseT.cs b/src/DbEx/Migration/MigrationArgsBaseT.cs index 9f707b2..f85dfcf 100644 --- a/src/DbEx/Migration/MigrationArgsBaseT.cs +++ b/src/DbEx/Migration/MigrationArgsBaseT.cs @@ -23,6 +23,17 @@ public new TSelf AddAssembly(params Assembly[] assemblies) return (TSelf)this; } + /// + /// Adds one or more to the . + /// + /// The assemblies to add. + /// The current instance to support fluent-style method-chaining. + public new TSelf AddAssembly(params MigrationAssemblyArgs[] assemblies) + { + base.AddAssembly(assemblies); + return (TSelf)this; + } + /// /// Adds one or more (being the underlying ) to . /// @@ -43,17 +54,8 @@ public TSelf AddAssembly(params Type[] types) /// /// Adds the (being the underlying ) to . /// - public TSelf AddAssembly() => AddAssembly(typeof(TAssembly)); - - /// - /// Adds the and (being the underlying ) to . - /// - public TSelf AddAssembly() => AddAssembly(typeof(TAssembly1), typeof(TAssembly2)); - - /// - /// Adds the , and (being the underlying ) to . - /// - public TSelf AddAssembly() => AddAssembly(typeof(TAssembly1), typeof(TAssembly2), typeof(TAssembly3)); + /// The ; defaults to . + public TSelf AddAssembly(params string[] dataNamespaces) => AddAssembly(new MigrationAssemblyArgs(typeof(TAssembly).Assembly, dataNamespaces)); /// /// Adds a parameter to the where it does not already exist; unless is selected then it will add or override. diff --git a/src/DbEx/Migration/MigrationAssemblyArgs.cs b/src/DbEx/Migration/MigrationAssemblyArgs.cs new file mode 100644 index 0000000..7a15f61 --- /dev/null +++ b/src/DbEx/Migration/MigrationAssemblyArgs.cs @@ -0,0 +1,39 @@ +// Copyright (c) Avanade. Licensed under the MIT License. See https://github.com/Avanade/DbEx + +using CoreEx; +using System.Reflection; + +namespace DbEx.Migration +{ + /// + /// Provides arguments. + /// + public class MigrationAssemblyArgs + { + /// + /// Gets or sets the default Data namespace part name. + /// + public static string DefaultDataNamespace { get; set; } = "Data"; + + /// + /// Initializes a new instance of the . + /// + /// The . + /// The ; defaults to . + public MigrationAssemblyArgs(Assembly assembly, params string[] dataNamespaces) + { + Assembly = assembly.ThrowIfNull(nameof(Assembly)); + DataNamespaces = dataNamespaces is null || dataNamespaces.Length == 0 ? [DefaultDataNamespace] : dataNamespaces; + } + + /// + /// Gets the . + /// + public Assembly Assembly { get; } + + /// + /// Gets the Data namespace part name(s). + /// + public string[] DataNamespaces { get; } + } +} \ No newline at end of file diff --git a/tests/DbEx.Test.Console/Program.cs b/tests/DbEx.Test.Console/Program.cs index 78d617a..ba441f8 100644 --- a/tests/DbEx.Test.Console/Program.cs +++ b/tests/DbEx.Test.Console/Program.cs @@ -9,7 +9,7 @@ public class Program .Create("Data Source=.;Initial Catalog=DbEx.Console;Integrated Security=True;TrustServerCertificate=true") .Configure(c => { - c.Args.AddAssembly(typeof(DbEx.Test.OutboxConsole.Program).Assembly); + c.Args.AddAssembly("Data", "Data2"); c.Args.AddSchemaOrder("Test", "Outbox"); c.Args.DataParserArgs.Parameter("DefaultName", "Bazza") .RefDataColumnDefault("SortOrder", i => i)