Fluens.Migrations
0.8.5
dotnet add package Fluens.Migrations --version 0.8.5
NuGet\Install-Package Fluens.Migrations -Version 0.8.5
<PackageReference Include="Fluens.Migrations" Version="0.8.5" />
<PackageVersion Include="Fluens.Migrations" Version="0.8.5" />
<PackageReference Include="Fluens.Migrations" />
paket add Fluens.Migrations --version 0.8.5
#r "nuget: Fluens.Migrations, 0.8.5"
#:package Fluens.Migrations@0.8.5
#addin nuget:?package=Fluens.Migrations&version=0.8.5
#tool nuget:?package=Fluens.Migrations&version=0.8.5
Fluens.Migrations
Provider-neutral FluentMigrator base classes and helpers for inbox/outbox tables, dead letters, saga tables, HiLo sequences, and auditable/deletable columns. Zero Fluens dependencies.
Installation
dotnet add package Fluens.Migrations
# Plus exactly one provider package:
dotnet add package Fluens.Migrations.PostgreSql
# or
dotnet add package Fluens.Migrations.SqlServer
Usage
Runner registration
The provider is opted into from the AddFluens callback on FluentMigrator's own ConfigureRunner pipeline —
never through a bespoke top-level method. rb.AddPostgres() / rb.AddSqlServer() (FluentMigrator's own
provider registration) and .AddFluens(f => f.AddPostgres()) / .AddFluens(f => f.AddSqlServer()) (the
Fluens conventions opt-in) are distinct extension targets — no ambiguity:
services.AddFluentMigratorCore()
.ConfigureRunner(rb => rb
.AddPostgres() // FluentMigrator's own provider registration
.WithGlobalConnectionString(connectionString)
.ScanIn(typeof(CreateOrdersSchema).Assembly).For.Migrations()
.AddFluens(f => f.AddPostgres())); // Fluens provider-conventions opt-in
Migration classes
Every FluensMigration subclass needs a forwarding constructor — Conventions is resolved by FluentMigrator's
own constructor-injection support when the runner instantiates the migration, so a single, unchanged
subclass creates the same schema on both providers:
public class CreateOrdersSchema : FluensMigration
{
public CreateOrdersSchema(IMigrationProviderConventions conventions) : base(conventions) { }
public override void Up()
{
Create.Schema("orders");
Create.Table("orders").InSchema("orders")
.WithColumn("id").AsGuid().PrimaryKey()
.WithColumn("number").AsString(50).NotNullable()
.WithColumn("total").AsDecimal().NotNullable()
.WithAuditableAndDeletableColumns()
.WithVersionColumn();
CreateMessagingTables("orders"); // outbox + inbox + dead_letters
CreateHiLoSequence("orders_hilo", "orders");
}
public override void Down()
{
DeleteMessagingTables("orders");
DeleteHiLoSequence("orders_hilo", "orders");
Delete.Table("orders").InSchema("orders");
}
}
Helpers: WithAuditableColumns(), WithDeletableColumn(), WithAuditableAndDeletableColumns(), WithVersionColumn(),
CreateOutboxTable(), CreateOutboxDeliveryTable(), CreateInboxTable(), CreateIngressOutboxTable(), CreateDeadLettersTable(),
CreateSagaTable(), CreateMessagingTables(), CreateHiLoSequence(), DeleteOutboxTable(), DeleteOutboxDeliveryTable(),
DeleteInboxTable(), DeleteIngressOutboxTable(), DeleteDeadLettersTable(), DeleteSagaTable(), DeleteMessagingTables(),
DeleteHiLoSequence().
Messaging schema (ADR 20260530000001 / ADR 20260530000002 / ADR 20260729000001)
All four messaging tables carry the serialized envelope blob (non-nullable; ADR 20260530000001 — replaces the
bare context column) plus the denormalized routing columns (partition_key, priority, and on outbox
also available_at) so the partial pending indexes can sort / filter on real columns. Each table has a
partial pending index emitted via Conventions.ApplyFilter(<index>.WithOptions(), "<predicate>") — the
registered provider package's .Filter(...) extension — so the hot fetch scans only in-flight rows:
outbox— the terminal marker issent_at(renamed from the legacypublished_atflag; ADR 20260530000002). Message-TTL columnsexpires_at(non-nullable, computed from(available_at ?? created_at)at write) andexpired_at(nullable, set in place on TTL expiry — the row is never deleted, only marked). Also carriesmint_seq(bigint, non-nullable, default0— a provider-neutral monotonic mint-time key derived fromid's embedded GUIDv7 timestamp; ADR 20260729000001). Partialix_outbox_pending(WHERE sent_at IS NULL) over(sent_at, priority DESC, mint_seq).inbox— composite primary key(message_id, handler_type)(one row per subscribing handler). Also carriesmint_seq(same shape asoutbox, derived frommessage_id) and addsnext_retry_at(stage-2 retry schedule). Partialix_inbox_pending(WHERE processed_at IS NULL) over(handler_type, partition_key, priority DESC, mint_seq).ingress_outbox— primary keymessage_id(boundary dedup). Partialix_ingress_pending(WHERE processed_at IS NULL) overreceived_at.dead_letters— adds the owningmodule_name(string(100), NOT NULL, no default), the why-columnshandler_type,partition_key,failure_code,exception_type,attempt_history(Conventions.JsonColumnType—jsonbon PostgreSQL,nvarchar(max)on SQL Server), and the replay markerreplayed_at. Two module-leading indexes serve the per-module operator queries (query, replay, housekeeping):ix_dead_letters_module_failed_aton (module_nameASC,failed_atDESC) andix_dead_letters_module_replayed_aton (module_nameASC,replayed_atASC);DeleteDeadLettersTabledrops both, then the table.
No upgrade (AddColumn) migration ships for mint_seq — CreateOutboxTable/CreateInboxTable are
create-table helpers only, so a consumer upgrading an already-deployed schema authors their own
AddColumn("mint_seq").AsInt64().NotNullable().WithDefaultValue(0) step plus the corresponding index
rebuild (ADR 20260729000001).
dead_letters.module_name is the deliberate exception to "an added column carries a default": it is required,
has no default and has no backward compatibility, because every dead letter must record the module that owns
it. A consumer upgrading an already-deployed dead_letters table adds the column and the two indexes in its own
migration (ADR 20261006184926).
Saga table (ADR 20261006184927)
CreateSagaTable(tableName, schema) creates the SagaData base schema for a Fluens.Messaging.Sagas saga and
returns the table syntax, so you chain your business columns straight on. The base columns are id (Guid,
primary key), created_at (NOT NULL), completed_at (nullable) and version, whose type comes from
Conventions.SagaVersionColumnType because the column type EF Core uses for the uint concurrency token differs
per provider (bigint on both PostgreSQL and SQL Server). Three indexes back the SQL-side saga operator queries:
ix_<tableName>_active—created_at DESC, partialWHERE completed_at IS NULL(viaConventions.ApplyFilter)ix_<tableName>_created_at—created_at DESCix_<tableName>_completed_at—completed_at ASC
public override void Up()
{
CreateSagaTable("order_fulfillment", "orders")
.WithColumn("order_id").AsGuid().NotNullable()
.WithColumn("customer_email").AsString(200).NotNullable()
.WithColumn("payment_captured").AsBoolean().NotNullable();
}
public override void Down() => DeleteSagaTable("order_fulfillment", "orders");
DeleteSagaTable drops the three indexes, then the table. The indexes come from this helper only — the EF model of
a consumer's saga table declares none.
CreateOutboxDeliveryTable(schema) creates the per-destination delivery table (one row per
(outbox_id, destination_app) pair) with a unique ix_outbox_delivery_outbox_dest index on
(outbox_id, destination_app) for re-fan-out idempotency, an ix_outbox_delivery_destination_app index, and
the partial ix_delivery_pending (WHERE delivered_at IS NULL AND expired_at IS NULL) over
(expires_at, destination_app); CreateMessagingTables creates it alongside outbox/inbox in the module
schema. CreateIngressOutboxTable(schema = "shared") creates the single staging table into which a
peer-delivered batch is written atomically before acking.
Provider-specific column types
Use Fluens.Migrations.PostgreSql or Fluens.Migrations.SqlServer for provider-specific column types —
independent of which IMigrationProviderConventions is registered for the run:
using Fluens.Migrations.PostgreSql; // AsCitext(), AsTimestampTz()
Create.Table("customers").InSchema("orders")
.WithColumn("id").AsInt32().PrimaryKey().Identity()
.WithColumn("email").AsCitext().NotNullable() // case-insensitive text
.WithColumn("created_at").AsTimestampTz().NotNullable(); // timestamp with time zone
using Fluens.Migrations.SqlServer; // AsNVarCharMax(), AsDateTimeOffset2()
Create.Table("customers").InSchema("orders")
.WithColumn("id").AsInt32().PrimaryKey().Identity()
.WithColumn("notes").AsNVarCharMax().Nullable() // unbounded Unicode text
.WithColumn("created_at").AsDateTimeOffset2().NotNullable(); // timestamp with time zone
Helpers: Fluens.Migrations.PostgreSql.AsCitext() / .AsTimestampTz(); Fluens.Migrations.SqlServer.AsNVarCharMax() / .AsDateTimeOffset2().
License
This project is licensed under the MIT License.
| 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
- FluentMigrator (>= 8.0.1)
- FluentMigrator.Runner.Core (>= 8.0.1)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on Fluens.Migrations:
| Package | Downloads |
|---|---|
|
Fluens.Migrations.PostgreSql
PostgreSQL provider for Fluens.Migrations — jsonb attempt_history column type, citext/timestamptz column helpers, and FluentMigrator.Postgres partial-index filters. Opt in via rb.AddFluens(f => f.AddPostgres()). |
|
|
Fluens.Migrations.SqlServer
SQL Server provider for Fluens.Migrations — nvarchar(max) attempt_history column type, datetimeoffset column helper, and FluentMigrator.SqlServer partial-index filters. Opt in via rb.AddFluens(f => f.AddSqlServer()). |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.8.5 | 68 | 10/7/2026 |
| 0.8.4 | 76 | 10/6/2026 |
| 0.8.3 | 77 | 10/6/2026 |
| 0.8.2 | 106 | 10/5/2026 |
| 0.8.1 | 172 | 9/15/2026 |
| 0.8.0 | 111 | 9/15/2026 |
| 0.7.11 | 109 | 9/15/2026 |
| 0.7.10 | 120 | 9/13/2026 |
| 0.7.9 | 117 | 9/11/2026 |
| 0.7.8 | 120 | 9/10/2026 |
| 0.7.7 | 122 | 9/10/2026 |
| 0.7.6 | 176 | 7/1/2026 |
| 0.7.5 | 148 | 6/22/2026 |
| 0.7.4 | 156 | 6/18/2026 |
| 0.7.2 | 132 | 6/18/2026 |
| 0.7.1 | 145 | 6/18/2026 |
| 0.6.6 | 275 | 3/11/2026 |
| 0.6.5 | 126 | 3/4/2026 |
| 0.6.4 | 119 | 3/4/2026 |
| 0.6.3 | 134 | 3/3/2026 |