本文转载:http://www.cnblogs.com/lovecherry/archive/2007/08/20/862365.html

检测并发

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

select * from products where categoryid=1

查询结果如下图:

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

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

foreach (var p in query)

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)]

为这两列标注不需要进行更新检测。假设现在产品价格和库存分别是27和32。那么,我们启动程序(设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和31。价格+1是之前更新的功劳,库存最终是-1是我们程序之后更新的功劳。当在同一个字段上(库存)发生并发冲突的时候,默认是最后的那次更新获胜。

 

解决并发

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

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

foreach (var p in query)

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

try

{

ctx.SubmitChanges(ConflictMode.ContinueOnConflict);

}

catch (ChangeConflictException)

{

foreach (ObjectChangeConflict cc in ctx.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。也就是说,放弃当前的更新,所有更新以原先更新为准。

我们来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和30。之前SQL语句库存-2生效了,而我们程序的更新(库存-1)被放弃了。在页面上也显示了所有分类为1的产品ID(因为我们之前的SQL语句是对所有分类为1的产品都进行修改的)。

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

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

来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是27和31。产品价格没有变化,库存-1了,都是我们程序的功劳,SQL语句的更新被放弃了。

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

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

来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别是28和31。这就是默认方式,在保持原先更新的基础上,对于发生冲突的字段以最后更新为准。

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

foreach (ObjectChangeConflict cc in ctx.ChangeConflicts)

{

Product p = (Product)cc.Object;

foreach (MemberChangeConflict mc in cc.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); // 原先更新有效,冲突字段以当前更新为准

}

}

比如上述代码就对库存字段作放弃原先更新处理,对价格字段作放弃当前更新处理。我们来测试一下,假设现在产品价格和库存分别是27和32。那么,我们启动程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)这里设置端点),然后运行UPDATE语句,把价格+1,库存-2,然后价格和库存分别为28和30了,继续程序可以发现价格和库存分别为28和31了。说明对价格的处理确实保留了原先的更新,对库存的处理保留了当前的更新。页面上显示的结果如下图:

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

ctx.SubmitChanges(ConflictMode.FailOnFirstConflict);

表示第一次发生冲突的时候就不再继续了,然后并且去除最后的ctx.SubmitChanges();语句。来测试一下,在执行了SQL后再继续程序可以发现界面上只输出了数字1,说明在第一条记录失败后,后续的并发冲突就不再处理了。

 

事务处理

Linq to sql在提交更新的时候默认会创建事务,一部分修改发生错误的话其它修改也不会生效:

ctx.Customers.Add(new Customer { CustomerID = "abcdf", CompanyName = "zhuye" });

ctx.Customers.Add(new Customer { CustomerID = "abcde", CompanyName = "zhuye" });

ctx.SubmitChanges();

假设数据库中已经存在顾客ID为“abcde”的记录,那么第二次插入操作失败将会导致第一次的插入操作失效。执行程序后会得到一个异常,查询数据库发现“abcdf”这个顾客也没有插入到数据库中。

如果每次更新后直接提交修改,那么我们可以使用下面的方式做事务:

if (ctx.Connection != null) ctx.Connection.Open();

DbTransaction tran = ctx.Connection.BeginTransaction();

ctx.Transaction = tran;

try

{

CreateCustomer(new Customer { CustomerID = "abcdf", CompanyName = "zhuye" });

CreateCustomer(new Customer { CustomerID = "abcde", CompanyName = "zhuye" });

tran.Commit();

}

catch

{

tran.Rollback();

}

private void CreateCustomer(Customer c)

{

ctx.Customers.Add(c);

ctx.SubmitChanges();

}

运行程序后发现增加顾客abcdf的操作并没有成功。或者,我们还可以通过TransactionScope实现事务:

using (TransactionScope scope = new TransactionScope())

{

CreateCustomer(new Customer { CustomerID = "abcdf", CompanyName = "zhuye" });

CreateCustomer(new Customer { CustomerID = "abcde", CompanyName = "zhuye" });

scope.Complete();

}

Linq to sql并发与事务的更多相关文章

  1. Linq to Sql : 并发冲突及处理策略

    原文:Linq to Sql : 并发冲突及处理策略 1. 通过覆盖数据库值解决并发冲突 try { db.SubmitChanges(ConflictMode.ContinueOnConflict) ...

  2. Linq to Sql并发冲突及处理策略

    0. 并发冲突的示例 单用户的系统现在应该比较罕见了,一般系统都会有很多用户在同时进行操作:在多用户系统中,涉及到的一个普遍问题:当多个用户“同时”更新(修改或者删除)同一条记录时,该如何更新呢?   ...

  3. LINQ to SQL语句(13)之开放式并发控制和事务

    Simultaneous Changes开放式并发控制 下表介绍 LINQ to SQL 文档中涉及开放式并发的术语: 术语 说明 并发 两个或更多用户同时尝试更新同一数据库行的情形. 并发冲突 两个 ...

  4. 年终巨献 史上最全 ——LINQ to SQL语句

    LINQ to SQL语句(1)之Where 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句.Where操 ...

  5. LINQ TO SQL 大全

    最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 LINQ to SQL语句(1)之Where 适用场景: ...

  6. LINQ to SQL大全

    LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...

  7. [转]LINQ To SQL 语法及实例大全

    转载自:http://blog.csdn.net/pan_junbiao/article/details/7015633 LINQ to SQL语句(1)之Where Where操作 适用场景:实现过 ...

  8. LINQ to SQL语句非常详细(原文来自于网络)

    LINQ to SQL语句(1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子 ...

  9. LINQ To SQL 语法及实例大全

    http://blog.csdn.net/pan_junbiao/article/details/7015633 http://blog.csdn.net/pan_junbiao/article/de ...

随机推荐

  1. java数据类型学习

    java数据类型基本分为两类: 一类为基本数据类型: 数值类型: 整数类型:byte.short.int.long 浮点类型:float.double 字符类型:char 布尔类型:boolean 一 ...

  2. 判断JS类型

    (function (root, factory) { if (typeof define === 'function' && define.amd) { define([], fac ...

  3. Java字符串学习

    Java中字符串是String类的实例,字符串也是对象,所以 Java将字符串作为对象进行管理 Java使用java.lang 包中的String类来创建字符串. 1.定义字符串: 使用 " ...

  4. WdatePicker.js 日期时间插件

    支持功能: 1.支持常规在input单击或获得焦点时调用,还支持使用其他的元素如:<img><div>等触发WdatePicker函数来调用弹出日期框 @1.input 调用: ...

  5. SAE 安装未包含的第三方依赖包

    如何使用virtualenv管理依赖关系 当你的应用依赖很多第三方包时,可以使用virtualenv来管理并导出这些依赖包,流程如下: 首先,创建一个全新的Python虚拟环境目录ENV,启动虚拟环境 ...

  6. google zxing 二维码扫描(android client分析)

    一.总体架构 二.架构分析 1. com.google.zxing.client.android AmbientLightManager 环境光线管理 Detects ambient light an ...

  7. 转:推荐!国外程序员整理的 C++ 资源大全

    原文来自于:http://blog.jobbole.com/78901/ 关于 C++ 框架.库和资源的一些汇总列表,由 fffaraz 发起和维护. 内容包括:标准库.Web应用框架.人工智能.数据 ...

  8. SharePoint 2013 WebTemplates

    SharePoint 2013 WebTemplates You are here: Home / SharePoint 2013 WebTemplates   January 24, 2013 Ta ...

  9. codeforces D. Long Path

    http://codeforces.com/contest/408/problem/D 题意:有一排房间每个房间有两扇门,一扇通往第i+1个房间,另一扇通往第p[i]个房间,(p[i]<=i)然 ...

  10. Keil MDK与h-jtag联调

    keil MDK也是可以借助h-jtag进行单步调试,写出来与大家一起分享一下. keil MDK编译器使用V4.01版本,下载地址:http://www.embedinfo.com/down-lis ...