Quartz.NETQuartz.NET
Home
Features
Blog
Discussions
NuGet
GitHub
Home
Features
Blog
Discussions
NuGet
GitHub
  • Getting Started

    • Overview
    • Quartz 4 Quick Start
    • Tutorial
      • Using Quartz
      • Jobs And Triggers
      • More About Jobs & JobDetails
      • Job Data
      • More About Triggers
      • Querying Jobs and Triggers
      • Simple Triggers
      • Cron Triggers
      • RecurrenceTrigger
      • Time and TimeProvider
      • Trigger and Job Listeners
      • Scheduler Listeners
      • Job Execution Middleware
      • Job Stores
      • Configuration, Resource Usage and Building a Scheduler
      • Building a Scheduler Without a Host
      • Clustering
      • Execution Groups
      • Node Affinity (Preferred Node)
      • Testing
      • Compile-Time Checks
      • Declaring Jobs with Attributes
      • Delegate Jobs
    • Configuration Reference
    • JSON Configuration
    • Cron Expression Reference
    • Multi-Tenancy
    • Comparison
    • Frequently Asked Questions
    • Best Practices
    • Before You Go Live
    • Operating a Cluster
    • Log Events
    • Tenancy Patterns
    • Database Schema
    • Database Schema Changes
    • Migration Guide
    • Troubleshooting
    • API Documentation
  • How To's
    • One-Off Job
    • Rescheduling Jobs
    • Backfill
    • Retrying Failed Jobs
    • Pausing with a Reason
    • Job Continuations
    • Overlap Policy
    • Progress and Execution Logs
    • Multiple Triggers
    • Job Template
    • Running Quartz under Aspire
    • Quartz.NET with Wolverine
    • Coming from Hangfire
    • Coming from TickerQ
    • Embedding Quartz in a Library
    • Running under an External Leader Election
    • Publishing Trimmed and Native AOT
    • Extending Quartz: what is open, what is closed, and how to ask
    • A Job Store of Your Own
    • A Driver Delegate for a New Database
    • Persisting a Custom Trigger Type
    • A Lock Handler of Your Own
  • Packages

    • Quartz Core Additions

      • Jobs
      • Serialization (System.Text.Json)
      • JSON Serialization
      • Plugins
    • Integrations

      • Aspire Integration
      • ASP.NET Core Integration
      • HTTP API
      • HTTP Client
      • Dashboard
      • Hosted Services Integration
      • Microsoft DI Integration
      • Multiple Schedulers with Microsoft DI
      • Observability
      • Redis Lock Handler
      • TimeZoneConverter Integration
      • Weasel Schema Management
    • 3rd Party Plugins for Quartz
  • Quartz 3.x

    • Getting Started

      • Quartz 3 Quick Start
      • Tutorial
        • Using Quartz
        • Library Overview
        • Jobs And Triggers
        • More About Jobs
        • More About Triggers
        • Execution Groups
        • Node Affinity (Preferred Node)
        • Simple Triggers
        • Cron Triggers
        • RecurrenceTrigger
        • Trigger and Job Listeners
        • Scheduler Listeners
        • Job Stores
        • Tuning the Scheduler
        • Configuration, Resource Usage and SchedulerFactory
        • Advanced (Enterprise) Features
      • Configuration Reference
      • JSON Configuration
      • Multi-Tenancy
      • Frequently Asked Questions
      • Best Practices
      • Tenancy Patterns
      • Troubleshooting
      • API Documentation
      • Database Schema
      • Database Schema Changes
      • Migration Guide
      • Miscellaneous Features
    • How To's

      • One-Off Job
      • Multiple Triggers
      • Job Template
      • Using the CronTrigger
      • Rescheduling Jobs
    • Packages

      • Quartz Core Additions

        • Dashboard
        • Jobs
        • Serialization (System.Text.Json)
        • Serialization (Newtonsoft Json.NET)
        • Plugins
      • Integrations

        • ASP.NET Core Integration
        • Hosted Services Integration
        • Microsoft DI Integration
        • Multiple Schedulers with Microsoft DI
        • OpenTelemetry Integration
        • OpenTracing Integration
        • Redis Lock Handler
        • TimeZoneConverter Integration
      • 3rd Party Plugins for Quartz
  • Old Releases

    • Quartz 2.x
      • Quartz 2 Quick Start
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
        • CronTrigger Tutorial
      • Configuration Reference
      • Migration Guide
      • API Documentation
    • Quartz 1.x
      • Tutorial
        • Lesson 1: Using Quartz
        • Lesson 2: Jobs And Triggers
        • Lesson 3: More About Jobs & JobDetails
        • Lesson 4: More About Triggers
        • Lesson 5: SimpleTrigger
        • Lesson 6: CronTrigger
        • Lesson 7: TriggerListeners and JobListeners
        • Lesson 8: SchedulerListeners
        • Lesson 9: JobStores
        • Lesson 10: Configuration, Resource Usage and SchedulerFactory
        • Lesson 11: Advanced (Enterprise) Features
        • Lesson 12: Miscellaneous Features of Quartz
      • API Documentation
  • License

The Quartz.Weasel packages put the ADO.NET job store's tables under Weasel, the schema tool behind Marten and Wolverine. An application already on that stack then creates and migrates Quartz's tables with the workflow it runs for its own: db-apply, db-assert, db-patch, resources setup, and AutoCreate at startup. Requires Quartz 4.3 or later.

An application not using Weasel keeps ProvisionSchema() and the scripts under database/migrations/.

Packages

PackageDatabase
Quartz.Weasel.PostgreSQLPostgreSQL, standalone or inside a Marten store
Quartz.Weasel.SqlServerSQL Server 2016 or later, disk-based tables
Quartz.Weasel.SQLiteSQLite, through Microsoft.Data.Sqlite
Quartz.Weaselthe shared glue; installed by any of the above

MySQL and Oracle wait for fixes in Weasel itself. Firebird is not planned: Weasel has no Firebird provider.

dotnet add package Quartz.Weasel.PostgreSQL

Registering

Call the dialect's method on the same store that chose the database:

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);

builder.Services.AddQuartz(q => q.UsePersistentStore(store =>
{
    store.UsePostgres(connectionString);
    store.UseSystemTextJsonSerializer();
    store.UseWeaselForPostgres();
}));
builder.Services.AddQuartzHostedService();

IHost host = builder.Build();

// db-apply, db-assert, db-patch, resources: JasperFx's command line, over this host
return await host.RunJasperFxCommands(args);
services.AddQuartz(q => q.UsePersistentStore(store =>
{
    store.UseSqlServer(connectionString);
    store.UseWeaselForSqlServer();
}));
services.AddQuartz(q => q.UsePersistentStore(store =>
{
    store.UseSqlite(connectionString);
    store.UseWeaselForSqlite();
}));

The dialect is in the method name, so an application with several packages never has an ambiguous call.

Checked at startup:

  • The store's driver matches the package: Npgsql for PostgreSQL, Microsoft.Data.SqlClient for SQL Server, Microsoft.Data.Sqlite for SQLite.
  • The store does not also call ProvisionSchema(). A schema has one owner.

Each scheduler is one Weasel database. Its identifier is the scheduler name and its subject URI is quartz://scheduler/<name>, so db-patch -d <name> picks it. Weasel connects through the store's own connection provider.

Startup

The schema is applied before any scheduler is built, so the store's validation sees the result.

AutoCreateSource
the value you setUseWeaselForPostgres(w => w.AutoCreate = …), and the same on the other dialects
the active JasperFx profile's ResourceAutoCreatewhen JasperFx is registered, as Marten and Wolverine read it
CreateOrUpdateotherwise

None applies nothing at startup; the store's validation then reports anything missing. To apply at deploy time only:

// Weasel applies nothing at startup in Production; db-apply does it at deploy time instead.
services.AddJasperFx(options => options.Production.ResourceAutoCreate = AutoCreate.None);

A failed apply fails the startup, unless the profile's ResourceMigrationFailureMode is ContinueOnFailures: then it is logged (event 10003) and the store's validation decides.

Commands

With the host's Main ending in await host.RunJasperFxCommands(args):

CommandDoes
db-applyapplies every difference
db-assertfails when the database differs from the model
db-patch <file>writes the DDL to a file, and a matching .drop.sql
db-listlists the databases, one per scheduler
resources setupthe same apply as db-apply
resources checkthe same check as db-assert

resources clear and resources teardown do nothing to Quartz's tables: they hold the schedule, not state that can be rebuilt.

What Weasel changes

ObjectWeasel
a missing Quartz table, column, index or keycreates it
a Quartz index whose columns changeddrops and recreates it
an index name 3.x created and 4.x retireddrops it, as database/migrations/4.0/ does
a column, index or foreign key you added to a Quartz tablekeeps it (tables are add-only)
your own tables, and other Weasel models'never looks at them
a change only possible by dropping a tablerefuses, even under AutoCreate.All

The model is generated from the same source as the store's own scripts, and names every object the way the database's catalog does. A database created by database/tables/, by ProvisionSchema() or by the migrations therefore reads as unchanged. The execution history tables are always part of it, as they are of a fresh install.

PostgreSQL

The schema is the table prefix's: quartz.qrtz_ puts the tables in schema quartz. Without one it is public.

// tables quartz.qrtz_job_details, quartz.qrtz_triggers, ...
store.ConfigureStore(options => options.TablePrefix = "quartz.qrtz_");
store.UseWeaselForPostgres();

Every apply takes a session advisory lock first, so nodes starting together, or a node starting during db-apply, take turns. The others then find nothing left to do.

Lock idOwner
0x5152545A (1364350042)Quartz, PostgresWeaselOptions.DefaultLockId
4004Marten
4006Wolverine
store.UseWeaselForPostgres(weasel =>
{
    // unset: the active JasperFx profile's ResourceAutoCreate, else CreateOrUpdate
    weasel.AutoCreate = AutoCreate.CreateOrUpdate;
    weasel.LockId = PostgresWeaselOptions.DefaultLockId;
    weasel.LockTimeout = TimeSpan.FromMinutes(1);
});

LockTimeout bounds the wait; an apply that times out fails like any other.

Inside a Marten store

To have Marten own the tables, add them as a feature schema and leave UseWeaselForPostgres() out:

builder.Services.AddMarten(connectionString).ApplyAllDatabaseChangesOnStartup();

// the same tables, with the scheduler's table prefix
builder.Services.ConfigureMarten((services, opts) =>
    opts.Storage.Add(QuartzPostgresFeatureSchema.ForScheduler(services)));
  • Marten applies them with its own schema: at startup, db-apply and resources setup. Marten's AutoCreate, migrator and lock (4004) apply; PostgresWeaselOptions does not.
  • CompletelyRemoveAllAsync leaves them alone. Storage.ExtendedSchemaObjects would drop them with CASCADE.
  • ApplyAllDatabaseChangesOnStartup() is required, or db-apply / resources setup before the first start. Without either, Marten creates nothing at startup and the store's schema validation fails on the missing tables.
  • ForScheduler refuses a scheduler that also calls UseWeaselForPostgres(). Constructing new QuartzPostgresFeatureSchema(prefix) directly is not checked.
  • One Quartz feature per Marten store: Marten keeps one feature per type.

SQL Server

The schema is the table prefix's: quartz.QRTZ_ or [quartz].QRTZ_ puts the tables in schema quartz. Without one it is dbo. Every name is the script's: PK_QRTZ_TRIGGERS, FK_QRTZ_TRIGGERS_QRTZ_JOB_DETAILS, IDX_QRTZ_T_NFT_ST.

Every apply takes a session application lock (sp_getapplock) first, so nodes starting together, or a node starting during db-apply, take turns. The lock is scoped to the database.

Lock resourceOwner
quartz:migrateQuartz, SqlServerWeaselOptions.DefaultLockResource
4006Wolverine's message store
polecat:migrate:<schema>Polecat
// tables quartz.QRTZ_JOB_DETAILS, quartz.QRTZ_TRIGGERS, ...
store.ConfigureStore(options => options.TablePrefix = "quartz.QRTZ_");
store.UseWeaselForSqlServer(weasel =>
{
    // unset: the active JasperFx profile's ResourceAutoCreate, else CreateOrUpdate
    weasel.AutoCreate = AutoCreate.CreateOrUpdate;
    weasel.LockResource = SqlServerWeaselOptions.DefaultLockResource;
    weasel.LockTimeout = TimeSpan.FromMinutes(1);
});

LockTimeout bounds the wait, even past the connection's command timeout; an apply that times out fails like any other.

CaseWeasel
memory-optimized tables (tables_sqlServerMOT.sql)refused before anything runs; keep that script
SQL Server before 2016 (tables_sqlServer_Below2016.sql)not supported
FK_QRTZ_BLOB_TRIGGERS_QRTZ_TRIGGERSnot modelled: tables_sqlServer.sql never created it; ProvisionSchema()'s is kept
a numeric column's precisionnot compared

SQLite

  • The store must use UseSqlite: Weasel speaks Microsoft.Data.Sqlite only.
  • No lock is taken. The file serializes writers, every CREATE is guarded, and an ADD COLUMN that loses a race is re-read and found done.
  • SQLite makes some changes by rebuilding the table, and the rebuild keeps only what the model declares. A rebuild of a Quartz table that carries your own columns, indexes or keys is refused before anything runs, naming them.

Moving off Weasel.Quartz.Postgres

Weasel.Quartz.Postgres was the first Weasel integration for Quartz.NET — thanks to Jaedyn for building it. The Quartz.Weasel packages need Quartz 4.3; an application still on Quartz 3.x keeps Weasel.Quartz.Postgres until it upgrades.

Its table and constraint names are PostgreSQL's defaults, as here, so switching renames nothing and rebuilds no table. The first apply is a 3.x-to-4.x upgrade: idx_qrtz_t_nft_st is dropped and recreated in its 4.x shape, the 4.x columns and tables are added, and any retired 3.x index names are dropped.

  1. Remove the Weasel.Quartz.Postgres package.
  2. Replace QuartzSchema.Create(...) with UseWeaselForPostgres() on the store.
  3. With Marten, replace options.Storage.ExtendedSchemaObjects.AddRange(QuartzSchema.AllTables()) with the feature schema above.

Every node in one deployment

Remove Weasel.Quartz.Postgres from every node at once. Its tables are not add-only and it applies with CreateOrUpdate, so a node still running it drops the 4.x columns its model does not know.

See also

  • Wolverine — Quartz beside Wolverine
  • Job stores — the ADO.NET store and its schema
  • Schema changes — every schema version and its migration
Help us by improving this page!
Last Updated: 9/28/26, 10:13 PM
Contributors: Marko Lahma, Claude Opus 5.5
Prev
TimeZoneConverter Integration