NexJob.SqlServer
0.2.0
See the version list below for details.
dotnet add package NexJob.SqlServer --version 0.2.0
NuGet\Install-Package NexJob.SqlServer -Version 0.2.0
<PackageReference Include="NexJob.SqlServer" Version="0.2.0" />
<PackageVersion Include="NexJob.SqlServer" Version="0.2.0" />
<PackageReference Include="NexJob.SqlServer" />
paket add NexJob.SqlServer --version 0.2.0
#r "nuget: NexJob.SqlServer, 0.2.0"
#:package NexJob.SqlServer@0.2.0
#addin nuget:?package=NexJob.SqlServer&version=0.2.0
#tool nuget:?package=NexJob.SqlServer&version=0.2.0
<div align="center">
<br/>
███╗ ██╗███████╗██╗ ██╗ ██╗ ██████╗ ██████╗
████╗ ██║██╔════╝╚██╗██╔╝ ██║██╔═══██╗██╔══██╗
██╔██╗ ██║█████╗ ╚███╔╝ ██║██║ ██║██████╔╝
██║╚██╗██║██╔══╝ ██╔██╗ ██ ██║██║ ██║██╔══██╗
██║ ╚████║███████╗██╔╝ ██╗╚█████╔╝╚██████╔╝██████╔╝
╚═╝ ╚═══╝╚══════╝╚═╝ ╚═╝ ╚════╝ ╚═════╝ ╚═════╝
Background jobs for .NET that stay out of your way.
<br/>
</div>
NexJob is the background job library that .NET deserved from day one.
No expression trees. No lock-in. No paid tiers for Redis. No wrestling with serialization. Just a clean interface, two lines of configuration, and a scheduler that handles the hard parts — retries, concurrency, cron, observability — while you focus on what your job actually does.
public class WelcomeEmailJob : IJob<WelcomeEmailInput>
{
public async Task ExecuteAsync(WelcomeEmailInput input, CancellationToken ct)
=> await _email.SendAsync(input.UserId, ct);
}
That's your entire job. NexJob handles the rest.
Why NexJob exists
Every .NET developer has used Hangfire. And every .NET developer has hit the same walls:
- The Redis adapter costs money. So does the MongoDB one.
async/awaitis bolted on — Hangfire serializesTask, it doesn't await it.- The dashboard looks like it was designed in 2014. Because it was.
- There's no concept of priority queues, resource throttling, or payload versioning.
- You can't change workers or pause a queue without restarting the server.
- The license is LGPL for the core and paid for anything production-worthy.
NexJob was built to solve all of that. MIT license, end to end. Every storage adapter open-source. Native async/await from the ground up. Priority queues and idempotency built in. A dark-mode dashboard to inspect every queue, job state, and retry — without touching the code. OpenTelemetry, appsettings.json support, and live runtime config are on the roadmap.
At a glance
✅ = implemented · 🔜 = on the roadmap
| NexJob | Hangfire | |
|---|---|---|
| License | MIT | LGPL / paid Pro |
async/await native |
✅ | ❌ |
| Priority queues | ✅ | ❌ |
| Idempotency keys | ✅ | ❌ |
| In-memory for testing | ✅ | ✅ |
| Cron / recurring jobs | ✅ | ✅ |
| PostgreSQL + MongoDB adapters free | ✅ | ❌ |
| Dashboard (dark mode) | ✅ | ❌ |
| All storage adapters free | ✅ | ❌ |
| Resource throttling | ✅ | ❌ |
| Job continuations (chaining) | ✅ | ❌ |
Per-job retry config ([Retry]) |
✅ | ❌ |
| Schema auto-migrations | ✅ | ❌ |
| Graceful shutdown | ✅ | ❌ |
| Distributed recurring lock | ✅ | ❌ |
appsettings.json support |
✅ | ❌ |
| Execution windows per queue | ✅ | ❌ |
| Live config without restart | ✅ | ❌ |
| OpenTelemetry built-in | 🔜 | ❌ |
| Payload versioning | 🔜 | ❌ |
Installation
# Core (includes in-memory provider for dev/tests)
dotnet add package NexJob
# Pick your storage — all free, all open-source
dotnet add package NexJob.Postgres
dotnet add package NexJob.SqlServer
dotnet add package NexJob.Redis
dotnet add package NexJob.MongoDB
dotnet add package NexJob.Oracle
# Optional dashboard
dotnet add package NexJob.Dashboard
Getting started
1 — Define your job
public record SendInvoiceInput(Guid OrderId, string CustomerEmail);
public class SendInvoiceJob : IJob<SendInvoiceInput>
{
private readonly IInvoiceService _invoices;
private readonly IEmailService _email;
public SendInvoiceJob(IInvoiceService invoices, IEmailService email)
{
_invoices = invoices;
_email = email;
}
public async Task ExecuteAsync(SendInvoiceInput input, CancellationToken ct)
{
var pdf = await _invoices.GenerateAsync(input.OrderId, ct);
await _email.SendAsync(input.CustomerEmail, pdf, ct);
}
}
2 — Register
builder.Services.AddNexJob(opt =>
{
opt.Workers = 10;
opt.PollingInterval = TimeSpan.FromSeconds(1);
});
// Register your jobs
builder.Services.AddTransient<SendInvoiceJob>();
3 — Schedule
// Fire and forget
await scheduler.EnqueueAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email));
// Delayed
await scheduler.ScheduleAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email),
delay: TimeSpan.FromMinutes(5));
// Recurring (cron) — default: skip if already running
await scheduler.RecurringAsync<MonthlyReportJob, MonthlyReportInput>(
id: "monthly-report",
input: new(DateTime.UtcNow.Month),
cron: "0 9 1 * *");
// Recurring — allow multiple instances in parallel (range-based sharding etc.)
await scheduler.RecurringAsync<ImportChunkJob, ImportChunkInput>(
id: "import-chunk",
input: new(ShardId: 0),
cron: "*/5 * * * *",
concurrencyPolicy: RecurringConcurrencyPolicy.AllowConcurrent);
// Continuation — runs only after parent succeeds
var jobId = await scheduler.EnqueueAsync<ProcessPaymentJob, PaymentInput>(paymentInput);
await scheduler.ContinueWithAsync<SendReceiptJob, ReceiptInput>(jobId, receiptInput);
// With idempotency key — safe to call multiple times
await scheduler.EnqueueAsync<SendInvoiceJob, SendInvoiceInput>(
new(orderId, email),
idempotencyKey: $"invoice-{orderId}");
4 — Dashboard
app.UseNexJobDashboard("/dashboard");
Open /dashboard and see every queue, every job state, every retry — live.
Priority queues
Jobs with Critical priority jump the queue. No workarounds, no separate deployments.
await scheduler.EnqueueAsync<AlertJob, AlertInput>(
input,
priority: JobPriority.Critical); // Critical → High → Normal → Low
Resource throttling
Limit how many instances of a job run concurrently across all workers — no extra infrastructure required.
[Throttle(resource: "stripe", maxConcurrent: 3)]
public class ChargeCardJob : IJob<ChargeInput> { ... }
Recurring concurrency policy
By default, NexJob prevents a recurring job from having more than one active instance at a time. If the previous execution is still running when the cron fires again, the new firing is silently skipped — no duplicates, no queued pile-up.
// SkipIfRunning (default) — safe for jobs that must not overlap
await scheduler.RecurringAsync<SyncInventoryJob, Unit>(
id: "sync-inventory",
input: Unit.Value,
cron: "*/5 * * * *");
// concurrencyPolicy defaults to RecurringConcurrencyPolicy.SkipIfRunning
Some jobs are designed to run in parallel — for example, range-based imports that each
process a different shard of data. Use AllowConcurrent to opt out of the overlap guard:
// AllowConcurrent — each firing spawns a new instance regardless of running ones
await scheduler.RecurringAsync<ImportShardJob, ShardInput>(
id: "import-shard",
input: new(ShardId: myShardId),
cron: "*/10 * * * *",
concurrencyPolicy: RecurringConcurrencyPolicy.AllowConcurrent);
The dashboard shows a ⟳ concurrent badge on any recurring job registered with
AllowConcurrent, so the behaviour is always visible at a glance.
Observability
🔜 Coming in v0.6 — OpenTelemetry spans for every job lifecycle event.
Payload versioning
🔜 Coming in v0.6 — migrate job inputs across schema versions without losing queued jobs.
Testing
The in-memory provider requires zero setup:
builder.Services.AddNexJob(opt => opt.Workers = 1);
// InMemoryStorageProvider is the default — no extra config needed
🔜
TestSchedulerwithShouldHaveEnqueuedassertions coming in v0.6.
Retry policy
Failed jobs retry with exponential backoff and jitter. Dead-lettered jobs are preserved — never silently dropped.
| Attempt | Delay |
|---|---|
| 1 | ~16 seconds |
| 2 | ~1 minute |
| 3 | ~5 minutes |
| 4 | ~17 minutes |
| 5 | ~42 minutes |
Configure globally:
builder.Services.AddNexJob(opt =>
{
opt.MaxAttempts = 3; // default: 10
});
Per-job override with [Retry]:
// Payment jobs: 5 retries, doubling delay from 30s up to 1h
[Retry(5, InitialDelay = "00:00:30", Multiplier = 2.0, MaxDelay = "01:00:00")]
public class PaymentJob : IJob<PaymentInput> { ... }
// Webhook jobs: dead-letter immediately on first failure
[Retry(0)]
public class WebhookJob : IJob<WebhookInput> { ... }
Schema migrations
NexJob automatically migrates its storage schema on startup. No manual SQL scripts, no deployment steps. Each migration runs in a transaction protected by a distributed advisory lock — safe when multiple instances start simultaneously.
// Nothing to call — migrations run automatically when your app starts
builder.Services.AddNexJob(builder.Configuration);
Graceful shutdown
When your host receives SIGTERM (Kubernetes rolling deployments, scale-down), NexJob waits for active jobs to complete before stopping.
{
"NexJob": {
"ShutdownTimeoutSeconds": 30
}
}
Jobs still running after the timeout are requeued automatically by the orphan watcher.
Quick start
# 1. Install
dotnet add package NexJob
dotnet add package NexJob.Dashboard
# 2. Or scaffold a complete starter project
dotnet new install NexJob.Templates
dotnet new nexjob -n MyApp
Storage providers
All open-source. No license walls. Ever.
| Package | Storage | Status |
|---|---|---|
NexJob |
In-memory | ✅ Dev and testing |
NexJob.Postgres |
PostgreSQL 14+ | ✅ SELECT FOR UPDATE SKIP LOCKED |
NexJob.MongoDB |
MongoDB 6+ | ✅ Atomic findAndModify |
NexJob.SqlServer |
SQL Server 2019+ | 🔜 Coming soon |
NexJob.Redis |
Redis 7+ | 🔜 Coming soon |
NexJob.Oracle |
Oracle 19c+ | 🔜 Coming soon |
Bring your own? Implement IStorageProvider — one interface, ~15 methods.
Configuration reference
// Storage — pick one and register BEFORE AddNexJob (InMemory is the default)
builder.Services.AddNexJobPostgres(connectionString);
builder.Services.AddNexJobMongoDB(connectionString, databaseName: "nexjob");
builder.Services.AddNexJob(opt =>
{
// Workers & queues
opt.Workers = 10;
opt.Queues = ["default", "critical"]; // polled in order
// Timing
opt.PollingInterval = TimeSpan.FromSeconds(5);
opt.HeartbeatInterval = TimeSpan.FromSeconds(30);
opt.HeartbeatTimeout = TimeSpan.FromMinutes(5);
// Retries
opt.MaxAttempts = 5;
// Graceful shutdown
opt.ShutdownTimeout = TimeSpan.FromSeconds(30);
});
Roadmap
v0.1 ✅ Core interfaces · in-memory provider · fire-and-forget
v0.2 ✅ PostgreSQL + MongoDB providers · delayed jobs · cron · dashboard (Blazor SSR)
v0.3 ✅ Priority queues · resource throttling ([Throttle]) · job continuations
v0.4 ✅ Recurring job execution status · unit + integration tests · CI pipeline
v0.5 ✅ SQL Server · Redis · Oracle providers · runtime settings · execution windows
v0.6 ✅ [Retry] per-job · graceful shutdown · schema migrations · distributed recurring lock
v0.7 ○ OpenTelemetry (Activity spans per job) · IJobMigration<TOld,TNew> · SchemaVersion
v1.0 ○ Stable API · production-ready · published to NuGet
Contributing
NexJob is built in the open. Issues, ideas, and PRs are welcome.
git clone git@github.com:oluciano/NexJob.git
cd NexJob
dotnet restore
dotnet test
Read CONTRIBUTING.md before opening a PR.
License
MIT © 2025 Luciano Azevedo
<div align="center"> <br/>
Built with obsession over developer experience.
If Hangfire is the past, NexJob is what comes next.
<br/> </div>
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. 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. |
-
net8.0
- Dapper (>= 2.1.35)
- Microsoft.Data.SqlClient (>= 5.2.2)
- NexJob (>= 0.2.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.