LINQ与EF的并发处理
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。