大家好,我是.NET修仙日记的掌门人。今天要和大家分享的是EF Core查询性能优化的实战经验,这些都是我在实际项目中踩过的坑、趟过的雷,希望能帮助各位道友在数据访问层修炼出更高效的功法!
上周,我们收到用户反馈:"洞府列表加载太慢,要等3-4秒才能显示!" 使用SQL Server Profiler抓取到的查询:
SELECT [p].[Id], [p].[Name], [p].[OwnerId], [p].[CreateTime],
[o].[Id], [o].[Name], [o].[Level], [o].[VipLevel]
FROM [Palaces] AS [p]
LEFT JOIN [Immortals] AS [o] ON [p].[OwnerId] = [o].[Id]
WHERE [p].[Type] = 1
ORDER BY [p].[CreateTime] DESC
思考:这个看似简单的查询为何如此之慢?
// Program.cs中配置EF Core日志
builder.Services.AddDbContext<CultivationDbContext>(options =>
options.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information));
在SSMS中执行SET STATISTICS IO ON 后运行查询,发现:
// 使用TelemetryClient记录查询耗时
var telemetry = new TelemetryClient();
var stopwatch = System.Diagnostics.Stopwatch.StartNew();
var palaces = await dbContext.Palaces
.Include(p => p.Owner)
.Where(p => p.Type == PalaceType.Normal)
.OrderByDescending(p => p.CreateTime)
.ToListAsync();
stopwatch.Stop();
telemetry.TrackMetric("PalaceQueryDuration", stopwatch.ElapsedMilliseconds);
优化前:
var palaces = await dbContext.Palaces
.Include(p => p.Owner) // 加载所有Owner信息
.ToListAsync();
优化后:
var palaces = await dbContext.Palaces
.Select(p => new PalaceDto
{
Id = p.Id,
Name = p.Name,
OwnerName = p.Owner.Name, // 只取需要的字段
CreateTime = p.CreateTime
})
.ToListAsync();
效果: 查询数据量减少60%,执行时间从1200ms降至400ms
优化前:
// 加载全部数据
var allPalaces = await dbContext.Palaces.ToListAsync();
优化后:
var pageSize = 20;
var pageNumber = 1;
var palaces = await dbContext.Palaces
.OrderBy(p => p.CreateTime)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
效果: 首次加载时间从3s降至200ms
在DbContext中配置索引:
modelBuilder.Entity<Palace>()
.HasIndex(p => new { p.Type, p.CreateTime })
.IncludeProperties(p => new { p.Name, p.OwnerId });
生成的查询将使用复合索引,性能提升显著。
优化前:
var palace = await dbContext.Palaces.FirstAsync(p => p.Id == id);
优化后:
var palace = await dbContext.Palaces
.AsNoTracking()
.FirstAsync(p => p.Id == id);
适用场景: 只读查询,不需要更新实体时使用
优化前:
foreach (var item in items)
{
dbContext.Add(item);
await dbContext.SaveChangesAsync(); // 每次保存
}
优化后:
dbContext.AddRange(items);
// 批量保存
await dbContext.SaveChangesAsync();
效果: 1000条数据插入从30s降至1s
优化前:
// 每次执行都会重新编译
var palace = await dbContext.Palaces
.FirstOrDefaultAsync(p => p.Id == id);
优化后:
private static readonly Func<CultivationDbContext, int, Task<Palace>> GetPalaceById =
EF.CompileAsyncQuery((CultivationDbContext db, int id) =>
db.Palaces.FirstOrDefault(p => p.Id == id));
// 使用预编译查询
var palace = await GetPalaceById(dbContext, id);
效果: 高频查询性能提升40%
// 传统方式
var palaces = await dbContext.Palaces
.Where(p => p.Level < 5)
.ToListAsync();
foreach (var p in palaces)
{
p.Level = 5;
}
await dbContext.SaveChangesAsync();
// EF Core 7+ 批量更新
await dbContext.Palaces
.Where(p => p.Level < 5)
.ExecuteUpdateAsync(setters => setters.SetProperty(p => p.Level, 5));
// 查询JSON列中的属性
var palaces = await dbContext.Palaces
.Where(p => p.MetadataJson.Contains("\"VIP\": true"))
.ToListAsync();
指标 | 优化前 | 优化后 | 提升幅度 |
---|---|---|---|
洞府列表加载 | 3200ms | 450ms | 7倍 |
批量插入1000条 | 30s | 1.2s | 25倍 |
CPU占用峰值 | 85% | 25% | 大幅降低 |
内存消耗 | 420MB | 180MB | 57%减少 |
经过这一轮优化,我深刻体会到EF Core性能优化的几个关键点:
(点击关注,修炼不迷路👇)
▌转载请注明出处,渡人渡己
🌟 感谢道友结缘! 若本文助您突破修为瓶颈,不妨[打赏灵丹]或[转发功德],让更多.NET道友共参CLR天道玄机。修真之路漫漫,我们以代码为符,共绘仙途!