Nextended.Cache
📚 Vollständige API-Referenz — jeder öffentliche Typ und Member, erzeugt aus der kompilierten Assembly.
Ausdrucksbasiertes Caching: Der Cache-Key entsteht aus dem Aufruf selbst, nicht aus einer von Hand getippten Zeichenkette.
Installation
dotnet add package Nextended.CacheDer Gedanke
Cache-Code besteht meistens aus denselben vier Zeilen: Key bauen, nachsehen, teure Operation ausführen, ablegen. Der Key ist die Stelle, an der es schiefgeht — er wird von Hand geschrieben, vergisst einen Parameter, und zwei Aufrufstellen sind sich uneinig.
Nextended.Cache leitet den Key stattdessen aus dem Aufrufausdruck ab. Sie übergeben den Aufruf, den Sie sonst gemacht hätten; der Key entsteht aus deklarierendem Typ, Ergebnistyp, Methodenname und den tatsächlichen Argumentwerten.
Einen Methodenaufruf cachen
using Nextended.Cache;
public class UserService
{
private readonly CacheProvider _cache = new();
public User GetUser(int userId)
// Das Lambda erhält die Instanz, damit der Ausdruck auswertbar ist.
=> _cache.ExecuteWithCache(this, self => self.LoadUserFromDb(userId));
private User LoadUserFromDb(int userId) => /* der teure Aufruf */;
}Der erzeugte Key sieht so aus:
MyApp.UserService_MyApp.User=UserService.LoadUserFromDb(userId=42)Eine andere userId ist damit automatisch ein anderer Eintrag.
Der Lambda-Rumpf muss ein Methodenaufruf sein
self => self.Property oder ein Closure ohne Aufruf (() => Load(id)) lässt sich nicht in einen Key übersetzen und wirft eine InvalidCastException.
Direkt auf IMemoryCache
using Nextended.Cache.Extensions;
var info = this.ExecuteWithCache(memoryCache, self => self.LoadReport(reportId));
info.Result; // der Wert
info.Key; // der erzeugte Key
info.IsNewEntry; // false, wenn er aus dem Cache kamExecuteWithCache nimmt auch einen expliziten Key, wenn Sie einen wollen, sowie MemoryCacheEntryOptions für Ablauf und Priorität. cache.MemoryCacheEntryOptions(token) baut einen sinnvollen Standard: normale Priorität, eine Stunde absoluter Ablauf, gebunden an ein Cancellation-Token.
Bedingte Invalidierung
CacheProvider kann sich selbst beobachten und alles verwerfen, sobald eine Bedingung zutrifft. Die Bedingungen werden in einem Hintergrund-Task alle ClearCheckInterval ausgewertet (Standard: 10 Minuten).
var cache = new CacheProvider();
cache.ClearCheckInterval = TimeSpan.FromMinutes(1);
cache.ClearWhen(c => (DateTime.Now - c.LastWriteTime).TotalHours > 1)
.ClearWhen(c => c.Count() > 10_000);
cache.Cleared += (_, _) => logger.LogInformation("Cache verworfen");Clear() arbeitet über ein Ablauf-Token, gegen das jeder Eintrag registriert ist. Ein Verwerfen ist damit O(1) und nicht eine Enumeration über alle Einträge.
Thread-sichere Initialisierung für ObjectCache
Beim klassischen AddOrGetExisting von System.Runtime.Caching läuft Ihre Factory bereits, bevor Sie den vorhandenen Eintrag zurückbekommen. Die Variante hier verpackt die Factory in ein Lazy<T>, sodass der teure Aufruf auch unter gleichzeitigem Zugriff genau einmal erfolgt.
using System.Runtime.Caching;
using Nextended.Cache.Extensions;
var rates = MemoryCache.Default.AddOrGetExisting(
"exchange-rates",
() => LoadExchangeRates(),
DateTimeOffset.Now.AddMinutes(10));Konfiguration
var cache = new CacheProvider(
memoryCache,
new MemoryCacheEntryOptions()
.SetPriority(CacheItemPriority.High)
.SetAbsoluteExpiration(TimeSpan.FromMinutes(15)));Alternativ mit IOptions<MemoryCacheOptions>; ohne Argument legt der Provider einen eigenen MemoryCache an.
| Member | Zweck |
|---|---|
CacheEntryOptions | Standardoptionen für alles, was dieser Provider ablegt |
ClearCheckInterval | Wie oft die ClearWhen-Bedingungen geprüft werden |
LastWriteTime | Zeitstempel des letzten neuen Eintrags — die übliche Grundlage für eine ClearWhen-Bedingung |
Count() | Aktuelle Anzahl der Einträge |
Clear() | Alles verwerfen und Cleared auslösen |
Cleared | Ereignis nach dem Verwerfen |
Praxishinweise
Nur cachen, was teuer ist. Der Key-Aufbau kostet selbst etwas Reflection über den Ausdruck; für einen Aufruf im Mikrosekundenbereich lohnt das nicht.
Ablaufzeit zur Datenlage passend wählen. Der Standard von einer Stunde ist für Stammdaten sinnvoll, für einen Warenkorb nicht.
Keine veränderlichen Objekte teilen. Was im Cache liegt, wird von allen Aufrufern geteilt. Wenn Aufrufer das Ergebnis verändern, legen Sie eine Kopie ab — etwa mit CloneDeep() aus Nextended.Core.
Bedingungen billig halten. ClearWhen-Prädikate laufen periodisch im Hintergrund; sie sollten keine Datenbank befragen.
Zusammenspiel
Nextended.Imaging hängt von diesem Paket ab, sodass sich Thumbnail-Erzeugung direkt cachen lässt:
var bytes = _cache.ExecuteWithCache(this, self => self.BuildThumbnail(imageId, 200));Unterstützte Frameworks
net8.0net9.0net10.0
Plattform
Plattformübergreifend.
Abhängigkeiten
- Nextended.Core
Microsoft.Extensions.Caching.AbstractionsMicrosoft.Extensions.Caching.MemorySystem.Runtime.Caching