前言
在進行 Entity Framework 查詢時,當你執行的是「唯讀」操作,卻沒有使用 AsNoTracking() 時,效能會有差距。本篇記錄實際踩雷的經驗——當資料量小時還看不出來,但隨著資料成長,超過預設的 60 秒 Timeout 限制,導致排程工作失敗。
問題情景
某個 .NET Framework 排程工作,定期執行一份複雜的資料查詢:
using (var db = new MyDbContext())
{
var result = db.Orders
.Where(o => o.Status == "Completed")
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
TotalAmount = g.Sum(o => o.Amount),
OrderCount = g.Count()
})
.ToList();
// 處理 result...
}
起初,資料量只有幾千筆,執行速度很快。但隨著程式修改,時間推移,累積到數十萬筆以上,這個查詢開始變得異常緩慢,最終超過預設的 60 秒 Timeout,排程執行失敗。
根本原因:Change Tracking
Entity Framework 預設會追蹤查詢結果中的每一個實體:
- 記憶體使用:EF 會在內部維護一個追蹤 dict,記錄每個實體的狀態(Unchanged、Modified 等)
- 快照儲存:EF 會保存實體的原始狀態,以便在 SaveChanges 時偵測變更
- 效能損耗:當結果集很大時,這些額外操作會顯著影響查詢執行時間
對於只讀查詢,這些追蹤機制是沒有必要的浪費。
解決方案:AsNoTracking()
在查詢上加上 AsNoTracking(),告訴 EF 你不需要追蹤這些實體:
using (var db = new MyDbContext())
{
var result = db.Orders
.AsNoTracking() // 停用追蹤
.Where(o => o.Status == "Completed")
.GroupBy(o => o.CustomerId)
.Select(g => new
{
CustomerId = g.Key,
TotalAmount = g.Sum(o => o.Amount),
OrderCount = g.Count()
})
.ToList();
// 處理 result...
}
加上 AsNoTracking() 後,相同的查詢執行時間從超過 60 秒下降到數秒,效能提升顯著。
趁著這次踩雷還記得清楚,趕快記下來…XD