實體類別欄位較多,但每次更新僅需修改部分欄位。使用 Attach + Refresh(KeepCurrentValues) 時,未賦值的欄位會被更新為 NULL,產生如下 SQL:
UPDATE [dbo].[log] SET [logMessage] = @p1, [x] = @p2 WHERE [logId] = @p0
-- @p2 變成 NULL,導致資料遺失
var n1 = (from x in dc.log select x).SingleOrDefault(c => c.logId == 1);
n1.logMessage = "xxxy";
updateRe(n1); // 拋出:已嘗試 Attach 或 Add 實體,該實體不是新實體...
無法輕易將實體從原 DataContext 脫離。
public partial class log
{
public log Clone()
{
return new log
{
logId = this.logId,
logMessage = this.logMessage,
x = this.x
};
}
}
// 使用
var n1 = ((from x in dc.log select x).SingleOrDefault(c => c.logId == 1)).Clone();
n1.logMessage = "xxxy";
updateRe(n1); // 產生正確 SQL:僅 UPDATE logMessage
缺點:每個實體類別都要手寫 Clone,維護成本高;新增欄位易忘記同步。
核心概念:**在同一個 DataContext 內查詢、修改、提交**,避免跨 Context Attach 問題。
static void Update(int id, Action<log> updater)
{
using (var ctx = new DataClasses1DataContext())
{
ctx.Log = Console.Out; // 觀察 SQL
log entity = ctx.log.First(c => c.logId == id);
updater(entity); // 執行呼叫端傳入的修改邏輯
ctx.SubmitChanges();
}
}
// 呼叫範例
public static void SetLogMessage(log l) { l.logMessage = "xxtx"; }
static void Main()
{
Update(1, SetLogMessage); // 或 Update(1, l => l.logMessage = "new value");
}
| 方案 | 優點 | 缺點 |
|---|---|---|
| Clone 手寫 | 邏輯直觀、可在離線/跨層傳遞實體 | 樣板碼多、易出錯、違反 DRY |
| 委派模式 (推薦) | 零樣板碼、自動只更新異動欄位、符合 Unit of Work 模式 | 需在同一 Context 內完成 (通常符合 Web Request/Transaction 範圍) |
| Entity Framework (Core/6+) | 內建 Update(entity) 自動追蹤異動、支援 IsModified 精確控制 | 需遷移至 EF |
context.Attach(entity).Property(e => e.Field).IsModified = true 精確控制欄位更新。LINQ to SQL 的 Attach/Refresh 設計初衷是處理離線實體,但在「部分更新」場景下極易誤傷未載入欄位。**委派模式**以最少程式碼完美解決問題,並自然契合「單一工作單元」原則。若專案允許,遷移至 EF Core 將獲得更強大的異動追蹤與更新控制能力。