Linq to sql并发与事务
本文转载: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并发与事务的更多相关文章
- Linq to Sql : 并发冲突及处理策略
原文:Linq to Sql : 并发冲突及处理策略 1. 通过覆盖数据库值解决并发冲突 try { db.SubmitChanges(ConflictMode.ContinueOnConflict) ...
- Linq to Sql并发冲突及处理策略
0. 并发冲突的示例 单用户的系统现在应该比较罕见了,一般系统都会有很多用户在同时进行操作:在多用户系统中,涉及到的一个普遍问题:当多个用户“同时”更新(修改或者删除)同一条记录时,该如何更新呢? ...
- LINQ to SQL语句(13)之开放式并发控制和事务
Simultaneous Changes开放式并发控制 下表介绍 LINQ to SQL 文档中涉及开放式并发的术语: 术语 说明 并发 两个或更多用户同时尝试更新同一数据库行的情形. 并发冲突 两个 ...
- 年终巨献 史上最全 ——LINQ to SQL语句
LINQ to SQL语句(1)之Where 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子句.Where操 ...
- LINQ TO SQL 大全
最近悟出来一个道理,在这儿分享给大家:学历代表你的过去,能力代表你的现在,学习代表你的将来. 十年河东十年河西,莫欺少年穷 学无止境,精益求精 LINQ to SQL语句(1)之Where 适用场景: ...
- LINQ to SQL大全
LINQ to SQL语句 (1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的 ...
- [转]LINQ To SQL 语法及实例大全
转载自:http://blog.csdn.net/pan_junbiao/article/details/7015633 LINQ to SQL语句(1)之Where Where操作 适用场景:实现过 ...
- LINQ to SQL语句非常详细(原文来自于网络)
LINQ to SQL语句(1)之Where Where操作 适用场景:实现过滤,查询等功能. 说明:与SQL命令中的Where作用相似,都是起到范围限定也就是过滤作用的,而判断条件就是它后面所接的子 ...
- LINQ To SQL 语法及实例大全
http://blog.csdn.net/pan_junbiao/article/details/7015633 http://blog.csdn.net/pan_junbiao/article/de ...
随机推荐
- 关于jQuery的cookies插件2.2.0版设置过期时间的说明
欢迎转载,转载请注明作者RunningOn jQuery应该是各位用JavaScript做web开发的常用工具了,它有些插件能非常方便地操作cookie. 不过非常让人郁闷的是,网上几乎所有人对于这些 ...
- Fedora 18 安装前指南
Secure Boot 与 Win 8 随着 Win8 的发布,先前关于 Secure Boot 和 UEFI 的诸多猜测也得到了证实,Fedora 18 也将如同当初计划的那样使用 shim + ...
- ECSHOP在商品详细页面上获取该商品的顶级分类id和名称
在 goods.php 文件, 找到 $smarty->assign('goods', $goods); 在它上面增加下面代码: 方法一: $cat_arr = get_parent_cats( ...
- mysql数据类型——整型INT(m)
1.整形分为四种 tinyint smallint mediumint int bigint 注意: 右侧的取值范围是在未加unsigned关键字的情况下,如果加了unsigned,则最大值翻倍,如t ...
- 使用windows live writer 编辑博客日志
使用Windows Live Writer 编辑日志 一 意义 写博客日志是个需要坚持的好习惯.使用Windows Live Writer,能不受网页自带编辑器限制. Markdown支持.安装mar ...
- Title of live Writer
Test From Windows Live Writer **markdown bold**
- 10条PHP高级技巧
1.使用一个SQL注射备忘单 一个基本的原则就是,永远不要相信用户提交的数据. 另一个规则就是,在你发送或者存储数据时对它进行转义(escape). 可以总结为:filter input, escap ...
- hibernate 一张数据表的流程
1. 写一个domain类来映射数据库表 2. 写一个*.hbm.xml文件来配置映射 <?xml version="1.0"?> <!DOCTYPE hiber ...
- BZOJ 1501 智慧珠游戏
Description Input 文件中包含初始的盘件描述,一共有10行,第i行有i个字符.如果第i行的第j个字符是字母”A”至”L”中的一个,则表示第i行第j列的格子上已经放了零件,零件的编号为对 ...
- Java中swap解惑
直接上代码…… public class Swap { public static void main(String[] args) { int a[] = new int[]{1,2}; Syste ...