在 Entity Framework (EF) 的核心架構中,ObjectStateManager 扮演著「變更追蹤中樞」的角色。它隸屬於 ObjectContext,負責監控所有受管理實體物件的狀態變化(新增、修改、刪除),並在呼叫 SaveChanges() 時,依據追蹤資訊自動生成對應的 T-SQL 指令批次送往資料庫執行。相較於 LINQ to SQL 的 DataContext,EF 的變更追蹤機制更為精細且功能豐富。
開發者常用的兩個關鍵方法:
GetObjectStateEntries(EntityState state):依指定狀態(Added、Modified、Deleted、Unchanged 等)回傳 ObjectStateEntry 集合。GetObjectStateEntry(object entity):取得特定實體對應的 ObjectStateEntry。每個 ObjectStateEntry 封裝了豐富的變更資訊:
EntityKey:實體的唯一識別鍵值。OriginalValues:實體載入時的原始屬性值(資料庫當前值)。CurrentValues:實體目前在記憶體中的屬性值(含使用者修改)。State:目前狀態枚舉值。以下程式碼示範如何擷取狀態為 Modified 的實體,並逐一列印其主鍵、原始值與目前值:
using (var context = new AdventureWorksLTEntities())
{
// 1. 擷取實體並修改屬性
var product = context.Product.FirstOrDefault(p => p.ProductID == 1004);
if (product != null)
{
product.Color = "Black";
product.StandardCost = 20;
product.ListPrice = 25;
}
// 2. 取得所有 Modified 狀態的實體項目
var entries = context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified);
foreach (var entry in entries)
{
// 顯示實體容器、集合名稱與主鍵
Console.WriteLine("{0} - {1} - {2} = {3}",
entry.EntityKey.EntityContainerName,
entry.EntityKey.EntitySetName,
entry.EntityKey.EntityKeyValues[0].Key,
entry.EntityKey.EntityKeyValues[0].Value);
// 逐欄位比對原始值與目前值
for (int i = 0; i < entry.OriginalValues.FieldCount; i++)
{
Console.WriteLine("\t {0} \u2192 {1}",
entry.OriginalValues[i],
entry.CurrentValues[i]);
}
}
}
輸出結果範例:
AdventureWorksLTEntities - Product - ProductID = 1004
1004 \u2192 1004
def \u2192 def
abc \u2192 abc
Black \u2192 Black
20.0000 \u2192 20
25.0000 \u2192 25
...
5b2cfc8c-8344-4431-8c2c-651358cce331 \u2192 5b2cfc8c-8344-4431-8c2c-651358cce331
透過此機制,開發者可輕鬆實現審計日誌、異動記錄、或自訂驗證邏輯。
當資料僅供唯讀顯示(如報表、GridView 列表、下拉選單)且無後續更新需求時,啟用 MergeOption.NoTracking 可大幅降低記憶體佔用與查詢延遲:
var sql = "SELECT VALUE m FROM AdventureWorksLTEntities.Model AS m";
var query = new ObjectQuery<Model>(sql, context, MergeOption.NoTracking);
foreach (var m in query)
Console.WriteLine("{0} {1} {2}", m.ModelID, m.Name, m.ModifiedDate);
LINQ 查詢無法直接傳遞 MergeOption,需對整個 EntitySet 設定:
var categories = from c in context.Category
where c.ParentCategory.Name != null
orderby c.Name
select c;
context.Category.MergeOption = MergeOption.NoTracking; // 設定於 EntitySet 層級
return categories.ToList();


| 場景 | 建議 MergeOption | 理由 |
|---|---|---|
| 一般 CRUD 操作 | AppendOnly (預設) | 需追蹤變更以支援 SaveChanges |
| 唯讀列表/報表/下拉選單 | NoTracking | 避免追蹤開銷,提升查詢效能 |
| 需重新附加更新的離線實體 | NoTracking + 手動 Attach | 先以 NoTracking 取得,修改後再 Attach 並標記 Modified |
善用 ObjectStateManager 與 MergeOption.NoTracking,能讓 EF 應用在功能正確性與執行效能之間取得最佳平衡。