Sage.Caching.Memory
1.0.0.7
See the version list below for details.
dotnet add package Sage.Caching.Memory --version 1.0.0.7
NuGet\Install-Package Sage.Caching.Memory -Version 1.0.0.7
<PackageReference Include="Sage.Caching.Memory" Version="1.0.0.7" />
<PackageVersion Include="Sage.Caching.Memory" Version="1.0.0.7" />
<PackageReference Include="Sage.Caching.Memory" />
paket add Sage.Caching.Memory --version 1.0.0.7
#r "nuget: Sage.Caching.Memory, 1.0.0.7"
#:package Sage.Caching.Memory@1.0.0.7
#addin nuget:?package=Sage.Caching.Memory&version=1.0.0.7
#tool nuget:?package=Sage.Caching.Memory&version=1.0.0.7
Sage.Caching.Memory
Sage.Caching.Memory 是对 Microsoft.Extensions.Caching.Memory 的轻量封装,提供了更直接的缓存读写接口,支持:
- 线程安全的内存缓存操作
Region分组管理与分组清理- 同步 / 异步缓存创建
- 可选统计信息
KeyPrefix键前缀隔离- AOT 兼容
适合在 ASP.NET Core、WebApi、Minimal API、Native AOT 项目中直接使用。
适用场景
- 单机 WebApi 的热点数据缓存
- 查询结果缓存
- 下游接口返回值短时缓存
- 按模块或业务分组清理缓存
不适合:
- 多实例共享缓存
- 需要跨进程一致性的缓存
- 需要持久化的缓存
这类场景建议使用 Redis 等分布式缓存。
安装
dotnet add package Sage.Caching.Memory
快速开始
1. 注册服务
using Microsoft.Extensions.Caching.Memory;
using Sage.Caching.Memory.Extensions;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCacheService(options =>
{
// 是否启用统计
options.EnableStats = false;
// 当工厂方法创建缓存失败时,是否直接抛出异常
options.ThrowOnCreateError = true;
// 键前缀,建议按应用或服务名设置
options.KeyPrefix = "WebService.Callback";
// 是否启用类型检查
options.EnableTypeChecking = false;
// 最大缓存大小限制
options.MaximumCacheSize = 10000;
// 到达内存压力时压缩比例
options.CompactionPercentage = 0.10;
// 过期扫描频率
options.ExpirationScanFrequency = TimeSpan.FromMinutes(1);
// 默认缓存项配置
options.DefaultEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10),
Size = 1,
Priority = CacheItemPriority.Normal
};
});
var app = builder.Build();
app.Run();
2. 在 WebApi Minimal(AOT) 中使用
推荐直接在 endpoint 参数中注入 ICacheService:
using Microsoft.Extensions.Caching.Memory;
using Sage.Caching.Memory.Services;
app.MapGet("/callbacks/{id}", async (string id, ICacheService cacheService) =>
{
var result = await cacheService.GetOrCreateAsync(
key: $"callback:{id}",
valueFactory: async () =>
{
await Task.Delay(50);
return new
{
Id = id,
Name = $"Callback-{id}",
Time = DateTimeOffset.UtcNow
};
},
region: "Callback",
options: new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5),
SlidingExpiration = TimeSpan.FromMinutes(2),
Size = 1,
Priority = CacheItemPriority.Normal
});
return Results.Ok(result);
});
3. 在 Controller 中使用
using Microsoft.AspNetCore.Mvc;
using Sage.Caching.Memory.Services;
[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
private readonly ICacheService _cacheService;
public DataController(ICacheService cacheService)
{
_cacheService = cacheService;
}
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
var data = await _cacheService.GetOrCreateAsync(
key: $"data:{id}",
valueFactory: async () =>
{
await Task.Delay(100);
return new { Id = id, Name = $"Item-{id}" };
},
region: "Data");
return Ok(data);
}
}
核心接口
主要接口为 ICacheService:
bool TryGetValue<T>(string key, out T? value);
void Set<T>(string key, T value, string? region = null, MemoryCacheEntryOptions? options = null);
void Remove(string key, string? region = null);
T? GetOrCreate<T>(string key, Func<T> valueFactory, string? region = null, MemoryCacheEntryOptions? options = null);
ValueTask<T?> GetOrCreateAsync<T>(string key, Func<ValueTask<T>> valueFactory, string? region = null, MemoryCacheEntryOptions? options = null);
void Clear();
int ClearRegion(string region);
void SetMany<T>(IEnumerable<KeyValuePair<string, T>> items, string? region = null, MemoryCacheEntryOptions? options = null);
int RemoveMany(IEnumerable<string> keys);
CacheStats GetCacheStats();
IReadOnlyCollection<string> GetRegionKeys(string region);
常用用法
1. 手动写入缓存
cacheService.Set(
key: "user:1001",
value: new UserDto(1001, "Alice"),
region: "User");
2. 手动读取缓存
if (cacheService.TryGetValue<UserDto>("user:1001", out var user))
{
return Results.Ok(user);
}
return Results.NotFound();
3. 缓存不存在时自动创建
同步:
var product = cacheService.GetOrCreate(
key: "product:1",
valueFactory: () => new ProductDto(1, "Apple"),
region: "Product");
异步:
var product = await cacheService.GetOrCreateAsync(
key: "product:1",
valueFactory: async () =>
{
await Task.Delay(50);
return new ProductDto(1, "Apple");
},
region: "Product");
4. 自定义当前缓存项过期时间
var order = await cacheService.GetOrCreateAsync(
key: "order:2026001",
valueFactory: async () =>
{
await Task.Delay(20);
return new { Id = 2026001, Status = "Paid" };
},
region: "Order",
options: new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(3),
SlidingExpiration = TimeSpan.FromMinutes(1),
Size = 1,
Priority = CacheItemPriority.High
});
5. 删除单个缓存
cacheService.Remove("user:1001");
如果你明确知道这个 key 属于哪个分组,也可以同时传入 region:
cacheService.Remove("user:1001", region: "User");
6. 清空某个分组
var removedCount = cacheService.ClearRegion("User");
7. 清空全部缓存
cacheService.Clear();
8. 批量写入
var items = new Dictionary<string, string>
{
["config:siteName"] = "Sage",
["config:theme"] = "default",
["config:lang"] = "zh-CN"
};
cacheService.SetMany(items, region: "Config");
9. 批量删除
var removed = cacheService.RemoveMany(new[]
{
"config:siteName",
"config:theme"
});
10. 查看分组内有哪些键
var keys = cacheService.GetRegionKeys("User");
11. 获取缓存统计信息
var stats = cacheService.GetCacheStats();
Console.WriteLine($"Hits: {stats.Hits}");
Console.WriteLine($"Misses: {stats.Misses}");
Console.WriteLine($"HitRate: {stats.HitRate:P2}");
Console.WriteLine($"TotalItems: {stats.TotalItems}");
配置项说明
MemoryCacheServiceOptions
| 配置项 | 说明 |
|---|---|
DefaultEntryOptions |
默认缓存项配置 |
EnableStats |
是否启用统计信息,默认 false |
ThrowOnCreateError |
工厂方法异常时是否直接抛出,默认 true |
StatsFlushInterval |
统计信息刷新间隔 |
MaximumCacheSize |
缓存总大小限制 |
ExpirationScanFrequency |
过期扫描频率 |
CompactionPercentage |
内存压力时压缩比例 |
EnableTypeChecking |
是否校验读取类型是否匹配 |
KeyPrefix |
键前缀,防止多模块键冲突 |
使用建议
1. key 要稳定、可预测
建议按业务维度命名:
"user:1001"
"order:2026001"
"callback:task-001"
不要直接把随机字符串当业务主键缓存,后期不方便排查和清理。
2. region 只是分组标签,不参与键隔离
例如下面两段代码:
cacheService.Set("user:1", userA, region: "A");
cacheService.Set("user:1", userB, region: "B");
它们依然是在操作同一个业务 key。
也就是说:
region主要用于ClearRegion("xxx")- 真正避免冲突靠的是
key - 不同分组里不要复用同名业务 key
3. 不要手动拼接 KeyPrefix
如果你配置了:
options.KeyPrefix = "WebService.Callback";
业务代码里依然只写原始 key:
cacheService.Set("callback:1001", value);
不要自己写成:
cacheService.Set("WebService.Callback:callback:1001", value);
前缀应由缓存服务内部统一处理。
4. 设置了 MaximumCacheSize 后,要注意每项的 Size
如果你启用了总大小限制:
options.MaximumCacheSize = 10000;
那么每个缓存项都应提供 Size。你可以:
- 在
DefaultEntryOptions里统一设置Size - 或者在单次写入时单独指定
Size
例如:
options.DefaultEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
Size = 1
};
注意:MaximumCacheSize 是 IMemoryCache 的大小单位,不等同于“字节数”。
5. 统计信息有轻微性能开销
如果你只关心缓存功能,不关心命中率、未命中数、逐出数,建议保持:
options.EnableStats = false;
WebApi Minimal(AOT) 推荐示例
下面是一份适合直接复制到 Program.cs 的最小示例:
using Microsoft.Extensions.Caching.Memory;
using Sage.Caching.Memory.Extensions;
using Sage.Caching.Memory.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCacheService(options =>
{
options.EnableStats = false;
options.ThrowOnCreateError = true;
options.KeyPrefix = "WebService.Callback";
options.EnableTypeChecking = false;
options.MaximumCacheSize = 10000;
options.CompactionPercentage = 0.10;
options.ExpirationScanFrequency = TimeSpan.FromMinutes(1);
options.DefaultEntryOptions = new MemoryCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30),
SlidingExpiration = TimeSpan.FromMinutes(10),
Size = 1,
Priority = CacheItemPriority.Normal
};
});
var app = builder.Build();
app.MapGet("/callback/{id}", async (string id, ICacheService cacheService) =>
{
var result = await cacheService.GetOrCreateAsync(
key: $"callback:{id}",
valueFactory: async () =>
{
await Task.Delay(50);
return new
{
Id = id,
Time = DateTimeOffset.UtcNow
};
},
region: "Callback");
return Results.Ok(result);
});
app.MapDelete("/cache/region/{region}", (string region, ICacheService cacheService) =>
{
var removed = cacheService.ClearRegion(region);
return Results.Ok(new { Region = region, Removed = removed });
});
app.MapGet("/cache/stats", (ICacheService cacheService) =>
{
return Results.Ok(cacheService.GetCacheStats());
});
app.Run();
常见问题
1. 为什么我设置了 region,不同分组还是可能冲突?
因为 region 的作用是“分组清理”,不是“键命名空间隔离”。
真正决定缓存项唯一性的核心仍然是 key。
2. 为什么建议加 KeyPrefix?
当同一个进程里存在多个模块、多个服务、多个业务域时,统一加前缀更容易避免键冲突。
3. Native AOT 可以用吗?
可以。本项目已按 AOT 兼容方式设计,适合在 Minimal API / Native AOT 项目中使用。
许可证
本项目采用 Apache 2.0 许可证。
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Microsoft.Extensions.Caching.Memory (>= 10.0.6)
-
net9.0
- Microsoft.Extensions.Caching.Memory (>= 10.0.6)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
修复 开启了 KeyPrefix 时 GetOrCreate / GetOrCreateAsync 的实现里会先加一次前缀的问题