Dloizides.Jobs.EntityFrameworkCore 1.2.0

dotnet add package Dloizides.Jobs.EntityFrameworkCore --version 1.2.0
                    
NuGet\Install-Package Dloizides.Jobs.EntityFrameworkCore -Version 1.2.0
                    
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="Dloizides.Jobs.EntityFrameworkCore" Version="1.2.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Dloizides.Jobs.EntityFrameworkCore" Version="1.2.0" />
                    
Directory.Packages.props
<PackageReference Include="Dloizides.Jobs.EntityFrameworkCore" />
                    
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 Dloizides.Jobs.EntityFrameworkCore --version 1.2.0
                    
#r "nuget: Dloizides.Jobs.EntityFrameworkCore, 1.2.0"
                    
#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 Dloizides.Jobs.EntityFrameworkCore@1.2.0
                    
#: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=Dloizides.Jobs.EntityFrameworkCore&version=1.2.0
                    
Install as a Cake Addin
#tool nuget:?package=Dloizides.Jobs.EntityFrameworkCore&version=1.2.0
                    
Install as a Cake Tool

Dloizides.Jobs.EntityFrameworkCore

The Entity Framework Core store for Dloizides.Jobs — the persistence half of the checkpointed background-jobs standard. It maps JobRun (jsonb checkpoint / progress on Postgres), creates the single-flight partial unique index, and implements every state transition as a compare-and-set ExecuteUpdate so a reclaim-vs-complete race is a benign no-op instead of a poll-killing DbUpdateConcurrencyException.

Why compare-and-set, not tracked SaveChanges

The runner's heartbeat advances a run's row while the job works. A tracked completion carries the row version it read AT CLAIM TIME, matches 0 rows against the heartbeated row, and throws DbUpdateConcurrencyException — which aborted the whole poll and re-ran the job forever. The store's conditional UPDATE ... WHERE Id = @id AND Outcome = 'running' AND ClaimedBy = @owner carries no tracker state: it finalises the row only while it is still running under this owner, so the owner's own heartbeats never collide with its own completion, and a reclaimed row simply matches 0 rows — a benign no-op the runner drops cleanly.

Wiring

  1. Map JobRun in your DbContext:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    base.OnModelCreating(modelBuilder);
    modelBuilder.ApplyJobRunConfiguration();            // auto-detects Npgsql -> jsonb columns
    // or: modelBuilder.ApplyJobRunConfiguration(Database.IsNpgsql());
}
  1. Add a migration for the new JobRuns table + the single-flight index.

  2. Select the store inside AddDloizidesJobs:

builder.AddDloizidesJobs(jobs =>
{
    jobs.AddJob<IngestJob>();
    jobs.UseEntityFrameworkStore<AppDbContext>();
});

What it maps

  • Checkpoint / Progress → jsonb on Postgres (plain string/TEXT elsewhere); on SQLite the timestamps convert to sortable UTC ticks so the claim/history queries translate.
  • Single-flight index IX_JobRuns_SingleFlight — UNIQUE (JobName) WHERE Outcome IN ('queued','running').
  • CAS transitions: ClaimNextAsync (claim queued or reclaim a lapsed lease, preserving StartedAt and the checkpoint for resume), HeartbeatAsync, SaveCheckpointAsync, ReportProgressAsync, CompleteAsync — each conditioned on the observed state, returning false/null on 0 rows.

Postgres status backplane (real-time PUSH)

This package also ships the Postgres LISTEN/NOTIFY status backplane — cross-pod real-time push with no new infra, reusing the same database as the store. Select it inside AddDloizidesJobs:

jobs.UseEntityFrameworkStore<AppDbContext>();
jobs.UsePostgresStatusBackplane<AppDbContext>();   // reads the connection string from AppDbContext
"Jobs": { "Status": { "Backplane": "Postgres", "Channel": "dloizides_jobs" } }

The publishing pod issues pg_notify(channel, payload); every pod holds one dedicated LISTEN connection and re-emits each event to local subscribers (the Dloizides.Jobs.AspNetCore wire then re-reads the durable JobStatus and streams it). Persist first, push second — NOTIFY is not durable, so a notification lost to a reconnect is simply healed by the next poll. The LISTEN loop is a hosted service that stays dormant unless Jobs:Status:Backplane=Postgres, and reconnects with backoff.

This adds a transitive Npgsql (raw ADO driver) reference — LISTEN/NOTIFY has no provider-agnostic EF surface. It is inert unless the Postgres backplane is selected.

Requires a relational provider

The store throws NotSupportedException on the in-memory provider by design: it ignores both ExecuteUpdate and the unique index, so it cannot exercise the concurrency guarantees this package exists to provide. Use Postgres in production and SQLite (or a Postgres Testcontainer) in tests — the bundled test suite runs on SQLite so the compare-and-set genuinely fires.

License

MIT

Product 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 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
1.2.0 135 9/13/2026
1.1.0 208 8/14/2026
1.0.0 754 8/14/2026