在LINQ to SQL中处理“更新已被其它用户删除对象”的错误
在多用户条件下,当你正在修改一条记录时,很有可能另一个用户已把此记录删除。这时,等你修改完毕向数据库提交请求时,会出现“更新已被其它用户删除对象”的错误。
在LINQ to SQL中,所有本次数据更改冲突都被记录到DataContext.ChangeConflicts集合中。
通过遍历这个集合,可以知道引发冲突的原因。
多用户条件下引发数据更改冲突的原因主要有两种:
1 更新已被其它用户更新的对象
2 更新已被其它用户删除的对象
对于上述两种状况,LINQ to SQL采取了不同的处理方法。
对于第1种情况,LINQ to SQL的DataContext.ChangeConflicts集合对象提供了一个“ResolveAll(更新模式)”方法,最常用的是以下这个形式:
//数据更新冲突时,将我的值与其他用户提交的值合并,但我修改过的值拥有最高优先级
context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
另两种更新冲突处理策略是:
(1) KeepCurrentValues 即我的值最优先,不管我改过没有,一律用我的值覆盖对应的数据库值来解决此冲突。
(2)OverwriteCurrentValues 用数据库中的当前值全面取代我现在的值,放弃我做的修改。
选择好合适策略调用ResolveAll()后,再次调用DataContext.SubmitChanges()方法将会成功更新数据库。
对于第2种情况(“更新已被其它用户删除的对象”),LINQ to SQL的处理比较另类:
如果你调用ResolveAll(更新策略)方法,LINQ to SQL仅是简单地将与此对应的冲突对象(ObjectChangeConflict类型)的IsResolve属性设置为True,其结果是LINQ to SQL将不再尝试更新此对象!
换句话说:你调用ResolveAll(更新策略)方法后,LINQ to SQL认为你不打算处理这个已被其它用户删除的对象,就将其打入“冷宫”,不再理会。
现在有一个问题:
如果我想当“更新已被其它用户删除的对象”这一现象出现时,重新向数据库插入此对象,怎么办?
除非采用序列化方法,否则LINQ to SQL不允许将一个实体对象分离出来。但我们可以克隆一个新对象再将其插入到Table<entity>中。然后,将“更新已被其它用户删除的对象”对应的冲突对象设置为“已解决”状态。
以下代码片断来自于一个Windows Form应用程序:
try
{
context.SubmitChanges(ConflictMode.ContinueOnConflict);
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict occ in context.ChangeConflicts)
{
//如果其它用户已删除此实体对象对应的数据库记录
if (occ.IsDeleted)
{
//克隆一个新实体对象
Book newbook = CloneBook(book);
//下次提交时,将插入新对象到Book表中
context.Book.InsertOnSubmit(newbook);
//通知DataContext不再处理此已被其他用户删除数据库记录的实体对象
occ.Resolve();
}
}
//普通的更新冲突,则将我的值与其他用户提交的值合并以解决冲突
context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
//再次提交
context.SubmitChanges(ConflictMode.FailOnFirstConflict);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "提示信息", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
上述模板代码中,context代表DataContext对象,Book是数据库中的表,LINQ to SQL会为它生成同名的实体类。
使用LINQ to SQL更新数据库需要有几个注意事项:
1 当数据表间有主键外键关联时,注意在数据库中将这个关联关系设置为层叠更新与删除。否则, SQL Server会阻止你删除主表记录。除非你一条条地将从表相关记录删除干净后才能删除主表记录。
对应地,如果你要删除某LINQ to SQL实体类对象,但这个对象还包容着相关联的子实体类对象时,一旦尝试向数据库提交更改,而数据库中没有将这个关联关系设置为层叠更新与删除,则LINQ to SQL将自动回滚操作,一条记录也删除不了!其原因是DataContext.SubmitChange()方法默认启动了一个事务,一出现异常则自动回滚。
2 克隆有主键外键关联的实体对象时,注意要一并克隆其所包容的子对象,而不要简单地赋值,那是“对象引用复制”而非“对象本身数据的复制”。具体来说,就是new一个新对象,然后逐个地把老对象的值赋给新对象,这个过程可能会递归好几层。最简单的方法是将原对象序列化到一个内存流中,然后从内存流中反序列化以实现对象的完全克隆。
3 如果需要获取数据库最新数据,可以调用DataContext.Refresh()方法,或者最简单的,直接向数据库发送一个新的LINQ查询,然后更新界面即可(推荐使用数据绑定),后面这个方法最简单可靠。
多用户环境下数据库的记录的存取冲突是一个令人头痛的问题,需要仔细地处理各种情况,这篇短文期望对大家有点帮助。
由于本人对LINQ to SQL技术研究与应用不深,因此,如本文有错,敬请朋友指出。