一步一步学Linq to sql(七):并发与事务

检测并发

首先使用下面的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();         }

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员的SOD蜜

一行代码调用实现带字段选取+条件判断+排序+分页功能的增强ORM框架

问题:3行代码 PDF.NET 是一个开源的数据开发框架,它的特点是简单、轻量、快速,易上手,而且是一个注释完善的国产开发框架,受到不少朋友的欢迎,也在我们公...

2979
来自专栏跟着阿笨一起玩NET

Linq to sql并发与事务

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

842
来自专栏大内老A

事件(Event),绝大多数内存泄漏(Memory Leak)的元凶[上篇]

最近这两天一直在忙着为一个项目检查内存泄漏(Memory Leak)的问题,对相关的知识进行了一下简单的学习和探索,其间也有了一些粗浅的经验积累,今天特意写一篇...

2466
来自专栏Golang语言社区

游戏服务器之内存数据库redis客户端应用(下)

(3)存储一个角色的基础信息(使用命令set) 存储结构: key:BASE角色id ,value 角色基础信息 int playerId = player-...

5608
来自专栏木宛城主

SharePoint下利用DocX组件导出Word

平常开发时,或多或少都需要和Word打交道,特变是编辑、导出Word。 利用DocX,开源的读写Word组件,可以快速帮助我们进行对Word的操作。 Do...

2435
来自专栏Create Sun

access基本操作(c#操作,远程连接,执行sql,加密,备份)

前言  最近项目用到了access,是的就是access,工作在桌面型的小数据库应用还是会用到的,如果你确定永远不会遇到access的操作,请忽略此篇文章 1....

4008
来自专栏GIS讲堂

C#连接Sqlite

SQLite,是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌...

1322
来自专栏dotnet & java

WCF 入门 (17)

首先修改服务端的app.config 配置文件,先使用basicHttpBinding

832
来自专栏大内老A

如何解决分布式系统中的跨时区问题[原理篇]

《谈谈你最熟悉的System.DateTime[上篇][下篇]》从跨时区的角度对DateTime这个我们熟知的类型进行了深入探讨,它们都是为这篇文章作的准备工作...

1927
来自专栏NetCore

利用反射自己写的一个ModelHelper类

开发中 很多人都会使用BLL Model这种开发,我也是,虽然现在有很多的自动生成工具,能在几秒内生成cs的模板,但我个人还不是很喜欢,我还是喜欢自己一个一个去...

2866

扫码关注云+社区

领取腾讯云代金券