Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions SimpleModule.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
</Folder>
<Folder Name="/tools/">
<Project Path="tools/SimpleModule.DevTools/SimpleModule.DevTools.csproj" />
<Project Path="tools/SimpleModule.PerfSeeder/SimpleModule.PerfSeeder.csproj" />
</Folder>
<Folder Name="/modules/" />
<Folder Name="/modules/Dashboard/">
Expand Down
127 changes: 127 additions & 0 deletions cli/SimpleModule.Cli/Commands/Seed/SeedCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
using System.Diagnostics;
using SimpleModule.Cli.Infrastructure;
using Spectre.Console;
using Spectre.Console.Cli;

namespace SimpleModule.Cli.Commands.Seed;

public sealed class SeedCommand : Command<SeedSettings>
{
public override int Execute(CommandContext context, SeedSettings settings)
{
var solution = SolutionContext.Discover();
if (solution is null)
{
AnsiConsole.MarkupLine(
"[red]Could not find .slnx file. Run this command from within a SimpleModule project.[/]"
);
return 1;
}

var seederProject = Path.Combine(
solution.RootPath,
"tools",
"SimpleModule.PerfSeeder",
"SimpleModule.PerfSeeder.csproj"
);
if (!File.Exists(seederProject))
{
AnsiConsole.MarkupLine(
$"[red]Perf seeder project not found at {seederProject.EscapeMarkup()}[/]"
);
return 1;
}

var args = BuildForwardArgs(settings, solution);

AnsiConsole.MarkupLine(
"[bold blue]Running perf seeder[/] (this may take a while for large counts)..."
);
AnsiConsole.MarkupLine(
$"[dim] dotnet run -c Release --project {seederProject.EscapeMarkup()} -- {string.Join(' ', args).EscapeMarkup()}[/]"
);

var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
WorkingDirectory = solution.RootPath,
UseShellExecute = false,
};
startInfo.ArgumentList.Add("run");
startInfo.ArgumentList.Add("-c");
startInfo.ArgumentList.Add("Release");
startInfo.ArgumentList.Add("--project");
startInfo.ArgumentList.Add(seederProject);
startInfo.ArgumentList.Add("--");
foreach (var arg in args)
{
startInfo.ArgumentList.Add(arg);
}

try
{
using var process = Process.Start(startInfo);
if (process is null)
{
AnsiConsole.MarkupLine("[red]Failed to start dotnet process.[/]");
return 1;
}
process.WaitForExit();
return process.ExitCode;
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
#pragma warning restore CA1031
{
AnsiConsole.MarkupLine($"[red]Seeder failed: {ex.Message.EscapeMarkup()}[/]");
return 1;
}
}

private static List<string> BuildForwardArgs(SeedSettings settings, SolutionContext solution)
{
var args = new List<string>
{
"--module",
settings.Module,
"--batch-size",
settings.BatchSize.ToString(System.Globalization.CultureInfo.InvariantCulture),
"--seed",
settings.RandomSeed.ToString(System.Globalization.CultureInfo.InvariantCulture),
};

if (settings.Count is { } count)
{
args.Add("--count");
args.Add(count.ToString(System.Globalization.CultureInfo.InvariantCulture));
}
if (!string.IsNullOrWhiteSpace(settings.Connection))
{
args.Add("--connection");
args.Add(settings.Connection);
}
if (!string.IsNullOrWhiteSpace(settings.Provider))
{
args.Add("--provider");
args.Add(settings.Provider);
}
if (settings.Truncate)
{
args.Add("--truncate");
}
if (settings.CreateSchema)
{
args.Add("--create-schema");
}

// Always pass the host project so the seeder reads the right appsettings.json.
var hostDir = Path.GetDirectoryName(solution.ApiCsprojPath);
if (!string.IsNullOrWhiteSpace(hostDir))
{
args.Add("--project");
args.Add(hostDir);
}

return args;
}
}
50 changes: 50 additions & 0 deletions cli/SimpleModule.Cli/Commands/Seed/SeedSettings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System.ComponentModel;
using Spectre.Console.Cli;

namespace SimpleModule.Cli.Commands.Seed;

public sealed class SeedSettings : CommandSettings
{
[CommandOption("-m|--module <MODULE>")]
[Description("Target module: products, orders, or all. Default: all.")]
public string Module { get; set; } = "all";

[CommandOption("-c|--count <COUNT>")]
[Description(
"Row count override applied to each target module. "
+ "Defaults: products=1,000,000, orders=100,000."
)]
public int? Count { get; set; }

[CommandOption("--connection <CONNSTR>")]
[Description(
"Override the database connection string. By default reads Database:DefaultConnection from the host's appsettings.json."
)]
public string? Connection { get; set; }

[CommandOption("--provider <PROVIDER>")]
[Description(
"Override the database provider (Sqlite|PostgreSql|SqlServer). By default auto-detected."
)]
public string? Provider { get; set; }

[CommandOption("--batch-size <SIZE>")]
[Description("Rows per SaveChanges batch. Default: 5000.")]
public int BatchSize { get; set; } = 5000;

[CommandOption("--seed <SEED>")]
[Description("Randomizer seed for deterministic data generation. Default: 42.")]
public int RandomSeed { get; set; } = 42;

[CommandOption("--truncate")]
[Description(
"Delete existing rows in the target tables before seeding. Products keeps rows with Id <= 10 (migration seed)."
)]
public bool Truncate { get; set; }

[CommandOption("--create-schema")]
[Description(
"Call EnsureCreated on each target DbContext before seeding. Useful against a fresh database when migrations are unavailable."
)]
public bool CreateSchema { get; set; }
}
7 changes: 7 additions & 0 deletions cli/SimpleModule.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using SimpleModule.Cli.Commands.Doctor;
using SimpleModule.Cli.Commands.Install;
using SimpleModule.Cli.Commands.New;
using SimpleModule.Cli.Commands.Seed;
using Spectre.Console.Cli;

var app = new CommandApp();
Expand Down Expand Up @@ -43,6 +44,12 @@
config
.AddCommand<DoctorCommand>("doctor")
.WithDescription("Validate project structure and conventions");

config
.AddCommand<SeedCommand>("seed")
.WithDescription(
"Seed the configured database with bulk test data for perf testing (Products, Orders). AuditLogs are populated automatically by the audit interceptor on real operations."
);
});

return app.Run(args);
10 changes: 10 additions & 0 deletions tools/SimpleModule.PerfSeeder/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using SimpleModule.PerfSeeder;
using Spectre.Console.Cli;

var app = new CommandApp<SeedCommand>();
app.Configure(config =>
{
config.SetApplicationName("SimpleModule.PerfSeeder");
config.PropagateExceptions();
});
return await app.RunAsync(args).ConfigureAwait(false);
Loading