> 新增一筆記錄,儲存後再去修改它,再儲存時就會出現錯誤: > "Row cannot be located for updating. > Some values may have been changed since it was last read." 當你利用 recordset 修改欄位資料,儲存時 ADO 會為你產生行動查詢( action query)以更新記錄,例如你將員工 A001 的薪水從 25000 加到 30000,儲存時 ADO 會產生如下的 update SQL: update Employees set Salary=30000 where EmpID='A001' and Salary=25000 上面的例子透露了一件事:在預設的情況下,ADO 產生的 update SQL 命 令的 where 子句是以 Primary Key 加上有修改的欄位的舊值為條件來找 尋欲更新的記錄。 如果欲更新的資料表沒有 Primary Key 的話,ADO 會嘗試使用 Unique Index 欄位,如果 PK 和 UI 都沒有,ADO 就會用最原始的方法,用所有 的欄位的舊值來作為尋找記錄的條件(跟 TDataSetProvider 的 UpdateMode 屬性使用 upWhereAll 時的作用一樣),這樣也最容易造成更新失敗。 導致這個錯誤的原因通常有下列幾種: - 使用自動編號欄位。 - 使用 timestamp 欄位。 - 使用欄位預設值。 - 某個欄位是由 trigger 來更新的。 有上述情形的話,當第一次儲存之後,資料庫的某些欄位值就會和 ADO 的 client-side cursor 所維護的資料不同,因此第二次的修改並且儲存時, 就會發生找不到記錄的錯誤。 如果你堅持不建立 PK 欄位,你可以在儲存之後呼叫 Requery 重新讓 ADO 執行查詢命令,像這樣: procedure TForm1.ADODataSet1AfterPost(DataSet: TDataSet); var bm: TBookmark; begin bm := ADODataSet1.GetBookmark; try ADODataSet1.Requery; ADODataSet1.GotoBookmark(bm); finally ADODataSet1.FreeBookmark(bm); end; end; (註:forward-only cursor, dynamic cursor, 以及 server-side cursor 不支援 bookmark) 此方法的缺點是資料量較大時速度會變慢(因為重新讀取所有資料列)。 如果是因為資料表沒有 PK 欄位導致錯誤發生,你應該考慮為資料表建立 一個 PK 欄位。 如果你的資料表有 PK 欄位,你可以選擇以下其中一種解法: 1.ADO 標準解法:設定 "Update Resync" 動態屬性,像這樣: ADODataSet1.Properties['Update Resync'].Value := adResyncUpdates; 如果有用到自動編號欄位的話,可以試試這個: ADODataSet1.Properties['Update Resync'].Value := adResyncAutoIncrement + adResyncInserts; 註: 這些 adResyncXxxx 的常數值可以用加總的方式來加以組合。 2.設定 'Update Criteria' 這個動態屬性來改變 ADO 尋找記錄的行為, 使它在尋找欲更新的記錄時只比對 PK 欄位,像這樣: ADODataSet1.Properties['Update Criteria'].Value := adCriteriaKey; 3.儲存之後重新讀取這筆記錄,參考下面的程式碼: procedure TForm1.ADODataSet1AfterPost(DataSet: TDataSet); begin with ADODataset1 do begin UpdateCursorPos; {MUST ALWAYS DO!} RecordSet.Resync(adAffectCurrent, adResyncAllValues); ReSync([]); {May not be needed} end; end; 註1: ADODataSet 必須在開啟狀態才能存取 Properties 中的動態屬性。 註2: 以上所列的程式碼都必須 uses ADOInt 這個單元。 ADO 程式設計開發指南(Programming ADO by David Sceppa)的第 10, 11 章有詳細的介紹。