Skip to content
Merged
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
28 changes: 25 additions & 3 deletions scripts/migrations.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ param(
[Parameter(Position = 2)]
[string] $Argument,

[Parameter(Position = 3)]
[string] $ToMigration,

[switch] $Force,

[Alias("h")]
Expand All @@ -33,17 +36,20 @@ Usage:
./scripts/migrations.ps1 remove [provider|all] [-Force]
./scripts/migrations.ps1 list [provider|all]
./scripts/migrations.ps1 check [provider|all]
./scripts/migrations.ps1 script <provider|all> [from-migration] [to-migration]

Providers:
sqlite, postgresql (or postgres), mysql, sqlserver (or mssql), all

Environment:
SPINDLE_EF_FRAMEWORK Target framework used by dotnet ef (default: net10.0)
SPINDLE_EF_SCHEMA Optional schema for explicit PostgreSQL or SQL Server script validation

Examples:
./scripts/migrations.ps1 add all AddFlowPriority
./scripts/migrations.ps1 check postgresql
./scripts/migrations.ps1 remove sqlite
./scripts/migrations.ps1 script postgresql 0
"@
}

Expand Down Expand Up @@ -72,7 +78,8 @@ function Invoke-ProviderCommand {
param(
[string] $ProviderName,
[string] $MigrationCommand,
[string] $CommandArgument
[string] $CommandArgument,
[string] $TargetMigration
)

$Project = "src/Spindle.Persistence.EFCore.$ProviderName/Spindle.Persistence.EFCore.$ProviderName.csproj"
Expand Down Expand Up @@ -105,6 +112,17 @@ function Invoke-ProviderCommand {
"check" {
Invoke-DotNetEf -Arguments (@("migrations", "has-pending-model-changes") + $CommonArguments)
}
"script" {
$EfArguments = @("migrations", "script")
if ($CommandArgument) {
$EfArguments += $CommandArgument
}
if ($TargetMigration) {
$EfArguments += $TargetMigration
}

Invoke-DotNetEf -Arguments ($EfArguments + $CommonArguments + @("--", "--spindle-ef-script"))
}
}
}

Expand All @@ -130,10 +148,14 @@ switch ($Command) {
throw "The remove command does not accept a positional argument; use -Force if needed."
}
}
{ $_ -in "list", "check" } { }
{ $_ -in "list", "check", "script" } { }
default { throw "Unknown command: $Command" }
}

if ($Command -ne "script" -and $ToMigration) {
throw "The $Command command accepts at most one argument."
}

if ($Force -and $Command -ne "remove") {
throw "-Force can only be used with the remove command."
}
Expand All @@ -149,7 +171,7 @@ try {

foreach ($ProviderName in $Providers) {
$CommandArgument = if ($Force) { "--force" } else { $Argument }
Invoke-ProviderCommand $ProviderName $Command $CommandArgument
Invoke-ProviderCommand $ProviderName $Command $CommandArgument $ToMigration
}
}
finally {
Expand Down
39 changes: 35 additions & 4 deletions scripts/migrations.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@ Usage:
migrations.sh remove [provider|all] [--force]
migrations.sh list [provider|all]
migrations.sh check [provider|all]
migrations.sh script <provider|all> [from-migration] [to-migration]

Providers:
sqlite, postgresql (or postgres), mysql, sqlserver (or mssql), all

Environment:
SPINDLE_EF_FRAMEWORK Target framework used by dotnet ef (default: net10.0)
SPINDLE_EF_SCHEMA Optional schema for explicit PostgreSQL or SQL Server script validation

Examples:
./scripts/migrations.sh add all AddFlowPriority
./scripts/migrations.sh check postgresql
./scripts/migrations.sh remove sqlite
./scripts/migrations.sh script postgresql 0
EOF
}

Expand All @@ -47,9 +50,14 @@ run_for_provider() {
local provider="$1"
local command="$2"
local argument="${3:-}"
local second_argument="${4:-}"
local project="src/Spindle.Persistence.EFCore.${provider}/Spindle.Persistence.EFCore.${provider}.csproj"

printf '\n==> %s: %s\n' "$provider" "$command"
if [[ "$command" == "script" ]]; then
printf '\n==> %s: %s\n' "$provider" "$command" >&2
else
printf '\n==> %s: %s\n' "$provider" "$command"
fi

case "$command" in
add)
Expand Down Expand Up @@ -83,12 +91,29 @@ run_for_provider() {
--startup-project "$project" \
--framework "$framework"
;;
script)
local script_arguments=(migrations script)
if [[ -n "$argument" ]]; then
script_arguments+=("$argument")
fi
if [[ -n "$second_argument" ]]; then
script_arguments+=("$second_argument")
fi

dotnet ef "${script_arguments[@]}" \
--project "$project" \
--startup-project "$project" \
--framework "$framework" \
-- \
--spindle-ef-script
;;
esac
}

command="${1:-help}"
requested_provider="${2:-all}"
argument="${3:-}"
second_argument="${4:-}"

case "$command" in
help|-h|--help)
Expand All @@ -109,20 +134,26 @@ case "$command" in
exit 2
fi
;;
list|check) ;;
list|check|script) ;;
*)
echo "Unknown command: $command" >&2
usage >&2
exit 2
;;
esac

if [[ "$command" != "script" && -n "$second_argument" ]]; then
echo "The $command command accepts at most one argument." >&2
usage >&2
exit 2
fi

cd "$repository_root"

if [[ "${requested_provider,,}" == "all" ]]; then
for provider in Sqlite PostgreSQL MySql SqlServer; do
run_for_provider "$provider" "$command" "$argument"
run_for_provider "$provider" "$command" "$argument" "$second_argument"
done
else
run_for_provider "$(normalize_provider "$requested_provider")" "$command" "$argument"
run_for_provider "$(normalize_provider "$requested_provider")" "$command" "$argument" "$second_argument"
fi
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,12 @@ public SpindleDbContext CreateDbContext(string[] args)
{
var options = new DbContextOptionsBuilder<SpindleDbContext>()
.UseMySQL("Server=localhost;Database=spindle;User=root;Password=spindle", mysql =>
mysql.MigrationsAssembly(typeof(MySqlDesignTimeDbContextFactory).Assembly.FullName))
mysql
.MigrationsAssembly(typeof(MySqlDesignTimeDbContextFactory).Assembly.FullName)
.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromMilliseconds(100),
errorNumbersToAdd: null))
.Options;

return new SpindleDbContext(options);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,28 @@ public static class SpindleMySqlServiceCollectionExtensions
public static IServiceCollection AddSpindleMySql(
this IServiceCollection services,
string connectionString)
{
return AddSpindleMySql(services, connectionString, configure: null);
}

public static IServiceCollection AddSpindleMySql(
this IServiceCollection services,
string connectionString,
Action<DbContextOptionsBuilder>? configure)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);

return services.AddSpindleEntityFramework(options =>
{
options.UseMySQL(
connectionString,
mysql => mysql.MigrationsAssembly(typeof(SpindleMySqlServiceCollectionExtensions).Assembly.FullName)));
mysql => mysql
.MigrationsAssembly(typeof(SpindleMySqlServiceCollectionExtensions).Assembly.FullName)
.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromMilliseconds(100),
errorNumbersToAdd: null));
configure?.Invoke(options);
});
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Spindle.Persistence.EFCore;

namespace Spindle.Persistence.EFCore.PostgreSQL;
Expand All @@ -9,9 +11,19 @@ internal sealed class PostgreSqlDesignTimeDbContextFactory
{
public SpindleDbContext CreateDbContext(string[] args)
{
var schema = SpindleDesignTimeSchema.Get(args);

var options = new DbContextOptionsBuilder<SpindleDbContext>()
.UseNpgsql("Host=localhost;Database=spindle;Username=postgres;Password=postgres", postgres =>
postgres.MigrationsAssembly(typeof(PostgreSqlDesignTimeDbContextFactory).Assembly.FullName))
postgres
.MigrationsAssembly(typeof(PostgreSqlDesignTimeDbContextFactory).Assembly.FullName)
.MigrationsHistoryTable("__EFMigrationsHistory", schema)
.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromMilliseconds(100),
errorCodesToAdd: null))
.ReplaceService<IModelCacheKeyFactory, SpindleModelCacheKeyFactory>()
.ReplaceService<IMigrationsSqlGenerator, SpindlePostgreSqlMigrationsSqlGenerator>()
.Options;

return new SpindleDbContext(options);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure.Internal;
using Npgsql.EntityFrameworkCore.PostgreSQL.Migrations;
using Spindle.Persistence.EFCore;

#pragma warning disable EF1001

namespace Spindle.Persistence.EFCore.PostgreSQL;

internal sealed class SpindlePostgreSqlMigrationsSqlGenerator(
MigrationsSqlGeneratorDependencies dependencies,
INpgsqlSingletonOptions npgsqlSingletonOptions,
ICurrentDbContext currentContext)
: NpgsqlMigrationsSqlGenerator(dependencies, npgsqlSingletonOptions)
{
public override IReadOnlyList<MigrationCommand> Generate(
IReadOnlyList<MigrationOperation> operations,
IModel? model,
MigrationsSqlGenerationOptions options)
{
var schema = currentContext.Context is SpindleDbContext spindleContext
? spindleContext.Schema
: null;
return base.Generate(
SchemaAwareMigrationOperations.Prepare(operations, model, schema),
model,
options);
}

protected override void Generate(
EnsureSchemaOperation operation,
IModel? model,
MigrationCommandListBuilder builder)
{
builder
.Append("CREATE SCHEMA IF NOT EXISTS ")
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(operation.Name))
.Append(Dependencies.SqlGenerationHelper.StatementTerminator)
.EndCommand();
}
}

#pragma warning restore EF1001
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore;

Expand All @@ -9,12 +10,56 @@ public static class SpindlePostgreSqlServiceCollectionExtensions
public static IServiceCollection AddSpindlePostgreSql(
this IServiceCollection services,
string connectionString)
{
return AddSpindlePostgreSql(services, connectionString, schema: null, configure: null);
}

public static IServiceCollection AddSpindlePostgreSql(
this IServiceCollection services,
string connectionString,
Action<DbContextOptionsBuilder>? configure)
{
return AddSpindlePostgreSql(services, connectionString, schema: null, configure);
}

public static IServiceCollection AddSpindlePostgreSql(
this IServiceCollection services,
string connectionString,
string? schema)
{
return AddSpindlePostgreSql(services, connectionString, schema, configure: null);
}

public static IServiceCollection AddSpindlePostgreSql(
this IServiceCollection services,
string connectionString,
string? schema,
Action<DbContextOptionsBuilder>? configure)
{
ArgumentException.ThrowIfNullOrWhiteSpace(connectionString);
ValidateSchema(schema);

return services.AddSpindleEntityFramework(options =>
{
options.UseNpgsql(
connectionString,
postgres => postgres.MigrationsAssembly(typeof(SpindlePostgreSqlServiceCollectionExtensions).Assembly.FullName)));
postgres => postgres
.MigrationsAssembly(typeof(SpindlePostgreSqlServiceCollectionExtensions).Assembly.FullName)
.MigrationsHistoryTable("__EFMigrationsHistory", schema)
.EnableRetryOnFailure(
maxRetryCount: 5,
maxRetryDelay: TimeSpan.FromMilliseconds(100),
errorCodesToAdd: null));
options.ReplaceService<IMigrationsSqlGenerator, SpindlePostgreSqlMigrationsSqlGenerator>();
configure?.Invoke(options);
});
}

private static void ValidateSchema(string? schema)
{
if (schema is not null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(schema);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Migrations.Operations;
using Microsoft.EntityFrameworkCore.Update;
using Spindle.Persistence.EFCore;

namespace Spindle.Persistence.EFCore.SqlServer;

internal sealed class SpindleSqlServerMigrationsSqlGenerator(
MigrationsSqlGeneratorDependencies dependencies,
ICommandBatchPreparer commandBatchPreparer,
ICurrentDbContext currentContext)
: SqlServerMigrationsSqlGenerator(dependencies, commandBatchPreparer)
{
public override IReadOnlyList<MigrationCommand> Generate(
IReadOnlyList<MigrationOperation> operations,
IModel? model,
MigrationsSqlGenerationOptions options)
{
var schema = currentContext.Context is SpindleDbContext spindleContext
? spindleContext.Schema
: null;
return base.Generate(
SchemaAwareMigrationOperations.Prepare(operations, model, schema),
model,
options);
}
}
Loading
Loading