Davasorus.Utility.DotNet.Cache
2026.2.2.5
See the version list below for details.
dotnet add package Davasorus.Utility.DotNet.Cache --version 2026.2.2.5
NuGet\Install-Package Davasorus.Utility.DotNet.Cache -Version 2026.2.2.5
<PackageReference Include="Davasorus.Utility.DotNet.Cache" Version="2026.2.2.5" />
<PackageVersion Include="Davasorus.Utility.DotNet.Cache" Version="2026.2.2.5" />
<PackageReference Include="Davasorus.Utility.DotNet.Cache" />
paket add Davasorus.Utility.DotNet.Cache --version 2026.2.2.5
#r "nuget: Davasorus.Utility.DotNet.Cache, 2026.2.2.5"
#:package Davasorus.Utility.DotNet.Cache@2026.2.2.5
#addin nuget:?package=Davasorus.Utility.DotNet.Cache&version=2026.2.2.5
#tool nuget:?package=Davasorus.Utility.DotNet.Cache&version=2026.2.2.5
Davasorus.Utility.DotNet.Cache
A unified, high-performance caching library for .NET 8+ that provides a common interface for multiple cache backends: in-memory, SQLite, and SQL Server.
Features
- Unified Interface: Common
ICacheOperationsinterface across all backends - Multiple Backends: Choose between in-memory (fast), SQLite (persistent, file-based), or SQL Server (distributed)
- Batch Operations: Get/set multiple items efficiently with
GetManyAsyncandSetManyAsync - Smart Caching: Built-in
GetOrSetAsyncprevents cache stampede - Statistics & Monitoring: Track cache performance with
ICacheStatistics - Key Pattern Operations: Search and remove by pattern with
ICacheKeyOperations - Service/Client Pattern: Scoped services with transient clients for dependency injection
- Expiration Support: Sliding and absolute expiration for all backends
- Type-Safe: Fully generic with strong typing
- Production Ready: Comprehensive test coverage (200+ tests)
- Async/Await: First-class async support with cancellation tokens
Installation
dotnet add package Davasorus.Utility.DotNet.Cache
Quick Start
Memory Cache (In-Memory)
Perfect for high-performance, non-persistent caching.
Note: Memory cache stores references, not copies. Values you retrieve from
MemoryCacheServiceare the same instances stored. Do not mutate retrieved objects — that mutation is visible to all subsequent readers of the same key. If you need copy semantics, clone the object after retrieval. (This matches the behavior ofIMemoryCache,ConcurrentDictionary, and other in-process .NET caches.)
using Davasorus.Utility.DotNet.Cache.Configuration;
// Recommended: Use extension methods
services.AddMemoryCacheServices();
// Or with fluent configuration for batch operations
services.AddMemoryCacheServices(cache => cache
.WithParallelBatching()
.WithMaxDegreeOfParallelism(4)
.WithBatchSize(100));
// Usage
public class MyService
{
private readonly IMemoryCacheService _cache;
public MyService(IMemoryCacheService cache)
{
_cache = cache;
}
public async Task<User> GetUserAsync(int userId)
{
var key = $"user:{userId}";
// Get or create with factory pattern
// Factory receives the cache key and a CancellationToken
return await _cache.GetOrSetAsync(key, async (_, ct) =>
{
// This only runs if cache miss
return await _database.GetUserAsync(userId, ct);
}, new CacheOptions
{
SlidingExpiration = TimeSpan.FromMinutes(15)
});
}
}
SQLite Cache (File-Based Persistence)
Ideal for persistent caching in desktop apps or services without database infrastructure.
using Davasorus.Utility.DotNet.Cache.Configuration;
// Recommended: Use extension methods
services.AddSqliteCacheServices();
// Or with fluent configuration
services.AddSqliteCacheServices(cache => cache
.DisableParallelBatching()
.WithBatchSize(50));
// Initialize at startup
var cache = serviceProvider.GetRequiredService<ISqliteCacheService>();
await cache.InitializeAsync("./cache/app-cache.db");
// Usage
public class ProductService
{
private readonly ISqliteCacheService _cache;
public ProductService(ISqliteCacheService cache)
{
_cache = cache;
}
public async Task<List<Product>> GetProductsAsync(string category)
{
var key = $"products:{category}";
var cached = await _cache.GetAsync<List<Product>>(key);
if (cached != null)
return cached;
var products = await _database.GetProductsByCategoryAsync(category);
await _cache.SetAsync(key, products, new CacheOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
return products;
}
// Database maintenance
public async Task PerformMaintenanceAsync()
{
// Remove expired entries
var removed = await _cache.PruneExpiredEntriesAsync();
Console.WriteLine($"Removed {removed} expired entries");
// Compact database
await _cache.CompactDatabaseAsync();
// Get statistics
var stats = await _cache.GetStatisticsAsync();
Console.WriteLine($"Cache size: {stats.SizeInBytes / 1024}KB");
}
}
SQL Server Cache (Distributed)
Best for distributed applications requiring shared cache across multiple servers.
using Davasorus.Utility.DotNet.Cache.Configuration;
// Recommended: Use extension methods
services.AddSqlServerCacheServices();
// Or with fluent configuration
services.AddSqlServerCacheServices(cache => cache
.WithSqlBatchSize(500)
.WithBatchSize(100));
// Initialize at startup
var cache = serviceProvider.GetRequiredService<ISqlServerCacheService>();
await cache.InitializeAsync(
connectionString: "Server=localhost;Database=AppCache;...",
schemaName: "cache",
tableName: "Cache"
);
// Note: You must create the cache table first using dotnet-sql-cache tool:
// dotnet tool install --global dotnet-sql-cache
// dotnet sql-cache create "Server=..." --schema cache --table Cache
// Usage
public class SessionService
{
private readonly ISqlServerCacheService _cache;
public SessionService(ISqlServerCacheService cache)
{
_cache = cache;
}
public async Task<Session> GetSessionAsync(string sessionId)
{
var key = $"session:{sessionId}";
return await _cache.GetAsync<Session>(key);
}
public async Task SaveSessionAsync(string sessionId, Session session)
{
var key = $"session:{sessionId}";
await _cache.SetAsync(key, session, new CacheOptions
{
// Session expires after 20 minutes of inactivity
SlidingExpiration = TimeSpan.FromMinutes(20)
});
}
}
API Reference
Common Interface (ICacheOperations)
All cache services implement this unified interface:
public interface ICacheOperations : IAsyncDisposable
{
// Get single value from cache
Task<T?> GetAsync<T>(string key, CancellationToken cancellationToken = default);
// Get with explicit hit/miss disambiguation (distinguishes key-missing from stored-null)
Task<(bool found, T? value)> TryGetAsync<T>(
string key,
CancellationToken cancellationToken = default
);
// Get multiple values in one operation
Task<Dictionary<string, T?>> GetManyAsync<T>(
IEnumerable<string> keys,
CancellationToken cancellationToken = default
);
// Get from cache or create using factory (prevents cache stampede)
// Factory receives the cache key and a CancellationToken
Task<T?> GetOrSetAsync<T>(
string key,
Func<string, CancellationToken, Task<T>> factory,
CacheOptions? options = null,
CancellationToken cancellationToken = default
);
// Set single value in cache
Task SetAsync<T>(
string key,
T value,
CacheOptions? options = null,
CancellationToken cancellationToken = default
);
// Set multiple values in one operation
Task SetManyAsync<T>(
Dictionary<string, T> items,
CacheOptions? options = null,
CancellationToken cancellationToken = default
);
// Remove value from cache
Task<bool> RemoveAsync(string key, CancellationToken cancellationToken = default);
// Check if key exists
Task<bool> ExistsAsync(string key, CancellationToken cancellationToken = default);
}
Note: Initialization is implementation-specific. Each cache type provides its own initialization method with appropriate parameters (e.g., database path for SQLite, connection string for SQL Server). Memory cache requires no initialization.
Cache Options
public class CacheOptions
{
// Sliding expiration - resets on access
public TimeSpan? SlidingExpiration { get; set; }
// Absolute expiration relative to now
public TimeSpan? AbsoluteExpirationRelativeToNow { get; set; }
// Absolute expiration at specific time
public DateTimeOffset? AbsoluteExpiration { get; set; }
// Priority (Memory cache only)
public CacheItemPriority Priority { get; set; } // Low, Normal, High, NeverRemove
}
Statistics Interface (ICacheStatistics)
Memory and SQLite caches support detailed statistics. All backends return the
canonical CacheStats record with three fields:
public interface ICacheStatistics
{
Task<CacheStats> GetStatisticsAsync(CancellationToken cancellationToken = default);
}
public record CacheStats
{
public long ItemCount { get; init; } // Total items in cache
public long? SizeInBytes { get; init; } // Cache size in bytes (SQLite only; null otherwise)
public long? ExpiredEntryCount { get; init; } // Entries past expiration but not yet pruned (SQLite only)
}
Hit/miss/operation-count metrics are no longer fields on CacheStats. Consume
them from the OTel cache.operations Counter on the backend's Meter instead
(see "OpenTelemetry instrumentation" below).
Key Pattern Operations (ICacheKeyOperations)
Memory and SQLite caches support key pattern matching:
public interface ICacheKeyOperations
{
// Get keys matching pattern (* and ? wildcards)
Task<IEnumerable<string>> GetKeysAsync(
string? pattern = null,
CancellationToken cancellationToken = default
);
// Streaming key traversal — yields keys lazily without buffering the whole result set
IAsyncEnumerable<string> EnumerateKeysAsync(
string? pattern = null,
CancellationToken cancellationToken = default
);
// Remove all keys matching pattern
Task<int> RemoveByPatternAsync(
string pattern,
CancellationToken cancellationToken = default
);
}
// Example usage:
var userKeys = await cache.GetKeysAsync("user:*");
var removedCount = await cache.RemoveByPatternAsync("session:expired:*");
// Streaming variant — useful for large key sets
await foreach (var key in cache.EnumerateKeysAsync("user:*"))
{
// process each key without materializing the full list
}
Memory Cache Specific
// Implements: ICacheOperations, IClearableCache, ICacheStatistics, ICacheKeyOperations
// Synchronous key access
IEnumerable<string> GetAllKeys();
// Cache statistics — async only; returns the canonical CacheStats
Task<CacheStats> GetStatisticsAsync(CancellationToken cancellationToken = default);
SQLite Cache Specific
// Implements: ICacheOperations, IClearableCache, ICacheStatistics, ICacheKeyOperations
// Initialize database (must be called before use)
Task InitializeAsync(string databasePath, CancellationToken cancellationToken = default);
// Cache statistics — returns the canonical CacheStats record
// (ItemCount, SizeInBytes, ExpiredEntryCount)
Task<CacheStats> GetStatisticsAsync(CancellationToken cancellationToken = default);
// Remove expired entries
Task<int> PruneExpiredEntriesAsync(CancellationToken cancellationToken = default);
// Compact database file
Task CompactDatabaseAsync(CancellationToken cancellationToken = default);
SQL Server Cache Specific
// Implements: ICacheOperations only (no statistics or pattern operations)
// Initialize with connection string (must be called before use)
Task InitializeAsync(
string connectionString,
string schemaName = "cache",
string tableName = "CacheData",
CancellationToken cancellationToken = default
);
Recent Breaking Changes
This section documents breaking changes shipped in the most recent release. For older breaking changes, consult the package's git history and GitHub release notes.
Spec B: API consolidation
1. GetStatistics / GetStatisticsAsync returns CacheStats
The backend-specific stats types (CacheStatistics, SqliteCacheStatistics)
have been deleted. All backends now return the canonical CacheStats record
with three fields: ItemCount (long), SizeInBytes (long?), and
ExpiredEntryCount (long?).
// BEFORE
var stats = await sqliteService.GetStatisticsAsync();
var entries = stats.EntryCount;
var dbSize = stats.DatabaseSize;
// AFTER
var stats = await sqliteService.GetStatisticsAsync();
var entries = stats.ItemCount; // long
var bytes = stats.SizeInBytes; // long?
var expired = stats.ExpiredEntryCount; // long?
Memory's GetStatistics() (sync) is replaced by GetStatisticsAsync():
// BEFORE
var stats = memoryService.GetStatistics();
// AFTER
var stats = await memoryService.GetStatisticsAsync();
Hit-rate and operation-count metrics are no longer stats fields — consume
them from the OTel cache.operations Counter on the backend's Meter
instead.
2. GetOrSetAsync factory takes (string key, CancellationToken ct)
The factory delegate signature has changed from Func<Task<T>> to
Func<string, CancellationToken, Task<T>>.
// BEFORE
await cache.GetOrSetAsync("user:42", () => fetchUserAsync(42));
// AFTER (using the key from the factory)
await cache.GetOrSetAsync("user:42", (key, ct) =>
fetchUserAsync(int.Parse(key.Split(':')[1]), ct));
// AFTER (ignoring the new parameters with discards)
await cache.GetOrSetAsync("user:42", (_, _) => fetchUserAsync(42));
Additionally, GetOrSetAsync now distinguishes "key missing" from "key
present with stored null." Previously, a stored null re-invoked the
factory; with the new internal use of TryGetAsync, a stored null is
treated as a cache hit and the factory is not invoked. Consumers
relying on the old behavior should call RemoveAsync(key) before
GetOrSetAsync.
3. Activity-tag operation values shifted
GetAsync is now a thin wrapper over TryGetAsync. As a result, calls
to GetAsync emit metrics under operation="tryGet" (not
operation="get"). Existing dashboards filtering on operation="get"
should be updated to filter on operation="tryGet" (or use
operation IN ("get", "tryGet") to capture both pre- and post-Spec-B
data).
New additive features
Task<(bool found, T? value)> TryGetAsync<T>(string key, CancellationToken)onICacheOperations— distinguishes key-missing from stored-null.IAsyncEnumerable<string> EnumerateKeysAsync(string? pattern, CancellationToken)onICacheKeyOperations— streaming key traversal (Memory + SQLite).
Performance Characteristics
| Backend | Speed | Persistence | Distributed | Statistics | Pattern Ops | Use Case |
|---|---|---|---|---|---|---|
| Memory | Fastest | No | No | Yes | Yes | High-frequency, session data |
| SQLite | Fast | Yes | No | Yes | Yes | Single-server, desktop apps |
| SQL Server | Moderate | Yes | Yes | No | No | Multi-server, distributed applications |
OpenTelemetry instrumentation
The cache services emit OpenTelemetry traces and metrics through the
Davasorus.Utility.DotNet.Telemetry package. All instrumentation is
zero-cost when no listener is attached — safe to leave on in production.
ActivitySources
| Backend | ActivitySource |
|---|---|
| Memory | Davasorus.Utility.Cache.memory |
| SQLite | Davasorus.Utility.Cache.sqlite |
| SqlServer | Davasorus.Utility.Cache.mssql |
Meters
| Backend | Meter |
|---|---|
| Memory | Davasorus.Utility.Cache.memory |
| SQLite | Davasorus.Utility.Cache.sqlite |
| SqlServer | Davasorus.Utility.Cache.mssql |
Activity tags
Standard tags emitted on every operation:
| Tag | Description |
|---|---|
cache.backend |
memory | sqlite | mssql |
cache.result |
hit | miss | ok | removed | cancelled | error |
db.operation.name |
get | set | remove | getOrSet | getMany | setMany | clear | exists | ... |
db.system.name |
sqlite | mssql (Memory backend omits this tag) |
db.collection.name |
<schema>.<table> (SqlServer only, after InitializeAsync) |
cache.lock_waited |
bool — True if the GetOrSet caller waited on a contended per-key lock (i.e., observed stampede protection); false if no contention or fast-path hit. Emitted on getOrSet operations only. |
cache.key is opt-in — set RecordCacheKeys = true (default false)
to stamp the literal key on activity tags. Leave disabled for caches with
high-cardinality keyspaces (user IDs, session tokens) to avoid blowing up
trace storage.
Metrics
| Instrument | Type | Backends | Tags |
|---|---|---|---|
cache.operations |
Counter | all | operation, backend, result |
cache.operation.duration |
Histogram (ms) | all | operation, backend |
cache.errors |
Counter | all | operation, backend, exception.type |
cache.entries |
ObservableGauge | Memory, SQLite | backend |
cache.evictions |
Counter | Memory, SQLite | backend, reason |
SqlServer does not expose cache.entries or cache.evictions — the
underlying IDistributedCache abstraction has no cheap entry-count or
eviction signals.
Configuring cache-key recording
services.AddMemoryCacheServices(cache => cache
.WithCacheKeyRecording(true)); // opts in to stamping cache.key on traces
WithCacheKeyRecording(false) is equivalent to the default and explicitly
disables recording.
Service lifetime requirement
Cache services (IMemoryCacheService, ISqliteCacheService,
ISqlServerCacheService) must be registered as singletons. The
AddMemoryCacheServices / AddSqliteCacheServices / AddSqlServerCacheServices
extensions do this for you. Manual scoped or transient registration causes
the eviction-counter wiring (which takes a strong reference to the cache
client) to retain across DI scopes, leaking memory and producing
inconsistent metrics over the process lifetime.
The cache.entries ObservableGauge is registered exactly once per backend
Meter via a static guard, so it does not leak even under non-singleton
registration — but the eviction wiring still does. Always use the provided
extension methods.
Best Practices
1. Use GetOrSetAsync to Prevent Cache Stampede
// BAD: Multiple requests may hit database simultaneously
var cached = await cache.GetAsync<Data>(key);
if (cached == null)
{
cached = await ExpensiveOperation(); // Multiple threads may execute this
await cache.SetAsync(key, cached);
}
// GOOD: Only one request hits database
// Factory receives (cache key, CancellationToken)
var result = await cache.GetOrSetAsync(key, async (_, ct) =>
await ExpensiveOperation(ct) // Only executed once
);
2. Choose Appropriate Expiration
// Sliding: Resets on access (good for user sessions)
SlidingExpiration = TimeSpan.FromMinutes(20)
// Absolute: Expires at specific time (good for time-sensitive data)
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
AbsoluteExpiration = DateTimeOffset.UtcNow.AddHours(1)
3. Use Meaningful Cache Keys
// BAD: Unclear
var key = "u123";
// GOOD: Clear namespace and purpose
var key = $"user:profile:{userId}";
var key = $"products:category:{categoryId}:page:{pageNum}";
4. Handle Nulls Appropriately
var user = await cache.GetAsync<User>(key);
// GetAsync returns null for cache miss AND when null was cached.
// If you need to distinguish, prefer TryGetAsync (single round-trip):
var (found, cachedUser) = await cache.TryGetAsync<User>(key);
if (!found)
{
// Definitely a cache miss
}
else
{
// Hit (cachedUser may legitimately be null if a null was stored)
}
5. SQLite Maintenance
// Run maintenance periodically (e.g., daily background job)
await cache.PruneExpiredEntriesAsync(); // Remove expired entries
await cache.CompactDatabaseAsync(); // Reclaim space
6. SQL Server Table Setup
// Must create table before using SQL Server cache
// Use dotnet-sql-cache tool:
// dotnet tool install --global dotnet-sql-cache
// dotnet sql-cache create "YourConnectionString" --schema cache --table Cache
Cache stampede prevention via GetOrSetAsync
GetOrSetAsync provides built-in stampede prevention: when multiple
callers concurrently request the same key with a missing value, only
one caller's factory delegate is invoked. The other callers wait for
the factory to complete and receive the same cached result.
Lock granularity is per-key: callers requesting different keys run in parallel. The lock is held only for the duration of the factory plus the cache write, then released. Lock cleanup is automatic — the internal lock map self-drains when no waiters remain.
Cancellation: if a caller's CancellationToken fires while waiting
for the lock or during the factory, the caller observes
OperationCanceledException immediately. The cache state is not
modified by a cancelled caller.
Factory exceptions: if the designated factory caller throws, the exception propagates to that caller. Other waiters retry: they acquire the lock next, check the cache (still empty), and become the new designated factory caller. This "thundering retry" pattern is idiomatic for cache stampedes; if you want exception-caching semantics (where a transient error is briefly cached to avoid hammering an unhealthy backend), see the future stale-while-revalidate work tracked under Spec G.
Testing
The package includes 200+ tests covering all functionality:
cd "DotNet/12 Davasorus.Utility.DotNet.Cache"
dotnet test --configuration Release
All tests pass, including:
- Core cache operations (Get, Set, Remove, Exists)
- Batch operations (GetMany, SetMany)
- GetOrSetAsync pattern
- Expiration (sliding and absolute)
- Statistics and key pattern operations
- Service layer validation and logging
- Configuration and DI extension methods
- SQL Server integration tests with Testcontainers
Running benchmarks
The package includes a BenchmarkDotNet suite for measuring per-backend performance. Benchmarks run on-demand (not in CI) — full BDN runs take minutes per class, well beyond CI budget.
cd Davasorus.Utility.DotNet.Cache.Benchmarks
# Run every benchmark on .NET 8:
dotnet run -c Release --framework net8.0
# Same on .NET 10:
dotnet run -c Release --framework net10.0
# Filter to a specific class:
dotnet run -c Release --framework net8.0 -- --filter '*Memory*'
# List all benchmarks without running:
dotnet run -c Release --framework net8.0 -- --list flat
The benchmark project multi-targets net8.0;net10.0, so dotnet run
requires an explicit --framework <tfm> flag.
SqlServer benchmarks require Docker running locally (Testcontainers spins up a SQL Server 2022 container). Memory and SQLite benchmarks have no external dependencies.
Results land in Davasorus.Utility.DotNet.Cache.Benchmarks/BenchmarkDotNet.Artifacts/results/
(gitignored). Use them as a baseline when evaluating perf-sensitive
changes (Spec D per-key locking, Spec E pluggable serializer +
compression, etc.).
Advanced Usage
Custom Serialization
// By default, System.Text.Json is used
// Complex types are automatically serialized/deserialized
public class ComplexObject
{
public int Id { get; set; }
public List<string> Tags { get; set; }
public Dictionary<string, object> Metadata { get; set; }
}
await cache.SetAsync("key", new ComplexObject { ... });
var obj = await cache.GetAsync<ComplexObject>("key");
Batch Operations
// Retrieve multiple cache entries at once
var keys = new[] { "user:1", "user:2", "user:3" };
var results = await cache.GetManyAsync<User>(keys);
foreach (var kvp in results)
{
Console.WriteLine($"{kvp.Key}: {kvp.Value?.Name ?? "not found"}");
}
// Set multiple cache entries at once
var users = new Dictionary<string, User>
{
["user:1"] = new User { Id = 1, Name = "Alice" },
["user:2"] = new User { Id = 2, Name = "Bob" },
["user:3"] = new User { Id = 3, Name = "Charlie" }
};
await cache.SetManyAsync(users, new CacheOptions
{
SlidingExpiration = TimeSpan.FromMinutes(15)
});
Statistics and Monitoring
// Get cache statistics (Memory and SQLite only)
var stats = await cache.GetStatisticsAsync();
Console.WriteLine($"Items: {stats.ItemCount}");
Console.WriteLine($"Size: {stats.SizeInBytes / 1024}KB"); // SQLite only; null on memory backend
Console.WriteLine($"Expired: {stats.ExpiredEntryCount}"); // SQLite only; null on memory backend
// Hit rate, hit/miss counts, and oldest-entry timestamps are no longer fields on
// CacheStats. Consume those from the OTel `cache.operations` Counter on the
// backend's Meter (see "OpenTelemetry instrumentation" above).
Key Pattern Operations
// Get all keys matching a pattern (Memory and SQLite only)
var sessionKeys = await cache.GetKeysAsync("session:*");
var userKeys = await cache.GetKeysAsync("user:profile:*");
// Remove all keys matching a pattern
var removed = await cache.RemoveByPatternAsync("temp:*");
Console.WriteLine($"Removed {removed} temporary items");
// Clear specific user's cache
await cache.RemoveByPatternAsync($"user:{userId}:*");
Dependency Injection Setup
Recommended: Extension Methods
using Davasorus.Utility.DotNet.Cache.Configuration;
public void ConfigureServices(IServiceCollection services)
{
// Memory cache (with defaults)
services.AddMemoryCacheServices();
// SQLite cache (with defaults)
services.AddSqliteCacheServices();
// SQL Server cache (with defaults)
services.AddSqlServerCacheServices();
// Or with fluent configuration
services.AddMemoryCacheServices(cache => cache
.WithParallelBatching()
.WithMaxDegreeOfParallelism(4)
.WithBatchSize(100));
// Or from appsettings.json
services.AddMemoryCacheServices(Configuration.GetSection("CacheOptions"));
}
Manual Registration
// Memory cache — service and client must both be singletons
services.AddMemoryCache();
services.AddSingleton<IMemoryCacheClient, MemoryCacheClient>();
services.AddSingleton<IMemoryCacheService, MemoryCacheService>();
// SQLite cache — service must be singleton
services.AddSingleton<ISqliteCacheClient, SqliteCacheClient>();
services.AddSingleton<ISqliteCacheService, SqliteCacheService>();
// SQL Server cache — service must be singleton; client stays transient
services.AddTransient<ISqlServerCacheClient, SqlServerCacheClient>();
services.AddSingleton<ISqlServerCacheService, SqlServerCacheService>();
Initialization
// SQLite and SQL Server caches require initialization at startup
var sqliteCache = serviceProvider.GetRequiredService<ISqliteCacheService>();
await sqliteCache.InitializeAsync("./data/cache.db");
var sqlServerCache = serviceProvider.GetRequiredService<ISqlServerCacheService>();
await sqlServerCache.InitializeAsync(Configuration.GetConnectionString("Cache"));
Troubleshooting
SQL Server: "Invalid object name 'cache.Cache'"
Solution: Create the cache table using dotnet-sql-cache tool before initializing.
dotnet tool install --global dotnet-sql-cache
dotnet sql-cache create "Server=...;Database=..." --schema cache --table Cache
SQLite: Database file locked
Solution: Ensure only one process accesses the database, or use Cache = SqliteCacheMode.Shared in connection string (already default).
Memory: High memory usage
Solution: Set cache priorities and size limits:
services.AddMemoryCache(options =>
{
options.SizeLimit = 1024; // Limit total size
options.CompactionPercentage = 0.25; // Compact by 25% when limit hit
});
// Use priorities
await cache.SetAsync(key, value, new CacheOptions
{
Priority = CacheItemPriority.Low // Will be evicted first
});
Migration Guide
From IMemoryCache to This Package
// Before
_memoryCache.Set("key", value, TimeSpan.FromMinutes(5));
var cached = _memoryCache.Get<MyType>("key");
// After
await _cache.SetAsync("key", value, new CacheOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5)
});
var cached = await _cache.GetAsync<MyType>("key");
From IDistributedCache to This Package
// Before
var bytes = await _distributedCache.GetAsync("key");
var value = JsonSerializer.Deserialize<MyType>(bytes);
// After
var value = await _cache.GetAsync<MyType>("key");
License
This package follows the repository license.
Contributing
Contributions are welcome! Please follow the repository's contribution guidelines in NEWCOMERS.md.
Support
For issues, questions, or feature requests, please refer to the repository's issue tracker.
| 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 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
- Davasorus.Utility.DotNet.Contracts.Types (>= 2026.2.2.1)
- Davasorus.Utility.DotNet.Telemetry (>= 2026.2.2.1)
- Microsoft.Data.Sqlite (>= 10.0.7)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Caching.Memory (>= 10.0.7)
- Microsoft.Extensions.Caching.SqlServer (>= 10.0.7)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
-
net8.0
- Davasorus.Utility.DotNet.Contracts.Types (>= 2026.2.2.1)
- Davasorus.Utility.DotNet.Telemetry (>= 2026.2.2.1)
- Microsoft.Data.Sqlite (>= 10.0.7)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Caching.Memory (>= 10.0.7)
- Microsoft.Extensions.Caching.SqlServer (>= 10.0.7)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.7)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.7)
- Microsoft.Extensions.Options (>= 10.0.7)
- System.Text.Json (>= 10.0.7)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Davasorus.Utility.DotNet.Cache:
| Package | Downloads |
|---|---|
|
Davasorus.Utility.DotNet.Auth
Handles Authentication for TEPS Utilities |
|
|
Davasorus.Utility.DotNet.Api
API Interaction for TEPS Utilities with generic deserialization, configurable error reporting, and improved DI configuration. Supports REST, GraphQL, gRPC, WebSocket, SignalR, and SSE protocols. |
|
|
Davasorus.Utility.DotNet.Services
Windows Service management (start, stop, enable, disable, enumerate) for TEPS Utilities, with OpenTelemetry tracing and DI-based wiring. Targets .NET 8 and .NET 10. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2026.2.2.14 | 404 | 5/17/2026 |
| 2026.2.2.13 | 560 | 5/15/2026 |
| 2026.2.2.12 | 82 | 5/15/2026 |
| 2026.2.2.11 | 427 | 5/12/2026 |
| 2026.2.2.10 | 277 | 5/9/2026 |
| 2026.2.2.9 | 97 | 5/9/2026 |
| 2026.2.2.8 | 99 | 5/9/2026 |
| 2026.2.2.7 | 95 | 5/9/2026 |
| 2026.2.2.6 | 95 | 5/9/2026 |
| 2026.2.2.5 | 95 | 5/9/2026 |
| 2026.2.2.4 | 98 | 5/8/2026 |
| 2026.2.2.3 | 112 | 5/8/2026 |
| 2026.2.2.2 | 108 | 5/8/2026 |
| 2026.2.2.1 | 92 | 5/7/2026 |
| 2026.2.1.2 | 3,730 | 4/9/2026 |
| 2026.2.1.1 | 1,120 | 4/1/2026 |
| 2026.1.3.4 | 616 | 3/29/2026 |
| 2026.1.3.3 | 636 | 3/24/2026 |
| 2026.1.3.2 | 1,246 | 3/13/2026 |
| 2026.1.3.1 | 534 | 3/10/2026 |