-
Notifications
You must be signed in to change notification settings - Fork 0
Spindle EF Core persistence
This page describes EF Core setup for a new application and the three supported ways to apply Spindle's migrations:
Database.MigrateAsync()dotnet ef database update- Generated migration scripts
Install Spindle.Hosting if the application also hosts flows, plus the Spindle EF Core provider package for the database:
| Database | Package | Registration | Schema parameter |
|---|---|---|---|
| SQLite | Spindle.Persistence.EFCore.Sqlite |
AddSpindleSqlite(...) |
No |
| PostgreSQL | Spindle.Persistence.EFCore.PostgreSQL |
AddSpindlePostgreSql(...) |
Yes |
| MySQL | Spindle.Persistence.EFCore.MySql |
AddSpindleMySql(...) |
No |
| SQL Server | Spindle.Persistence.EFCore.SqlServer |
AddSpindleSqlServer(...) |
Yes |
For EF Core command-line operations, install the dotnet-ef tool and reference the matching EF Core design package in the application project:
dotnet new tool-manifest
dotnet tool install dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.DesignUse a design package version matching the EF Core provider major version. The provider packages contain Spindle's provider-specific migrations; the application normally consumes those migrations rather than creating a second copy of them.
Register the provider before AddSpindleRuntime or AddSpindleWorker. The provider registration supplies the Spindle store, pooled IDbContextFactory<SpindleDbContext>, provider migrations assembly, and retrying execution strategy.
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore.Sqlite;
builder.Services.AddSpindleSqlite(
builder.Configuration.GetConnectionString("Spindle")
?? "Data Source=spindle.db");SQLite also supports the existing SqliteConnection overload. SQLite does not expose a schema parameter because the installed EF Core provider does not support EF Core schemas.
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore.PostgreSQL;
builder.Services.AddSpindlePostgreSql(
builder.Configuration.GetConnectionString("Spindle")
?? throw new InvalidOperationException("The Spindle connection string is required."),
schema: builder.Configuration["Spindle:Schema"]);When a schema is supplied, Spindle applies it to entity tables and the migrations history table. Generated migration SQL creates the schema when needed.
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore.MySql;
builder.Services.AddSpindleMySql(
builder.Configuration.GetConnectionString("Spindle")
?? throw new InvalidOperationException("The Spindle connection string is required."));MySQL does not expose a schema parameter because the installed EF Core provider does not support EF Core schemas.
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore.SqlServer;
builder.Services.AddSpindleSqlServer(
builder.Configuration.GetConnectionString("Spindle")
?? throw new InvalidOperationException("The Spindle connection string is required."),
schema: builder.Configuration["Spindle:Schema"]);SQL Server sensitive-data logging is disabled by default. Provider options can be changed through the optional configuration action, which runs after Spindle's defaults:
builder.Services.AddSpindleSqlServer(
connectionString,
schema: schema,
configure: options =>
{
options.EnableDetailedErrors();
// EnableSensitiveDataLogging() should only be used in a controlled environment.
});The EF tools must be able to construct the application's SpindleDbContext without starting the production process. The simplest setup is to keep provider registration in the application's normal host-building path:
using Microsoft.Extensions.DependencyInjection;
using Spindle.Hosting;
using Spindle.Persistence.EFCore.PostgreSQL;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSpindlePostgreSql(
builder.Configuration.GetConnectionString("Spindle")!,
schema: builder.Configuration["Spindle:Schema"]);
builder.Services.AddSpindleRuntime();The same registration should be used for runtime and design time so that the migration assembly, history table, provider options, and schema agree. Verify discovery before applying a migration:
dotnet ef dbcontext info \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContextIf the application cannot be constructed by the EF tools, add an application-owned IDesignTimeDbContextFactory<SpindleDbContext> and configure it with the same provider, connection string, schema, migrations assembly, and options as the application. Do not use a second model with a different schema or migrations assembly.
Spindle's provider packages contain the migrations for Spindle's own model. A consuming application should normally:
- Reference one provider package.
- Configure the provider and optional runtime schema.
- Apply the migrations during deployment or startup according to its operational policy.
- Upgrade the provider package when Spindle publishes new migrations.
Do not run migrations add in the consuming application to recreate Spindle's migrations. The repository's add, check, list, and remove helper commands are for maintaining the provider migrations inside the Spindle repository. They use schema-neutral design-time models.
This is convenient for local development, integration tests, and a single controlled application instance. It is usually not appropriate for every production replica to run migrations on startup.
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Spindle.Persistence.EFCore;
var app = builder.Build();
await using (var scope = app.Services.CreateAsyncScope())
{
var contextFactory = scope.ServiceProvider
.GetRequiredService<IDbContextFactory<SpindleDbContext>>();
await using var database =
await contextFactory.CreateDbContextAsync();
await database.Database.MigrateAsync();
}
await app.RunAsync();For a worker service, run this after builder.Build() and before RunAsync(). For a non-worker service, the same pattern can be used if that service is the designated migration owner.
The configured schema is used for both Spindle's tables and __EFMigrationsHistory. Changing the schema does not move existing data from another schema.
Use this from a developer machine or a dedicated deployment/migration job after dbcontext info succeeds:
dotnet ef database update \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContextIf the schema comes from application configuration, provide it through the normal configuration source used by the startup project. For example, with an environment-backed Spindle:Schema setting:
Spindle__Schema=spindle dotnet ef database update \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContextThis command applies the migrations shipped in the configured provider assembly. Treat it as a controlled deployment operation in production; do not run it concurrently from every application replica.
Script generation is usually the preferred production workflow when SQL must be reviewed, approved, audited, or applied by a database deployment system.
Generate a complete script:
dotnet ef migrations script 0 \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContext \
--output artifacts/spindle-migrations.sqlGenerate an idempotent script when the target database may be at different migration levels:
dotnet ef migrations script 0 \
--idempotent \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContext \
--output artifacts/spindle-migrations-idempotent.sqlTo generate only a range, provide both migration IDs or names:
dotnet ef migrations script \
20260809115519_GenericNodes \
20260825072319_AddConditionWaits \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContext \
--output artifacts/spindle-migrations-range.sqlFor PostgreSQL or SQL Server, the application's design-time configuration must contain the same schema that production will use. The schema should come from application configuration, for example:
Spindle__Schema=spindle dotnet ef migrations script 0 \
--idempotent \
--project MyApplication.csproj \
--startup-project MyApplication.csproj \
--context SpindleDbContext \
--output artifacts/spindle-migrations.sqlThe special SPINDLE_EF_SCHEMA variable is for the Spindle repository's own script helper, not a replacement for a consuming application's design-time configuration:
SPINDLE_EF_SCHEMA=spindle \
./scripts/migrations.sh script postgresql 0That helper is useful when validating the provider migrations from a Spindle checkout. In a new application, use the application's own dotnet ef command and configuration as shown above.
| Workflow | Good for | Production guidance |
|---|---|---|
Database.MigrateAsync() |
Local development, tests, single-owner startup | Avoid running concurrently from every replica. |
dotnet ef database update |
Controlled release or migration job | Run from a deployment identity with database permissions. |
| Generated SQL script | Review, approval, audit, DBA-controlled deployment | Prefer an idempotent script when deployment state varies. |
Whichever workflow is selected, deploy the schema and migration history together with the application version that requires them. Keep connection strings and credentials outside source control.