检测并发

首先使用下面的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开始详细讲解.一步一步学Linq to sql(一):预备知识 什么是Linq to sql Linq to sql(或者叫DLINQ)是LINQ(.NET语言集 ...

  2. 【转】一步一步学Linq to sql(五):存储过程

    普通存储过程 首先在查询分析器运行下面的代码来创建一个存储过程: create proc sp_singleresultset as set nocount on select * from cust ...

  3. 一步一步学Linq to sql(十):分层构架的例子

    项目介绍 这节将要把<一步一步学Linq to sql(三):增删改>中留言簿的例子修改为使用WCF的多层构架.我们将会建立以下项目: l         A,网站项目 WebSite:留 ...

  4. 一步一步学Linq to sql(六):探究特性

      延迟执行 IQueryable query = from c in ctx.Customers select c; 这样的查询句法不会导致语句立即执行,它仅仅是一个描述,对应一个SQL.仅仅在需要 ...

  5. 一步一步学linq to sql(四)查询句法

    select 描述:查询顾客的公司名.地址信息 查询句法: var 构建匿名类型1 = from c in ctx.Customers select new { 公司名 = c.CompanyName ...

  6. 一步一步学linq to sql(二)

    DataContext与实体 DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方和把实体的修 ...

  7. 一步一步学Linq to sql(八):继承与关系

    论坛表结构 为了演示继承与关系,我们创建一个论坛数据库,在数据库中创建三个表: 1.  论坛版块分类表 dbo.Categories: 字段名 字段类型 可空 备注 CategoryID int no ...

  8. 一步一步学Linq to sql(三):增删改

    一.示例数据库 在数据库中创建一个名为GuestBook 的数据库, 在里面创建一个 tbGuestBook 的表,结构如下表. . 二.生产实体类 右键点击网站项目,选择添加新项,然后选择" ...

  9. 一步一步学Linq to sql:增删改

    示例数据库 字段名 字段类型 允许空 字段说明 ID uniqueidentifier 表主键字段 UserName varchar(50) 留言用户名 PostTime datetime 留言时间 ...

最新文章

  1. 我眼中真正优秀的CTO
  2. 熟悉Linux的环境实验报告,实验1 熟悉Linux开发环境 实验报告
  3. 021_Jedis的ZSet数据类型
  4. OpenCV使用基本算法检测颜色检查器
  5. 锐捷服务器虚拟化技术_数据中心边缘虚拟交换(IEEE 802.1Qbg)技术白皮书
  6. 网络编程(part3)--文件读写之二进制文件读写/with操作
  7. python处理完数据导入数据库_python 将execl测试数据导入数据库操作
  8. Vue优化策略_项目发布_01
  9. Java中数组的打印
  10. Eclipse中错误为 Access restriction 的解决方案
  11. 驰声科技推出AI对话考辅系统 将VR引入语音评测
  12. Android eclipse sdk包升级
  13. Java从入门到精通 第12章 类的封装、继承与多态
  14. NLTK(自然语言工具包)
  15. Unity基础——刚体
  16. 10位Swift和iOS开发大师
  17. c语言实验:厘米换算英尺英寸
  18. 天线和频率(波长)关系
  19. 朱啸虎建议创业者忘记区块链,遭应书岭回讽:你老了
  20. 翡翠手链更能够突显佩戴者的非凡气质

热门文章

  1. Memcached缓存实例
  2. CPlus的简单线程的制作
  3. sql去除重复语句(转)
  4. How Browser Works
  5. JS判断数字字母中文
  6. 如何设计出一个有灵魂的「签到功能」?
  7. 俞军:为什么多数产品经理都不合格
  8. 《简明 PHP 教程》01 关于 PHP
  9. 利用原生js 如何实现div移动?
  10. WebForms 开发基础