ABP VNext + EF Core 二级缓存:提升查询性能 🚀
📚 目录
- [ABP VNext + EF Core 二级缓存:提升查询性能 🚀](#ABP VNext + EF Core 二级缓存:提升查询性能 🚀)
 - 
- [引言 🚀](#引言 🚀)
 - [一、环境与依赖 🛠️](#一、环境与依赖 🛠️)
 - [二、集成步骤 ⚙️](#二、集成步骤 ⚙️)
 - 
- [2.1 安装 NuGet 包](#2.1 安装 NuGet 包)
 - [2.2 注册缓存服务与拦截器](#2.2 注册缓存服务与拦截器)
 - [2.3 对特定查询启用缓存 🎯](#2.3 对特定查询启用缓存 🎯)
 
 - [三、缓存依赖与失效 🔄](#三、缓存依赖与失效 🔄)
 - [四、性能对比测试 📈](#四、性能对比测试 📈)
 - 
- [4.1 测试环境 🖥️](#4.1 测试环境 🖥️)
 - [4.2 对比指标 🔥](#4.2 对比指标 🔥)
 
 - [五、最佳实践与注意事项 ⚠️](#五、最佳实践与注意事项 ⚠️)
 - [六、高级配置 🧩](#六、高级配置 🧩)
 
 
引言 🚀
TL;DR ✨
- 集成 
EFCoreSecondLevelCacheInterceptorv5.3.1,为 ABP VNext 应用添加跨DbContext、跨请求的二级缓存,显著降低重复查询开销 - 几行配置即可启用内存或 Redis 缓存,并支持自动失效与手动失效策略 🔄
 - 支持按实体类型或表名缓存,无需手动管理复杂缓存键 🛡️
 - 实测:平均响应时间由 ~120 ms 降至 ~15 ms,QPS 从 ~500 提升至 ~3 500,数据库访问次数减少至 1 次/秒 📊
 
关系型数据库在高并发场景下常见瓶颈包括 CPU、IO 与连接数。EF Core 默认仅在单个 DbContext 生命周期内缓存实体,请求结束后即释放。引入二级缓存(跨 DbContext、跨请求)可显著减少重复查询开销,缓解数据库压力。
一、环境与依赖 🛠️
- 
运行平台:.NET 6.0 LTS + ABP VNext 6.x
 - 
EF Core 版本:6.x
 - 
EFCoreSecondLevelCacheInterceptor:5.3.1
 - 
缓存提供者:
- 内存:
EFCoreSecondLevelCacheInterceptor.MemoryCache - Redis:
EFCoreSecondLevelCacheInterceptor.StackExchange.Redis 
 - 内存:
 - 
其他依赖 :
Volo.Abp.EntityFrameworkCore - 
ABP CLI :
Volo.Abp.Cliv6.x - 
前提 :项目已集成 EF Core 与 ABP 基础模块,已配置连接字符串与常规
DbContext - 
注意:如需在 .NET 7/8 下使用,请升级到 ABP 7.x 或 ABP 8.x 🔄
 
二、集成步骤 ⚙️
项目启动 ConfigureServices 注册缓存服务 配置全局策略 UseMemoryCacheProvider/Redis CacheAllQueries(...) Configure AddInterceptors(SecondLevelCacheInterceptor) DbContext 工作流程
2.1 安装 NuGet 包
            
            
              bash
              
              
            
          
          dotnet add package EFCoreSecondLevelCacheInterceptor --version 5.3.1
dotnet add package EFCoreSecondLevelCacheInterceptor.MemoryCache        # 内存缓存
# 或
dotnet add package EFCoreSecondLevelCacheInterceptor.StackExchange.Redis  # Redis 缓存
        2.2 注册缓存服务与拦截器
在 ABP 模块(如 MyProjectEntityFrameworkCoreModule)的 ConfigureServices 方法中:
            
            
              csharp
              
              
            
          
          public override void ConfigureServices(ServiceConfigurationContext context)
{
    // 1. 添加二级缓存服务
    context.Services.AddEFSecondLevelCache(options =>
        options.UseMemoryCacheProvider()
               .ConfigureLogging(false)                               // 生产环境关闭日志
               .UseCacheKeyPrefix("EF_")                              // 统一前缀,便于分区管理
               .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1)) // 缓存不可用时回退数据库
               .CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5)) // 全局缓存所有查询
               .AllowCachingWithExplicitTransactions(true)            // 显式事务中也可缓存
    ); 
    // 2. 注册 DbContext 并注入拦截器(仅针对 MyDbContext)
    context.Services.AddAbpDbContext<MyDbContext>(options =>
    {
        options.AddDefaultRepositories();
    });
    context.Services.Configure<AbpDbContextOptions>(opts =>
    {
        opts.Configure<MyDbContext>(config =>
        {
            config.DbContextOptions
                  .UseSqlServer(context.Services.GetConfiguration().GetConnectionString("Default"))
                  .AddInterceptors(
                      context.Services.GetRequiredService<SecondLevelCacheInterceptor>()
                  );
        });
    });
}
        2.3 对特定查询启用缓存 🎯
            
            
              csharp
              
              
            
          
          // 使用全局策略(5 分钟绝对过期)
var products = await _productRepository
    .WithDetails()
    .Cacheable()
    .ToListAsync();
// 自定义滑动过期 1 分钟
var recentOrders = await _orderRepository
    .Where(o => o.CreatedDate > since)
    .Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(1))
    .ToListAsync();
        三、缓存依赖与失效 🔄
Cacheable 未命中 命中 触发拦截 Query 二级缓存 数据库 返回结果 SaveChanges()/SaveChangesAsync() 自动清理相关缓存
- 
自动失效 :拦截所有
SaveChanges()/SaveChangesAsync(),根据受影响表自动清除相关缓存,无需额外配置 - 
批量操作限制 :EF Core 的
ExecuteUpdate()与ExecuteDelete()绕过 ChangeTracker,不会触发缓存失效,需手动清理:csharpawait context.Blogs.Where(b => b.IsObsolete) .ExecuteUpdateAsync(s => s.SetProperty(b => b.IsActive, false)); _cacheServiceProvider.ClearAllCachedEntries(); - 
按类型或表名缓存:
csharpservices.AddEFSecondLevelCache(options => { options.UseMemoryCacheProvider() .CacheQueriesContainingTypes( CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), typeof(Product), typeof(Order) ) .CacheQueriesContainingTableNames( CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableNameComparison.ContainsOnly, "Products", "Orders" ); }); - 
手动清理示例 :在服务中注入并使用
IEFCacheServiceProvidercsharppublic class ProductAppService : ApplicationService { private readonly IEFCacheServiceProvider _cacheServiceProvider; public ProductAppService(IEFCacheServiceProvider cacheServiceProvider) { _cacheServiceProvider = cacheServiceProvider; } public void RefreshProductCache() { _cacheServiceProvider.ClearAllCachedEntries(); // 清除所有缓存 _cacheServiceProvider.ClearCacheByPrefix("EF_Products"); // 按前缀清理 } } 
四、性能对比测试 📈
4.1 测试环境 🖥️
- 机房环境:Windows Server 2019,Intel Xeon Gold 6248(8 核/16 线程),32 GB RAM
 - 数据库:SQL Server 2019
 - 数据量:100 万条订单记录
 - 测试工具 :自编脚本 + 
Stopwatch 
            
            
              csharp
              
              
            
          
          // 预热
await WarmUpDbAsync();
// 测试 1,000 次请求
var sw = Stopwatch.StartNew();
for (int i = 0; i < 1000; i++)
{
    await _orderRepository
        .WithDetails()
        .Cacheable()
        .FirstOrDefaultAsync();
}
sw.Stop();
Console.WriteLine($"Elapsed: {sw.ElapsedMilliseconds} ms");
        控制台输出示例
            
            
              text
              
              
            
          
          Warm-up completed.
Testing 1000 requests...
Elapsed: 15000 ms
        声明:以上测试基于串行脚本,仅对比缓存前后性能变化,实际生产环境下并发吞吐量会更高,读者可使用 BenchmarkDotNet 进行多线程基准测试,并查看脚本和日志以复现。
4.2 对比指标 🔥
| 指标 | 无缓存模式 | 启用二级缓存 | 
|---|---|---|
| 平均响应时间 | ~120 ms | ~15 ms | 
| QPS | ~500/sec | ~3 500/sec | 
| DB 访问次数 | ~10 次/秒 | ~1 次/秒 | 
五、最佳实践与注意事项 ⚠️
- 读多写少:Cache-Aside 模式仅适合读多写少场景,高写场景慎用
 - 缓存粒度:对超大结果集拆分分页或按关键字段缓存,避免一次性加载过多数据
 - 容量管理:根据业务规模调优 MemoryCache 或 Redis 参数(如内存上限、Eviction 策略),防止 OOM
 - 雪崩/穿透:结合互斥锁、预热与空值缓存策略,保障系统稳定性
 - 事务内缓存 :显式事务内查询默认不缓存,启用需调用 
.AllowCachingWithExplicitTransactions(true) 
六、高级配置 🧩
            
            
              csharp
              
              
            
          
          services.AddEFSecondLevelCache(options =>
{
    options.UseMemoryCacheProvider()
           // 跳过包含特定 SQL 的查询缓存
           .SkipCachingCommands(cmd => cmd.Contains("NEWID()"))
           // 跳过空结果集的缓存
           .SkipCachingResults(result =>
               result.Value == null ||
               (result.Value is EFTableRows rows && rows.RowsCount == 0))
           // 避免某些更新命令触发失效
           .SkipCacheInvalidationCommands(cmd =>
               cmd.Contains("UPDATE [Posts] SET [Views]"))
           // 动态覆盖某些查询的缓存策略
           .OverrideCachePolicy(context =>
           {
               if (context.IsCrudCommand) return null; // CRUD 不缓存
               if (context.CommandTableNames.Contains("posts"))
                   return new EFCachePolicy()
                       .ExpirationMode(CacheExpirationMode.NeverRemove);
               return null;
           });
});
        这些配置取自官方高级示例,可按需组合使用。