在linq to sql中处理“更新已被其它用户删除对象”的错误

在LINQ to SQL中处理“更新已被其它用户删除对象”的错误

 

在多用户条件下,当你正在修改一条记录时,很有可能另一个用户已把此记录删除。这时,等你修改完毕向数据库提交请求时,会出现“更新已被其它用户删除对象”的错误。

 

在LINQ to SQL中,所有本次数据更改冲突都被记录到DataContext.ChangeConflicts集合中。

通过遍历这个集合,可以知道引发冲突的原因。

 

多用户条件下引发数据更改冲突的原因主要有两种:

 

1 更新已被其它用户更新的对象

2 更新已被其它用户删除的对象

 

 

对于上述两种状况,LINQ to SQL采取了不同的处理方法。

 

对于第1种情况,LINQ to SQL的DataContext.ChangeConflicts集合对象提供了一个“ResolveAll(更新模式)”方法和“Resolve方法”,最常用的是以下这个形式:     

try
{
  db.SubmitChanges(ConflictMode.ContinueOnConflict);//遇到冲突继续执行
}
catch (ChangeConflictException)
{
foreach (ObjectChangeConflict occ in db.ChangeConflicts)
{
var supper = occ.Object as SuppliersInfo;
occ.Resolve(RefreshMode.KeepChanges);

或者使用

  //数据更新冲突时,将我的值与其他用户提交的值合并,但我修改过的值拥有最高优先级

        context.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);

Console.WriteLine("CompanyName={0}发生并发冲突", supper.CompanyName);
foreach (MemberChangeConflict s in occ.MemberConflicts)
{
  Console.WriteLine("字段{0}原始值为{1},数据库中值为{2},新值为{3}",
  s.Member.Name,s.OriginalValue,s.DatabaseValue,s.CurrentValue);
}
}
//处理冲突以后从新提交
customer.SubmitChanges(ConflictMode.FailOnFirstConflict);//遇到冲突立即终止
}

 

另两种更新冲突处理策略是:

 

(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技术研究与应用不深,因此,如本文有错,敬请朋友指出。

原文来自: http://blog.csdn.net/bitfan/article/details/4034710

posted @ 2012-04-10 11:29  初雪之恋  阅读(455)  评论(0编辑  收藏  举报