LowCodeHub.Migration.PostgreSql
0.0.10
See the version list below for details.
dotnet add package LowCodeHub.Migration.PostgreSql --version 0.0.10
NuGet\Install-Package LowCodeHub.Migration.PostgreSql -Version 0.0.10
<PackageReference Include="LowCodeHub.Migration.PostgreSql" Version="0.0.10" />
<PackageVersion Include="LowCodeHub.Migration.PostgreSql" Version="0.0.10" />
<PackageReference Include="LowCodeHub.Migration.PostgreSql" />
paket add LowCodeHub.Migration.PostgreSql --version 0.0.10
#r "nuget: LowCodeHub.Migration.PostgreSql, 0.0.10"
#:package LowCodeHub.Migration.PostgreSql@0.0.10
#addin nuget:?package=LowCodeHub.Migration.PostgreSql&version=0.0.10
#tool nuget:?package=LowCodeHub.Migration.PostgreSql&version=0.0.10
LowCodeHub.Migration.PostgreSql
A simple startup migration runner for PostgreSQL using embedded .sql scripts and deterministic execution order. Scripts are ordered by Unix timestamp prefix, executed in individual transactions, and journaled by DbUp — no EF Core migrations, no code-first overhead.
Why This Library?
| Feature | LowCodeHub.Migration | EF Core Migrations | Raw DbUp |
|---|---|---|---|
| Script format | Plain SQL files | C# migration classes | Plain SQL files |
| Ordering | Unix timestamp prefix (deterministic) | Sequential migration IDs | Alphabetical |
| Fail-fast | Strict naming validation | Runtime errors | Silent |
| Always-execute | Built-in directory support | Manual | Manual NamingConvention |
| Transactions | Per-script (auto rollback) | Per-migration (configurable) | Per-script |
| DI integration | AddMigration() + RunDatabaseMigrationAsync() |
Database.Migrate() |
Manual |
| Cancellation | Between scripts | Not supported | Not supported |
Installation
dotnet add package LowCodeHub.Migration.PostgreSql
Quick Start
using LowCodeHub.Migration.PostgreSql.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMigration(builder.Configuration, sectionName: "Migration");
var app = builder.Build();
await app.RunDatabaseMigrationAsync<Program>();
app.Run();
{
"Migration": {
"ConnectionString": "Host=localhost;Database=MyDb;Username=postgres;Password=yourpassword",
"Directories": ["MyApp.Migrations.Scripts"],
"FailOnNoScriptsFound": true
}
}
That's it. On startup, the library scans the assembly for embedded .sql resources in the specified directories, orders them by Unix timestamp prefix, and executes each one in its own transaction — skipping already-applied scripts.
Table of Contents
- Script Naming Convention
- Multiple Script Assemblies
- Configuration
- Journaled vs Always-Execute Scripts
- Execution Behavior
- Cancellation
- How It Works
- Best Practices
- Requirements
- License
Multiple Script Assemblies
For one script assembly, keep using the generic scanner overload:
await app.RunDatabaseMigrationAsync<Program>();
For scripts split across several assemblies, pass marker types or assemblies explicitly:
await app.RunDatabaseMigrationAsync(
new[] { typeof(CoreMigrations), typeof(BillingMigrations) });
await app.RunDatabaseMigrationAsync(
new[] { typeof(CoreMigrations).Assembly, typeof(BillingMigrations).Assembly },
app.Lifetime.ApplicationStopping);
Scripts from all assemblies are merged and ordered by numeric prefix before execution.
Script Naming Convention
Scripts must start with a numeric Unix timestamp prefix:
| Script Name | Valid? |
|---|---|
1766733156_create_users.sql |
Yes |
1766733160_add_index.sql |
Yes |
create_users.sql |
No — missing prefix |
v1_create_users.sql |
No — prefix not numeric |
If naming is invalid, the migration throws with a clear format error at startup — fail-fast, not silent corruption.
Ordering Rules
- Primary sort — numeric prefix (ascending)
- Secondary sort — script file name (for duplicate prefixes)
- Folder name — used only for filtering (which scripts to include), not for ordering
Configuration
appsettings.json
{
"Migration": {
"ConnectionString": "Host=localhost;Database=MyDb;Username=postgres;Password=yourpassword",
"Directories": ["MyApp.Migrations.Scripts"],
"AlwaysExecuteDirectories": ["MyApp.Migrations.Always"],
"FailOnNoScriptsFound": true
}
}
All Options
| Option | Default | Description |
|---|---|---|
ConnectionString |
required | PostgreSQL connection string |
Directories |
null (full assembly scan) |
Embedded resource directories for journaled scripts |
AlwaysExecuteDirectories |
null |
Embedded resource directories for always-execute scripts |
FailOnNoScriptsFound |
true |
Throw if no scripts are found in the specified directories |
EnsureDatabaseExists |
true |
Create the database if it does not exist before migrating. Requires the CREATEDB privilege — set to false when the migration principal lacks it |
UseDistributedLock |
true |
Hold a session-scoped pg_advisory_lock for the whole migration run so concurrent app instances do not run the same migrations simultaneously. Set to false only when a single migrator is already guaranteed |
Code-Based Configuration
builder.Services.AddMigration(options =>
{
options.ConnectionString = "Host=localhost;Database=MyDb;Username=postgres;Password=yourpassword";
options.Directories = ["MyApp.Migrations.Scripts"];
options.AlwaysExecuteDirectories = ["MyApp.Migrations.Always"];
options.FailOnNoScriptsFound = true;
});
Journaled vs Always-Execute Scripts
| Type | Directory Config | Behavior |
|---|---|---|
| Journaled | Directories |
Run once — tracked by DbUp's journal table. Skipped on subsequent startups. |
| Always-Execute | AlwaysExecuteDirectories |
Run every startup — not tracked in the journal. |
Always-execute scripts are useful for:
- Permission grants
- View / stored procedure refresh scripts
- Maintenance scripts that must re-apply every deployment
Always-execute scripts must be idempotent — they run on every startup.
Execution Behavior
Migrations execute synchronously on the calling thread — DbUp has no async API. The
Asyncmethods return an already-completed task and exist for API symmetry with startup code.
Per-Script Transactions
Each script runs in its own transaction:
- Success — transaction is committed, script is journaled
- Failure — transaction is rolled back, migration stops immediately, subsequent scripts are not executed
Failure Behavior
Script 1: ✓ committed + journaled
Script 2: ✓ committed + journaled
Script 3: ✗ rolled back → migration stops
Script 4: ⊘ not executed
Script 5: ⊘ not executed
On next startup, Script 3 runs again (it was rolled back and not journaled).
Cancellation
Pass a CancellationToken to cancel between script executions:
await app.RunDatabaseMigrationAsync<Program>(app.Lifetime.ApplicationStopping);
Cancellation is checked between scripts — a running script completes (or rolls back on error) before cancellation takes effect.
How It Works
┌─────────────────────────────────────────────────────────┐
│ RunDatabaseMigrationAsync<Program>() │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ IMigrationService.MigrateAsync(assemblies, ct) │
│ 1. Scan assembly for embedded .sql resources │
│ 2. Filter by Directories / AlwaysExecuteDirectories │
│ 3. Order by Unix timestamp prefix │
│ 4. Acquire DB-level migration lock (pg_advisory_lock) │
└─────────────────────┬───────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ DbUp Engine │
│ For each script: │
│ ├── Check journal (skip if already applied) │
│ ├── BEGIN TRANSACTION │
│ ├── Execute SQL │
│ ├── COMMIT + journal entry │
│ └── On error: ROLLBACK + stop │
└─────────────────────────────────────────────────────────┘
- Assembly scanning — The
<TScriptScanner>type parameter identifies one assembly; pass scanner types or assemblies to scan several. - Filtering — Only resources matching the configured directories are included.
- Naming validation — Every script must have a valid numeric prefix. Invalid names cause immediate failure.
- Execution — DbUp runs each script in its own transaction, journaling successful scripts.
Best Practices
- Keep script names immutable once merged — renaming breaks the journal.
- Use Unix seconds prefix for predictable, conflict-free ordering.
- Keep one concern per script (small and reversible where possible).
- Use
AlwaysExecuteDirectoriesonly for idempotent scripts. - Keep
FailOnNoScriptsFound = truein production. - Embed SQL files as resources (
<EmbeddedResource Include="Scripts\**\*.sql" />).
Requirements
- .NET 10 or later
dbup-postgresql7.2+ (included as a dependency)Npgsql7.0+ (included as a dependency)
License
MIT © Ahmed Abuelnour
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- dbup-postgresql (>= 7.0.1)
- Npgsql (>= 10.0.3)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.