Linq to sql:检测并发

来源:互联网 发布:淘宝不能上传新图片 编辑:程序博客网 时间:2024/05/30 02:52

首先使用下面的SQL语句查询数据库的产品表:

 

select *from products wherecategoryid=1

 

为了看起来清晰,我已经事先把所有分类为1产品的价格和库存修改为相同值了。然

 

后执行下面的程序:

 

var query =from p in ctx.Products where p.CategoryID == 1select p;

 

foreach (var pinquery)

 

                        p.UnitsInStock =Convert.ToInt16(p.UnitsInStock - 1);

 

        ctx.SubmitChanges(); //  在这里设断点

 

我们使用调试方式启动,由于设置了断点,程序并没有进行更新操作。此时,我们在数

 

据库中运行下面的语句:

 

update products 

 

set unitsinstock= unitsinstock-2, unitprice= unitprice+ 1

 

where categoryid= 1


然后在继续程序,会得到修改并发(乐观并发冲突)的异常,提示要修改的行不存在或

 

者已经被改动。当客户端提交的修改对象自读取之后已经在数据库中发生改动,就产生了修

 

改并发。解决并发的包括两步,一是查明哪些对象发生并发,二是解决并发。如果你仅仅是

 

希望更新时不考虑并发的话可以关闭相关列的更新验证,这样在这些列上发生并发就不会出

 

现异常:

 

[Column(Storage="_UnitsInStock", DbType="SmallInt", UpdateCheck =

 

UpdateCheck.Never)]

 

[Column(Storage="_UnitPrice", DbType="Money", UpdateCheck = UpdateCheck.Never)]

 

为这两列标注不需要进行更新检测。假设现在产品价格和库存分别是2732。那么,

 

我们启动程序(设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库

 

存分别为2830 了,继续程序可以发现价格和库存分别是2831。价格+1是之前更新

 

的功劳,库存最终是-1是我们程序之后更新的功劳。当在同一个字段上(库存)发生并发

 

冲突的时候,默认是最后的那次更新获胜。

 

解决并发

 

如果你希望自己处理并发的话可以把前面对列的定义修改先改回来,看下面的例子:

 

var query =from p in ctx.Products where p.CategoryID == 1select p;

 

foreach (var pinquery)

 

                        p.UnitsInStock =Convert.ToInt16(p.UnitsInStock - 1);

 

try

          {


           ctx.SubmitChanges(ConflictMode.ContinueOnConflict);

 

                }

 

catch (ChangeConflictException)

 

                {

 

foreach (ObjectChangeConflict ccinctx.ChangeConflicts)

 

                        {

 

Product p = (Product)cc.Object;

 

               Response.Write(p.ProductID +"<br/>");

 

                                cc.Resolve(RefreshMode.OverwriteCurrentValues);//  放弃当前更新,

 

所有更新以原先更新为准

 

                        }

 

                }

 

       ctx.SubmitChanges();

 

首先可以看到,我们使用try{}catch{}来捕捉并发冲突的异常。在SubmitChanges的时

 

候,我们选择了ConflictMode.ContinueOnConflict选项。也就是说遇到并发了还是继续。

 

catch{}中,我们从ChangeConflicts中获取了并发的对象,然后经过类型转化后输出了

 

产品ID,然后选择的解决方案是RefreshMode.OverwriteCurrentValues。也就是说,放弃

 

当前的更新,所有更新以原先更新为准。

 

我们来测试一下,假设现在产品价格和库存分别是2732。那么,我们启动程序(在

 

ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE

 

语句,把价格+1,库存-2,然后价格和库存分别为2830 了,继续程序可以发现价格和

 

库存分别是2830。之前SQL语句库存-2 生效了,而我们程序的更新(库存-1)被放弃

 

了。在页面上也显示了所有分类为1的产品ID(因为我们之前的SQL语句是对所有分类为

 

1 的产品都进行修改的)。

 

然后,我们来修改一下解决并发的方式:

 

 

cc.Resolve(RefreshMode.KeepCurrentValues);// 放弃原先更新,所有更新以当前更新为

 

 

来测试一下,假设现在产品价格和库存分别是2732。那么,我们启动程序(在

 

ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE

 

语句,把价格+1,库存-2,然后价格和库存分别为2830 了,继续程序可以发现价格和

 

库存分别是2731。产品价格没有变化,库存-1了,都是我们程序的功劳,SQL语句的

 

更新被放弃了。

 

然后,我们再来修改一下解决并发的方式:

 

cc.Resolve(RefreshMode.KeepChanges);// 原先更新有效,冲突字段以当前更新为准

 

来测试一下,假设现在产品价格和库存分别是2732。那么,我们启动程序(在

 

ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE

 

语句,把价格+1,库存-2,然后价格和库存分别为2830 了,继续程序可以发现价格和

 

库存分别是2831。这就是默认方式,在保持原先更新的基础上,对于发生冲突的字段以

 

最后更新为准。

 

我们甚至还可以针对不同的字段进行不同的处理策略:

 

 

foreach (ObjectChangeConflict ccinctx.ChangeConflicts)

 

{

 

Product p = (Product)cc.Object;

 

foreach (MemberChangeConflict mcincc.MemberConflicts)

 

       {

 

string currVal = mc.CurrentValue.ToString();

 

string origVal = mc.OriginalValue.ToString();

 

string databaseVal = mc.DatabaseValue.ToString();

 

MemberInfo mi = mc.Member;

 

string memberName = mi.Name;



 

       Response.Write(p.ProductID + " " + mi.Name + " " + currVal + " " + origVal +" "+

 

databaseVal + "<br/>");

 

if (memberName =="UnitsInStock")

 

                        mc.Resolve(RefreshMode.KeepCurrentValues);//  放弃原先更新,所有更新

 

以当前更新为准

 

else if (memberName =="UnitPrice")

 

                        mc.Resolve(RefreshMode.OverwriteCurrentValues);//  放弃当前更新,所有

 

更新以原先更新为准

 

else

 

                        mc.Resolve(RefreshMode.KeepChanges);//  原先更新有效,冲突字段以当

 

前更新为准

 

 

 

 

        }

 

}

 

比如上述代码就对库存字段作放弃原先更新处理,对价格字段作放弃当前更新处理。我

 

们来测试一下,假设现在产品价格和库存分别是2732。那么,我们启动程序(在

 

ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE


语句,把价格+1,库存-2,然后价格和库存分别为2830 了,继续程序可以发现价格和

 

库存分别为2831 了。说明对价格的处理确实保留了原先的更新,对库存的处理保留了

 

当前的更新。页面上显示的结果如下图:

 

最后,我们把提交语句修改为:

 

 

ctx.SubmitChanges(ConflictMode.FailOnFirstConflict);

 

表示第一次发生冲突的时候就不再继续了,然后并且去除最后的ctx.SubmitChanges();

 

语句。来测试一下,在执行了SQL后再继续程序可以发现界面上只输出了数字1,说明在

 

第一条记录失败后,后续的并发冲突就不再处理了。


原创粉丝点击