Davasorus.Utility.DotNet.Cache 2026.2.1.2

There is a newer version of this package available.
See the version list below for details.
dotnet add package Davasorus.Utility.DotNet.Cache --version 2026.2.1.2
                    
NuGet\Install-Package Davasorus.Utility.DotNet.Cache -Version 2026.2.1.2
                    
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="Davasorus.Utility.DotNet.Cache" Version="2026.2.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Davasorus.Utility.DotNet.Cache" Version="2026.2.1.2" />
                    
Directory.Packages.props
<PackageReference Include="Davasorus.Utility.DotNet.Cache" />
                    
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 Davasorus.Utility.DotNet.Cache --version 2026.2.1.2
                    
#r "nuget: Davasorus.Utility.DotNet.Cache, 2026.2.1.2"
                    
#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 Davasorus.Utility.DotNet.Cache@2026.2.1.2
                    
#: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=Davasorus.Utility.DotNet.Cache&version=2026.2.1.2
                    
Install as a Cake Addin
#tool nuget:?package=Davasorus.Utility.DotNet.Cache&version=2026.2.1.2
                    
Install as a Cake Tool

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 ICacheOperations interface 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 GetManyAsync and SetManyAsync
  • Smart Caching: Built-in GetOrSetAsync prevents 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.

using Tyler.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
		return await _cache.GetOrSetAsync(key, async () =>
		{
			// This only runs if cache miss
			return await _database.GetUserAsync(userId);
		}, new CacheOptions
		{
			SlidingExpiration = TimeSpan.FromMinutes(15)
		});
	}
}

SQLite Cache (File-Based Persistence)

Ideal for persistent caching in desktop apps or services without database infrastructure.

using Tyler.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.DatabaseSize / 1024}KB");
	}
}

SQL Server Cache (Distributed)

Best for distributed applications requiring shared cache across multiple servers.

using Tyler.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 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)
	Task<T?> GetOrSetAsync<T>(
		string key,
		Func<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:

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 (SQLite only)
	public long? HitCount { get; init; }           // Cache hits (if tracked)
	public long? MissCount { get; init; }          // Cache misses (if tracked)
	public DateTimeOffset? OldestEntry { get; init; } // Oldest entry timestamp
	public double? HitRate { get; }                // Calculated hit rate %
}

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
	);

	// 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:*");

Memory Cache Specific

// Implements: ICacheOperations, IClearableCache, ICacheStatistics, ICacheKeyOperations

// Synchronous key access
IEnumerable<string> GetAllKeys();

// Cache statistics
CacheStatistics GetStatistics();

SQLite Cache Specific

// Implements: ICacheOperations, IClearableCache, ICacheStatistics, ICacheKeyOperations

// Initialize database (must be called before use)
Task InitializeAsync(string databasePath, CancellationToken cancellationToken = default);

// Cache statistics (entry count, database size, expired entry count)
Task<SqliteCacheStatistics> 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
);

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

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
var result = await cache.GetOrSetAsync(key, async () =>
	await ExpensiveOperation() // 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, use ExistsAsync
if (!await cache.ExistsAsync(key))
{
	// Definitely a cache miss
}

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

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

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");
Console.WriteLine($"Hit Rate: {stats.HitRate:F2}%");
Console.WriteLine($"Oldest: {stats.OldestEntry}");

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

using Tyler.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
services.AddMemoryCache();
services.AddTransient<IMemoryCacheClient, MemoryCacheClient>();
services.AddScoped<IMemoryCacheService, MemoryCacheService>();

// SQLite cache
services.AddSingleton<ISqliteCacheClient, SqliteCacheClient>();
services.AddScoped<ISqliteCacheService, SqliteCacheService>();

// SQL Server cache
services.AddTransient<ISqlServerCacheClient, SqlServerCacheClient>();
services.AddScoped<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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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 405 5/17/2026
2026.2.2.13 562 5/15/2026
2026.2.2.12 82 5/15/2026
2026.2.2.11 428 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 113 5/8/2026
2026.2.2.2 108 5/8/2026
2026.2.2.1 92 5/7/2026
2026.2.1.2 3,742 4/9/2026
2026.2.1.1 1,120 4/1/2026
2026.1.3.4 616 3/29/2026
2026.1.3.3 637 3/24/2026
2026.1.3.2 1,246 3/13/2026
2026.1.3.1 535 3/10/2026
Loading failed