Kanject.Core.CacheDb.Provider.InMemory
3.6.0
Prefix Reserved
See the version list below for details.
dotnet add package Kanject.Core.CacheDb.Provider.InMemory --version 3.6.0
NuGet\Install-Package Kanject.Core.CacheDb.Provider.InMemory -Version 3.6.0
<PackageReference Include="Kanject.Core.CacheDb.Provider.InMemory" Version="3.6.0" />
<PackageVersion Include="Kanject.Core.CacheDb.Provider.InMemory" Version="3.6.0" />
<PackageReference Include="Kanject.Core.CacheDb.Provider.InMemory" />
paket add Kanject.Core.CacheDb.Provider.InMemory --version 3.6.0
#r "nuget: Kanject.Core.CacheDb.Provider.InMemory, 3.6.0"
#:package Kanject.Core.CacheDb.Provider.InMemory@3.6.0
#addin nuget:?package=Kanject.Core.CacheDb.Provider.InMemory&version=3.6.0
#tool nuget:?package=Kanject.Core.CacheDb.Provider.InMemory&version=3.6.0
Kanject.Core.CacheDb.Provider.InMemory
An in-process implementation of ICacheDb (from Kanject.Core.CacheDb.Abstractions), backed by IDistributedCache, with process-local lock extensions. Use it for local development, tests and single-process tools. For anything that runs more than one instance or must keep data across restarts, switch to a durable provider. Your code doesn't change, because it only depends on ICacheDb.
Installation
dotnet add package Kanject.Core.CacheDb.Provider.InMemory
Targets .NET 8, .NET 9 and .NET 10. Kanject.Core.CacheDb.Abstractions comes in transitively. The package is marked trimming / Native AOT compatible. The string members use source-generated JSON internally. The generic typed members use reflection-based JSON and are annotated [RequiresUnreferencedCode] / [RequiresDynamicCode]. For typed values in trimmed apps, use the JsonSerializerContext extensions from the abstractions package.
Quick start
using Kanject.Core.CacheDb.Provider.InMemory.Extensions;
builder.Services.AddInMemoryCacheDb("pricing-api");
using Kanject.Core.CacheDb.Abstractions;
public sealed class ExchangeRateService(ICacheDb cache, IRatesClient rates)
{
public async Task<string> GetRatesJsonAsync(string currency, CancellationToken cancellationToken)
{
var key = cache.FormatCacheKey("rates", currency); // "USD" -> "pricing-api_rates:usd"
if (cache.TryGetCacheData(key, out var cached) && !string.IsNullOrEmpty(cached))
return cached;
var json = await rates.FetchLatestJsonAsync(currency, cancellationToken);
await cache.CacheDataAsync(key, json, DateTime.UtcNow.AddMinutes(5), cancellationToken);
return json;
}
}
public interface IRatesClient
{
Task<string> FetchLatestJsonAsync(string currency, CancellationToken cancellationToken);
}
What registration does
AddInMemoryCacheDb(string @namespace) does three things:
- Calls
AddDistributedMemoryCache(), which registers the in-processMemoryDistributedCacheasIDistributedCache. It usesTryAdd, so if the container already has anotherIDistributedCache,InMemoryCacheDbwrites through that one instead. - Registers
InMemoryCacheDbas the singletonICacheDb. - Sets the static
InMemoryCacheDb.InstanceNameto@namespace. Because it is static, it applies to the whole process: the last value set is used for every instance.
AddServiceResponseCache(string instanceName) does exactly the same thing under another name. Neither method returns the service collection, so you can't chain calls on them.
Behavior
Keys. FormatCacheKey joins fragments with :, replaces spaces with _, lowercases the result and adds {namespace}_ in front. The prefix is skipped if the key already starts with the namespace. Every other member uses the key exactly as you pass it, so build keys with FormatCacheKey to keep them namespaced.
Expiry.
- String writes store the expiry in the entry and also set it as the memory cache's absolute expiration. The entry is evicted at that time and checked again on read.
- Typed (generic) writes store the expiry only in the envelope. It is checked when the entry is read, and an expired entry is removed at that point. Until then, it stays in memory.
duration: nullmeans no expiry. This provider doesn't applyICacheDb.DefaultCacheDuration, and neither do the DynamoDB and S3 Express providers. The entry lives until you remove it or the process exits. PassICacheDb.DefaultCacheDurationif you want the shared 10-minute default.- Empty values. Writes with a
nullpayload, anullor empty key, or (for strings) an empty payload are ignored. On read, an entry with an empty payload is removed and reported as a miss.
Misses. Every read reports a miss the same way: TryGetCacheData returns false with value set to null (or default for the typed overloads), and TryGetCacheDataAsync returns (false, null) / (false, default). That covers missing keys, expired entries and stored entries with an empty payload. Rely on the flag: exists: true always comes with the stored value.
Earlier releases reported a string miss as
value = "", and the stringTryGetCacheDataAsyncreturnedexists: truefor it. The typedTryGetCacheDataAsync<T>did the same for value types (for example(true, 0)for a missingint). On those releases, check the value as well as the flag.
Errors. Every member catches its own exceptions and reports failure as false or a miss. Nothing throws on a store error.
Console output. Every write, cache hit, expiry and caught exception is printed to standard output through Kanject.Core's PrintInConsole. Messages for string writes include the stored value. That helps in a dev loop. It's another reason to keep this provider out of shared or production environments.
Async and cancellation. The async members finish synchronously. The CancellationToken overloads check the token before they start and do nothing else with it.
Scope and concurrency
Thread-safe within one process.
MemoryDistributedCachehandles cache entries. The lock registry is aConcurrentDictionaryupdated with compare-and-swap loops.Only as big as one process. Cached entries and locks live in the current process's memory. They disappear on restart. Other processes, containers, replicas and separately scaled serverless instances can't see them.
Not for:
- Multi-instance production deployments
- Invalidating a cache across instances
- Locks that coordinate replicas
- Data that must survive a restart, such as recurring-job checkpoints
For those, use the DynamoDB or S3 Express provider.
Process-local locks
DbLockExtensions (in Kanject.Core.CacheDb.Provider.InMemory.Extensions) has the same AcquireLockAsync / ReleaseLockAsync signatures as the durable providers' lock extensions. Code written against it keeps working when you switch providers.
using Kanject.Core.CacheDb.Abstractions;
using Kanject.Core.CacheDb.Abstractions.Exceptions;
using Kanject.Core.CacheDb.Abstractions.Extensions;
using Kanject.Core.CacheDb.Provider.InMemory.Extensions;
public sealed class IndexRebuilder(ICacheDb cache)
{
public async Task RunAsync()
{
try
{
await cache.AcquireLockAsync("rebuild-index", DateTime.UtcNow.AddMinutes(2), comment: "nightly rebuild");
}
catch (DbLockConflictException conflict)
{
Console.WriteLine($"Skipped: {conflict.LockId} is held for another {conflict.LockDuration.ToReadableString()}");
return;
}
try
{
await RebuildAsync();
}
finally
{
await cache.ReleaseLockAsync("rebuild-index");
}
}
private Task RebuildAsync() => Task.CompletedTask; // your work
}
AcquireLockAsync(lockId, duration, comment, data, overrideData = true)takes the lock if it's free or has expired.durationis the absolute expiry. If another caller holds an active lock with the same id, it throwsDbLockConflictExceptionand increments that lock'sInvokeCount. WithoverrideData: true, the new caller'sdatareplaces the holder's. Withfalse, the holder'sdatais kept.ReleaseLockAsync(lockId)removes the lock without checking who holds it. It does nothing if the lock doesn't exist.SeekLockAsync(lockId)returns(lockId, createdOn, duration, invokeCount, comment), ornullwhen the lock is missing or has expired.- The registry is static. All
InMemoryCacheDbinstances in the process share it. It doesn't useIDistributedCacheor the namespace prefix. Expired locks are removed only when accessed, not by a background sweep. - Provider check. All three methods throw
NotSupportedExceptionwhen theICacheDbisn't anInMemoryCacheDb. - No counter or seat locks. This package doesn't have them.
Public surface at a glance
| Type / member | Purpose |
|---|---|
InMemoryCacheDb |
ICacheDb implementation over IDistributedCache |
InMemoryCacheDb.InstanceName |
Static key prefix used by FormatCacheKey, set during registration |
ServiceCollectionsExtension.AddInMemoryCacheDb(string) |
Registers the provider as a singleton ICacheDb |
ServiceCollectionsExtension.AddServiceResponseCache(string) |
Same as AddInMemoryCacheDb under another name |
DbLockExtensions.AcquireLockAsync / ReleaseLockAsync / SeekLockAsync |
Process-local named locks |
Related packages
| Package | Role | Availability |
|---|---|---|
Kanject.Core.CacheDb.Abstractions |
The ICacheDb contract, typed-value helpers and lock exceptions |
nuget.org |
Kanject.Core.Recurring.Provider.CacheDb |
Recurring-job leases and checkpoints over ICacheDb; pairs with this provider for local development |
nuget.org |
Kanject.Core.CacheDb.Provider.DynamoDb |
Durable, multi-instance ICacheDb on Amazon DynamoDB |
Commercial license (not on nuget.org) |
Kanject.Core.CacheDb.Provider.S3Express |
Durable, multi-instance ICacheDb 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.0)
-
net8.0
- Kanject.Core.CacheDb.Abstractions (>= 3.8.0)
-
net9.0
- Kanject.Core.CacheDb.Abstractions (>= 3.8.0)
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 |
|---|---|---|
| 3.8.0 | 3 | 10/5/2026 |
| 3.7.1 | 37 | 10/5/2026 |
| 3.7.0 | 54 | 10/2/2026 |
| 3.6.1 | 109 | 9/27/2026 |
| 3.6.0 | 83 | 9/27/2026 |
| 3.5.7 | 88 | 9/26/2026 |
| 3.5.6 | 118 | 9/7/2026 |
| 3.5.5 | 103 | 8/27/2026 |
| 3.5.4 | 105 | 8/22/2026 |
| 3.5.3 | 109 | 8/10/2026 |
| 3.5.2 | 105 | 8/9/2026 |
| 3.5.1 | 109 | 8/5/2026 |
| 3.5.0 | 109 | 8/5/2026 |
| 3.4.0 | 116 | 8/3/2026 |
| 3.3.8 | 119 | 7/30/2026 |
| 3.3.7 | 127 | 7/18/2026 |
| 3.3.6 | 144 | 7/13/2026 |
| 3.3.5 | 118 | 7/11/2026 |
| 3.3.4 | 120 | 7/11/2026 |
| 3.3.3 | 120 | 7/9/2026 |