1. 视图缓存的使用与失效策略

基础使用(.NET Framework)

在.NET Framework中,可以使用[OutputCache]特性来缓存视图。以下是一个基本示例,缓存首页10分钟,所有用户共享同一缓存:

[OutputCache(Duration = 600, VaryByParam = "none")]
public ActionResult Index()
{
    var hotProducts = _context.Products
        .Where(p => p.IsHot)
        .ToList();
    return View(hotProducts);
}

对于带参数的页面,如商品详情页,可以通过VaryByParam参数来缓存不同版本:

[OutputCache(Duration = 3600, VaryByParam = "id")]
public ActionResult Detail(int id)
{
    var product = _context.Products.Find(id);
    return View(product);
}
.NET Core中的替代方案

在.NET Core中,可以使用[ResponseCache]特性替代[OutputCache]

[ResponseCache(Duration = 600, Location = ResponseCacheLocation.Client)]
public IActionResult Index()
{
    // 业务逻辑
}
失效策略

当数据更新时,需要主动移除缓存以确保用户看到最新内容。以下是一个主动移除缓存的示例:

public ActionResult UpdateProduct(Product product)
{
    _context.Products.Update(product);
    _context.SaveChanges();
    
    // 清除该商品的详情页缓存
    HttpResponse.RemoveOutputCacheItem(Url.Action("Detail", new { id = product.Id }));
    return RedirectToAction("Detail", new { id = product.Id });
}

另一种方法是使用缓存依赖,当依赖的数据库表发生变动时自动失效缓存:

[OutputCache(Duration = 3600, VaryByParam = "id", SqlDependency = "MyDb:Products")]
public ActionResult Detail(int id)
{
    // 业务逻辑
}
常见问题与解决方案
  • VaryByParam设置错误:对于带参数的页面,使用VaryByParam = "none"会导致所有用户看到同一内容。应正确设置参数名,如VaryByParam = "id"
  • 缓存时间过长:数据更新后用户看不到新内容。解决方案是结合主动失效或适当缩短缓存时间。
  • 登录相关页面缓存:可能导致用户信息泄露。解决方法是通过VaryByCustom = "User"按用户隔离缓存。

2.数据库查询优化实践

索引优化

在C#中,通过Entity Framework Core进行数据库操作时,未加索引的字段查询会导致全表扫描,性能较差。例如:

var orders = _context.Orders
    .Where(o => o.OrderDate > DateTime.Now.AddDays(-30)) // 无索引时性能低下
    .ToList();

通过Fluent API为常用查询字段添加索引能显著提升性能:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Order>()
        .HasIndex(o => o.OrderDate); // 单字段索引

    modelBuilder.Entity<Order>()
        .HasIndex(o => new { o.UserId, o.OrderDate }); // 复合索引
}
解决N+1查询问题

典型的N+1问题表现为先查询主表,再循环查询关联表:

var orders = _context.Orders.Take(10).ToList();
foreach (var order in orders)
{
    var items = order.OrderItems.ToList(); // 每次循环产生新查询
}

使用Include方法预加载关联数据可避免该问题:

var orders = _context.Orders
    .Include(o => o.OrderItems) // 一次性加载关联数据
    .Take(10)
    .ToList();
常见误区与解决方案

过度索引会影响写入性能,需权衡查询频率和写入需求。使用Include时若关联数据过多,可通过Select投影减少数据传输量:

var orders = _context.Orders
    .Select(o => new { o.Id, o.OrderDate, Items = o.OrderItems.Select(i => i.Name) })
    .Take(10)
    .ToList();

分页查询必须配合排序以保证结果稳定性:

var orders = _context.Orders
    .OrderBy(o => o.OrderDate) // 必须排序
    .Skip(10)
    .Take(10)
    .ToList();
性能检测工具

识别N+1问题可使用EF Core的日志功能或专业工具:

  • 启用EF Core日志记录SQL语句
  • Application Insights或New Relic等APM工具
  • MiniProfiler等专用性能分析工具
  • 数据库自带的查询分析器(如SQL Server Profiler)

3.静态资源压缩与合并的配置方法

安装 BuildBundlerMinifier 包
在项目中通过 NuGet 安装 BuildBundlerMinifier 包,命令如下:

Install-Package BuildBundlerMinifier

配置 bundleconfig.json 文件
在项目根目录创建 bundleconfig.json,定义需要合并和压缩的静态资源。示例配置:

[
  {
    "outputFileName": "wwwroot/css/site.min.css",
    "inputFiles": [
      "wwwroot/css/bootstrap.css",
      "wwwroot/css/custom.css"
    ],
    "minify": { "enabled": true }
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/js/jquery.js",
      "wwwroot/js/common.js"
    ],
    "minify": { "enabled": true }
  }
]

引用压缩后的文件
在视图中直接引用生成的压缩文件:

<link href="~/css/site.min.css" rel="stylesheet" />
<script src="~/js/site.min.js"></script>

常见问题与解决方案

生产环境未启用压缩
检查发布配置,确保 minify.enabled 设置为 true。发布时验证输出目录是否生成了 *.min.* 文件。

合并顺序错误
依赖库(如 jQuery)必须在使用它的脚本之前加载。调整 inputFiles 数组顺序,确保依赖项在前:

"inputFiles": [
  "wwwroot/js/jquery.js",
  "wwwroot/js/dependent-script.js"
]

缓存策略问题
为静态资源添加版本号强制更新:

<link href="~/css/site.min.css?v=2" rel="stylesheet" />

或通过 ASP.NET Core 的 Tag Helper 自动追加哈希:

<link asp-href-include="~/css/site.min.css" rel="stylesheet" />

其他优化建议

启用 Gzip/Brotli 压缩
在服务器配置中启用动态压缩,进一步减少传输体积。例如在 Startup.cs 中添加:

services.AddResponseCompression(options =>
{
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
});

监控资源加载
使用浏览器开发者工具的 Network 面板,检查资源是否被正确压缩和缓存。

4.异步控制器方法的最佳实践

正确使用async/await模式
在ASP.NET Core控制器中,异步方法应始终返回TaskTask<T>。所有IO操作(数据库查询、API调用等)都应使用配套的异步方法,并配合await关键字:

public async Task<IActionResult> GetUserAsync(int id)
{
    var user = await _userService.GetByIdAsync(id);
    return Ok(user);
}

并行任务处理
当多个独立任务可并行执行时,使用Task.WhenAll实现非阻塞等待:

var userTask = _userRepo.GetAsync(userId);
var orderTask = _orderRepo.GetByUserAsync(userId);
await Task.WhenAll(userTask, orderTask);

var viewModel = new DashboardVM {
    User = userTask.Result,
    Orders = orderTask.Result
};

常见错误及解决方案

阻塞异步代码
错误使用同步方法或.Result/.Wait()会导致线程阻塞:

// 错误示范:同步阻塞
var products = _context.Products.ToList();  // 应改用ToListAsync()
var count = _service.GetCountAsync().Result; // 应改用await

async void陷阱
异步事件处理器除外,所有异步方法都应返回Task。async void会导致异常无法被捕获:

// 错误示范:async void
public async void ProcessData() {...} 

// 正确做法:
public async Task ProcessDataAsync() {...}

调试技巧

死锁诊断
当界面卡死但无异常时,检查是否存在以下情况:

  • 同步代码调用异步方法(如.Result
  • 未配置ConfigureAwait(false)的跨上下文调用
  • 嵌套的Task.Run或同步锁

性能分析工具

  • 使用Visual Studio的并发可视化工具
  • 检查诊断日志中的线程池 starvation 警告
  • 通过Application Insights 跟踪请求流水线耗时

实战建议

数据库上下文注意项
Entity Framework Core的异步操作需要特殊处理:

  • 单个DbContext实例不要跨线程共享
  • 大量操作考虑使用AddRangeAsync+批量提交
  • 复杂查询先用AsNoTracking测试是否性能提升

HTTP客户端优化
对于外部API调用:

  • 重用HttpClient实例
  • 设置合理的Timeout和MaxConnections
  • 考虑使用Polly实现重试机制
// 正确示例:带超时设置的HTTP调用
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));
var response = await _httpClient.GetAsync(url, cts.Token);

性能权衡

异步适用场景

  • IO密集型操作(数据库/网络请求)
  • 高并发下的线程池资源节约
  • 需要响应UI保持流畅的前端交互

同步更优情况

  • 简单的内存计算
  • 必须保证原子性的临界区操作
  • 初始化阶段的一次性配置

通过性能测试工具(如Benchmark.NET)验证实际效果,避免为异步而异步。典型优化顺序:先解决N+1查询 > 添加适当索引 > 引入缓存 > 最后考虑异步化改造。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐