Kanject.Core.Recurring.Provider.CacheDb
1.5.1
Prefix Reserved
See the version list below for details.
dotnet add package Kanject.Core.Recurring.Provider.CacheDb --version 1.5.1
NuGet\Install-Package Kanject.Core.Recurring.Provider.CacheDb -Version 1.5.1
<PackageReference Include="Kanject.Core.Recurring.Provider.CacheDb" Version="1.5.1" />
<PackageVersion Include="Kanject.Core.Recurring.Provider.CacheDb" Version="1.5.1" />
<PackageReference Include="Kanject.Core.Recurring.Provider.CacheDb" />
paket add Kanject.Core.Recurring.Provider.CacheDb --version 1.5.1
#r "nuget: Kanject.Core.Recurring.Provider.CacheDb, 1.5.1"
#:package Kanject.Core.Recurring.Provider.CacheDb@1.5.1
#addin nuget:?package=Kanject.Core.Recurring.Provider.CacheDb&version=1.5.1
#tool nuget:?package=Kanject.Core.Recurring.Provider.CacheDb&version=1.5.1
Kanject.Core.Recurring.Provider.CacheDb
An IRecurringDataProvider implementation (from Kanject.Core.Recurring.Abstractions) that keeps recurring-job leases and checkpoints in any ICacheDb (from Kanject.Core.CacheDb.Abstractions). Use it when a Kanject.Core [RecurringHosted] job sets LeaseKey or CheckpointKey and you want a CacheDb provider as the backing store.
Installation
dotnet add package Kanject.Core # [Recurring] / [RecurringHosted]
dotnet add package Kanject.Core.Recurring.Provider.CacheDb
dotnet add package Kanject.Core.CacheDb.Provider.InMemory # local-development store
Targets .NET 8, .NET 9 and .NET 10. Kanject.Core.Recurring.Abstractions comes in transitively. This package doesn't include a CacheDb provider. You have to install and register one.
Quick start
You need three pieces: a job marked with [RecurringHosted], a CacheDb provider, and this package bound to that provider's lock extensions.
// InvoiceSweeper.cs
using Kanject.Core.Annotations.Attributes.Recurring;
using Kanject.Core.Annotations.Attributes.Recurring.Enums;
namespace Billing.Jobs;
public sealed class InvoiceSweeper(IInvoiceStore invoices)
{
[Recurring(1, RecurringRateUnit.Minutes)]
[RecurringHosted(LeaseKey = "invoice-sweep", LeaseDurationSeconds = 120, CheckpointKey = "invoice-sweep")]
public Task SweepAsync(CancellationToken cancellationToken) =>
invoices.CloseExpiredAsync(cancellationToken);
}
public interface IInvoiceStore
{
Task CloseExpiredAsync(CancellationToken cancellationToken);
}
// Program.cs
using Billing.Jobs;
using Kanject.Core.CacheDb.Provider.InMemory.Extensions;
using Kanject.Core.Recurring.Provider.CacheDb.Extensions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IInvoiceStore, SqlInvoiceStore>(); // your implementation
// 1. A CacheDb provider. In-memory works within one process only, so use it for local development.
builder.Services.AddInMemoryCacheDb("billing");
// 2. Leases and checkpoints over the registered ICacheDb. The delegates must come from
// the SAME provider package that registered ICacheDb.
builder.Services.AddCacheDbRecurringDataProvider(options =>
{
options.AcquireLock = DbLockExtensions.AcquireLockAsync;
options.ReleaseLock = DbLockExtensions.ReleaseLockAsync;
});
// 3. Generated by [RecurringHosted] as Add{Type}{Method}Recurring, in the job's namespace.
builder.Services.AddInvoiceSweeperSweepAsyncRecurring();
await builder.Build().RunAsync();
On each tick, the generated service does three things:
- It tries to take the
invoice-sweeplease for 120 seconds. If another holder has it, the tick is skipped. - It runs
SweepAsync, then reads the stored checkpoint and saves the next one (iteration + 1, the stored state kept). - It releases the lease, unless the 120 seconds have already run out.
Going to production
With the in-memory provider, leases coordinate only inside one process, and checkpoints are lost on restart. For more than one instance, register a durable provider instead: Kanject.Core.CacheDb.Provider.DynamoDb or Kanject.Core.CacheDb.Provider.S3Express. Bind AcquireLock / ReleaseLock to that provider's DbLockExtensions.AcquireLockAsync / ReleaseLockAsync, which have the same signatures and live in the provider's own …Extensions namespace. The job code and the AddCacheDbRecurringDataProvider call don't change.
The lock extensions only work with their own provider's ICacheDb. If you mix packages, for example in-memory delegates with a DynamoDB ICacheDb, every lease call fails with NotSupportedException.
Registration and options
AddCacheDbRecurringDataProvider(Action<CacheDbRecurringDataProviderOptions> configure) works like this:
- It runs
configureimmediately, then checks that both delegates are set. If either is missing, it throwsInvalidOperationExceptionright there, at registration. - It registers the options instance and
CacheDbRecurringDataProvideras the singletonIRecurringDataProvider, both withTryAdd. If you call it twice, the first registration wins. - It returns the
IServiceCollectionso you can chain calls. - It needs an
ICacheDbregistered in the container.
| Option | Type | Default | Purpose |
|---|---|---|---|
AcquireLock |
AcquireCacheDbLockAsync? |
none (required) | Bind to your provider's DbLockExtensions.AcquireLockAsync |
ReleaseLock |
ReleaseCacheDbLockAsync? |
none (required) | Bind to your provider's DbLockExtensions.ReleaseLockAsync |
KeyPrefix |
string |
"kanject:recurring" |
First fragment of every lease and checkpoint key |
CheckpointRetention |
TimeSpan |
30 days | Checkpoint expiry when the caller passes no retention. The generated hosted service always passes none, so this governs its checkpoints |
The constructor runs the same delegate check, which covers the direct-construction path. ValidateOnBuild can't catch a missing delegate: it checks that dependencies resolve and never runs constructors. That's why the registration method checks eagerly.
AcquireCacheDbLockAsync and ReleaseCacheDbLockAsync are ordinary delegates. You can also point them at your own lambdas, for example to wrap the provider call with logging. To create the provider without DI, call new CacheDbRecurringDataProvider(cacheDb, options).
How data is stored
- Keys.
- Lease keys are built with
cacheDb.FormatCacheKey(KeyPrefix, "lease", leaseKey). - Checkpoint keys are built with
cacheDb.FormatCacheKey(KeyPrefix, "checkpoint", checkpointKey). - This keeps both kinds of record separate from your other cache entries, so a job can use the same name for
LeaseKeyandCheckpointKey. - The final format depends on the provider. With the in-memory provider registered as
"billing", the lease above is stored asbilling_kanject:recurring:lease:invoice-sweep.
- Lease keys are built with
- Acquiring a lease.
- It calls
AcquireLock(cacheDb, lockId, DateTime.UtcNow + duration, comment: null, data: holderData, overrideData: false). - On success, it returns
Acquired = truewith the formatted lock id asLeaseId. - If the provider throws
DbLockConflictException, it returnsAcquired = falsewith the conflict'sLockId,Data(asCurrentHolderData) andLockDuration(asRemainingDuration). - Any other exception propagates.
overrideData: falsestops a caller that loses the race from overwriting the current holder's data.
- It calls
- Releasing a lease.
- It calls
ReleaseLock(cacheDb, lockId). - The CacheDb providers delete the lock by id without checking who holds it. If an iteration runs longer than the lease, another instance can take the lease, and a release would delete that instance's lease.
- The generated hosted service guards against this: it skips the release once
LeaseDurationSecondshas elapsed since it acquired the lease. Code that callsReleaseLeaseAsyncdirectly, such as a Lambda handler, needs the same check. - An overrunning iteration can still overlap the next holder's iteration, so size
LeaseDurationSecondsabove your worst-case iteration time.
- It calls
- Checkpoints.
- They are serialized with System.Text.Json using web defaults (camelCase).
- They are written as a string through
ICacheDb.CacheDataAsync, with an expiry ofDateTime.UtcNow + (retention ?? CheckpointRetention). - A stored payload that can't be deserialized is read as
null, and the next save overwrites it.
- Cancellation. Every method checks the token when it starts. The token isn't passed on to the cache or lock calls.
Public surface at a glance
| Type / member | Purpose |
|---|---|
CacheDbRecurringDataProvider |
IRecurringDataProvider over ICacheDb |
CacheDbRecurringDataProviderOptions |
Lock delegates, key prefix, default checkpoint retention |
AcquireCacheDbLockAsync |
Delegate shaped like the providers' AcquireLockAsync(ICacheDb, string, DateTime, string?, string?, bool) |
ReleaseCacheDbLockAsync |
Delegate shaped like the providers' ReleaseLockAsync(ICacheDb, string) |
ServiceCollectionExtensions.AddCacheDbRecurringDataProvider |
DI registration |
Related packages
| Package | Role | Availability |
|---|---|---|
Kanject.Core |
The [Recurring] / [RecurringHosted] attributes, generators and scheduling engine |
nuget.org |
Kanject.Core.Recurring.Abstractions |
IRecurringDataProvider, RecurringLeaseResult, RecurringCheckpoint |
nuget.org |
Kanject.Core.CacheDb.Abstractions |
The ICacheDb contract and DbLockConflictException |
nuget.org |
Kanject.Core.CacheDb.Provider.InMemory |
In-process ICacheDb and lock extensions for local development and tests |
nuget.org |
Kanject.Core.CacheDb.Provider.DynamoDb |
Durable ICacheDb and lock extensions on Amazon DynamoDB |
Commercial license (not on nuget.org) |
Kanject.Core.CacheDb.Provider.S3Express |
Durable ICacheDb and lock extensions on Amazon S3 Express One Zone |
Commercial license (not on nuget.org) |
License
Licensed under the Kanject Code Libraries License Agreement (KCLLA); the full text ships in this package as LICENSE.md. Organizations whose trailing-twelve-month gross revenue and total funding raised are each below US$250,000 may use it at no cost under the Free Tier. At or above either threshold a commercial license is required — contact commercial@kanjectbusiness.com.
| 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 is compatible. 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. |
-
net10.0
- Kanject.Core.CacheDb.Abstractions (>= 3.8.1)
- Kanject.Core.Recurring.Abstractions (>= 1.6.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.11)
-
net8.0
- Kanject.Core.CacheDb.Abstractions (>= 3.8.1)
- Kanject.Core.Recurring.Abstractions (>= 1.6.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 8.0.2)
-
net9.0
- Kanject.Core.CacheDb.Abstractions (>= 3.8.1)
- Kanject.Core.Recurring.Abstractions (>= 1.6.1)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.18)
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.7.0 | 75 | 10/5/2026 |
| 1.6.1 | 79 | 10/5/2026 |
| 1.6.0 | 86 | 10/2/2026 |
| 1.5.1 | 116 | 9/27/2026 |
| 1.5.0 | 89 | 9/27/2026 |
| 1.4.7 | 93 | 9/26/2026 |
| 1.4.6 | 126 | 9/7/2026 |
| 1.4.5 | 106 | 8/27/2026 |
| 1.4.4 | 105 | 8/22/2026 |
| 1.4.3 | 126 | 8/10/2026 |
| 1.4.2 | 111 | 8/9/2026 |
| 1.4.1 | 112 | 8/5/2026 |
| 1.4.0 | 113 | 8/5/2026 |
| 1.3.0 | 124 | 8/3/2026 |
| 1.2.15 | 124 | 7/30/2026 |
| 1.2.14 | 130 | 7/18/2026 |
| 1.2.13 | 149 | 7/13/2026 |
| 1.2.12 | 122 | 7/11/2026 |
| 1.2.11 | 117 | 7/11/2026 |
| 1.2.10 | 124 | 7/9/2026 |