關於DataContext Attach()的眉眉角角
上回提到LINQ to SQL兩段式更新時,經網友提醒有Attach()這個好東西,今天試了一下,結果發現它並不是我所原本想像的好東西,用起來得費一些手腳。
MVP Rick Strahl對這個議題有兩篇文章(1, 2)做了深入探討,因此細節我就不再贅述,但簡單歸納一下我的整理:
Table(TEntity).Attach()有三個Overloading:
- 若只使用Attach(entity),不會產生任何SQL的更新動作。
- 使用Attach(entity, asModified),當設為true,但entity沒有Timestamp欄位時,會得到 An entity can only be attached as modified without original state if it declares a version member or does not have an update check policy. 錯誤。
- 先建立DataContext,使用Single(...)查出資料庫中的現有版本放入origEntity,再使用Attach(entity, origEntity),會得到Cannot add an entity with a key that is already in use.錯誤。
如果你不想為了Attach在資料表多加一個Timestamp欄位,也不想手動修改DBML加入UpdateCheck.Never屬性,那就只能由從第三種方法下手。
解決方式是"用另一個DataContext查出origEntity"(或者用序列化程序將origEntity"漂白"成與DataContext無關,但感覺上更繞路),如以下示範:
Member orig = null;
//要用不同的DataContext先取出資料物件,稍後做比對用
using (PlaygroundDataContext db = new PlaygroundDataContext())
{
orig = db.Members
.Single(o => o.UserId == int.Parse(e.DataKeyString));
}
//更新要用另一個DataContext
using (PlaygroundDataContext db = new PlaygroundDataContext())
{
var de = new Member();
//將HTML編修的結果更新到資料物件
UpdateHelper.SyncData(de);
db.Members.Attach(de, orig);
db.SubmitChanges();
}
PS: 因為Attach()種種限制,在前述文章討論中也有一些人是採用兩段式更新文中用的Refection重設資料值做法。
[2010-07-02更新]網友提供了更簡便的做法如下,感謝SuperShowwei分享!
using (PlaygroundDataContext db = new PlaygroundDataContext())
{
db.Log = new DebuggerWriter();
var de = new Member();
//將HTML編修的結果更新到資料物件
UpdateHelper.SyncData(de);
db.Members.Attach(de);
db.Refresh(System.Data.Linq.RefreshMode.KeepCurrentValues, de);
db.SubmitChanges();
}