LowCodeHub.Migration.PostgreSql 0.0.12

dotnet add package LowCodeHub.Migration.PostgreSql --version 0.0.12
                    
NuGet\Install-Package LowCodeHub.Migration.PostgreSql -Version 0.0.12
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LowCodeHub.Migration.PostgreSql" Version="0.0.12" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LowCodeHub.Migration.PostgreSql" Version="0.0.12" />
                    
Directory.Packages.props
<PackageReference Include="LowCodeHub.Migration.PostgreSql" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LowCodeHub.Migration.PostgreSql --version 0.0.12
                    
#r "nuget: LowCodeHub.Migration.PostgreSql, 0.0.12"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LowCodeHub.Migration.PostgreSql@0.0.12
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LowCodeHub.Migration.PostgreSql&version=0.0.12
                    
Install as a Cake Addin
#tool nuget:?package=LowCodeHub.Migration.PostgreSql&version=0.0.12
                    
Install as a Cake Tool

LowCodeHub.Migration.PostgreSql

A 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.

NuGet License: MIT

Why This Library?

Feature LowCodeHub.Migration.PostgreSql EF Core Migrations Raw DbUp
Script format Plain SQL files C# migration classes Plain SQL files
Ordering Unix timestamp prefix Sequential migration IDs Alphabetical
Multiple databases Target per scanner Manual setup Manual setup
Fail-fast Strict naming validation Runtime errors Easy to miss
Always-execute Built-in directory support Manual Manual
Transactions Per-script Per-migration Per-script
DI integration AddPostgreSqlMigrations() + RunPostgreSqlMigrationAsync() Database.Migrate() Manual

Installation

dotnet add package LowCodeHub.Migration.PostgreSql

Quick Start

using LowCodeHub.Migration.PostgreSql.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddPostgreSqlMigrations(migrations =>
{
    migrations.AddTarget<UsersScriptScanner>(options =>
    {
        options.ConnectionString = builder.Configuration.GetConnectionString("UsersDb")!;
        options.Directories = ["MyApp.Users.Migrations"];
    });
});

var app = builder.Build();

await app.RunPostgreSqlMigrationAsync(app.Lifetime.ApplicationStopping);

app.Run();

The scanner type is still required when adding a target because it identifies the assembly that contains embedded SQL resources. You do not pass scanners when running migrations; registered targets are enough.

Multiple PostgreSQL Targets

Register one target per scanner/database pair:

builder.Services.AddPostgreSqlMigrations(migrations =>
{
    migrations.AddTarget<UsersScriptScanner>(options =>
    {
        options.ConnectionString = builder.Configuration.GetConnectionString("UsersDb")!;
        options.Directories = ["MyApp.Users.Migrations"];
        options.AlwaysExecuteDirectories = ["MyApp.Users.Always"];
    });

    migrations.AddTarget<BillingScriptScanner>(options =>
    {
        options.ConnectionString = builder.Configuration.GetConnectionString("BillingDb")!;
        options.Directories = ["MyApp.Billing.Migrations"];
    });
});

await app.RunPostgreSqlMigrationAsync();

Targets run in registration order. Scripts are globally ordered only inside a single target. If two targets point at the same database, each target still uses its own scanner assembly and options, and the runner executes them sequentially.

Target-Based API

This package uses the target-based API only:

builder.Services.AddPostgreSqlMigrations(migrations =>
{
    migrations.AddTarget<MyScriptScanner>(options =>
    {
        options.ConnectionString = "Host=localhost;Database=MyDb;Username=postgres;Password=yourpassword";
        options.Directories = ["MyApp.Migrations.Scripts"];
    });
});

await app.RunPostgreSqlMigrationAsync();

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 is not numeric

Ordering rules inside each target:

  • Primary sort: numeric prefix ascending
  • Secondary sort: full script resource name
  • Directory name: used only for filtering

Target Options

Each target has its own MigrationOptions.

Option Default Description
ConnectionString required PostgreSQL connection string for this target
Directories null Embedded resource directories for journaled scripts
AlwaysExecuteDirectories null Embedded resource directories for always-execute scripts
FailOnNoScriptsFound true Throw if no scripts are found for this target
EnsureDatabaseExists true Create the database if it does not exist. Requires the CREATEDB privilege
UseDistributedLock true Hold a session-scoped pg_advisory_lock for the target migration run

Journaled vs Always-Execute Scripts

Type Directory Config Behavior
Journaled Directories Run once and tracked by DbUp's journal table
Always-execute AlwaysExecuteDirectories Run every startup and not tracked in the journal

Always-execute scripts must be idempotent.

Execution Behavior

Migrations execute synchronously on the calling thread because DbUp has no async API. The Async method returns a completed task and exists for startup-code ergonomics.

For each target:

  1. Ensure the target database exists, unless disabled.
  2. Scan the target scanner assembly for embedded .sql resources.
  3. Split scripts into journaled and always-execute groups.
  4. Order each group by Unix timestamp prefix.
  5. Acquire a PostgreSQL advisory lock.
  6. Execute each script in its own transaction.

If one target fails, migration stops and later targets are not executed.

Cancellation

Pass a CancellationToken to cancel between script executions:

await app.RunPostgreSqlMigrationAsync(app.Lifetime.ApplicationStopping);

A running script completes or rolls back before cancellation takes effect.

Best Practices

  1. Keep script names immutable once merged; renaming breaks the journal.
  2. Use Unix seconds prefixes for predictable ordering.
  3. Keep one concern per script.
  4. Use AlwaysExecuteDirectories only for idempotent scripts.
  5. Keep FailOnNoScriptsFound = true in production.
  6. Embed SQL files as resources:
<EmbeddedResource Include="Scripts\**\*.sql" />

Requirements

  • .NET 10 or later
  • dbup-postgresql 7.2+ (included as a dependency)
  • Npgsql 7.0+ (included as a dependency)

License

MIT (c) Ahmed Abuelnour

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.0.12 556 7/3/2026
0.0.11 134 6/23/2026
0.0.10 116 6/21/2026
0.0.3 699 5/18/2026
0.0.2 118 4/23/2026