public class Person { public int Id { get; set; } public string Name { get; set; } [Timestamp] public Byte[] Version { get; set; } }
LINQ和EF的並發控制,都是在其生成的SQL語句中的 where 加入時間戳字段作為查詢條件進行控制的,如:
EF 自動生成的SQL:
exec sp_executesql N'UPDATE [dbo].[People]
SET [Name] = @0
WHERE (([Id] = @1) AND ([Version] = @2))
SELECT [Version]
FROM [dbo].[People]
WHERE @@ROWCOUNT > 0 AND [Id] = @1',
N'@0 nvarchar(max) ,@1 int,@2 binary(8)',@0=N'my name',@1=1,@2=0x0000000000000BBF
最后返回 [Version] 更新記錄。
在 LINQ 的 dbml 中每個實體的字段屬性的更新檢查都是默認為始終的,即會默認產生並發沖突。
LINQ 並發的解決方法:
try {
//如果發生並發沖突,將繼續處理,進入 catch 進行處理方案選擇
db.SubmitChanges(ConflictMode.ContinueOnConflict);
//或FailOnFirstConflict,這種一般不會進行方案選擇,因為它在第一次有並發沖突時,就停止嘗試更新,
//而ContinueOnConflict則會嘗試更新所有更新,並累積和返回所有並發沖突。
} catch (ChangeConflictException) {
foreach (ObjectChangeConflict occ in db.ChangeConflicts) {
//相關的邏輯代碼
// LINQ的實體對象通過 occ.Object 獲取
// 數據庫元表通過 db.Mapping.GetTable(occ.Object.GetType()) 獲取
// 沖突成員通過 occ.MemberConflicts 獲取,在其中可以獲得 :
// 沖突成員的當前值(CurrentValue)、沖突成員的原始值(OriginalValue)、沖突成員的數據庫值(DatabaseValue)
// 可以根據上面邏輯代碼在以下三種方案選擇其一:(成員也可以有自己的解決方案)
// 1. 以數據庫的值為准
//occ.Resolve(RefreshMode.OverwriteCurrentValues);
// 2. 以LINQ實體對象的值為准
//occ.Resolve(RefreshMode.KeepCurrentValues);
// 3. 只更新LINQ實體對象中改變的字段的值,其他的保留不變
//occ.Resolve(RefreshMode.KeepChanges);
}
//最后再次更新數據庫
data.SubmitChanges();
}
還有一種解決辦法,就是通過事務處理,不作並發沖突方案選擇,如發生並發異常,就直接回滾。
using (TransactionScope scope = new TransactionScope())
{
db.SubmitChanges();
scope.Complete();
}
在 EF 的 edmx 或 model 中的每個實體的字段屬性的並發模式都是默認為None的,即不會默認產生並發沖突。
如要處理並發沖突,則如下操作:(需要才設置)
1. 在 edmx 中每個實體的字段屬性的並發模式設置為Fixed
2. 在 model 中的某個字段屬性上加 Timestamp 特性(每個實體只能有一個)或 ConcurrencyCheck特性。
並發的解決辦法:
try{
context.SaveChanges();
}
catch (OptimisticConcurrencyException ex){
// 沖突成員實體通過 ex.StateEntries 獲取
// 同樣邏輯代碼可以 根據 ObjectStateEntry xxx 對象進行操作
// 可以根據上面邏輯代碼在以下兩種方案選擇其一:
// 以實體對象為准
// context.Refresh(RefreshMode.ClientWins, xxx);
// 以數據庫為准
// context.Refresh(RefreshMode.StoreWins,xxx);
//最后再次更新數據庫
context.SaveChanges();
}
還有LINQ的事務解決辦法同樣適合EF。