前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一步一步学Linq to sql(七):并发与事务

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

作者头像
aehyok
发布2018-09-11 12:22:50
5490
发布2018-09-11 12:22:50
举报
文章被收录于专栏:技术博客

前言

检测并发

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

代码语言:javascript
复制
select UnitPrice,UnitsInStock,* from Products where categoryID=1

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

代码语言:javascript
复制
        static void Main(string[] args)
        {
            NorthWindDataContext ctx = new NorthWindDataContext();
            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(); // 在这里设断点
        }

我们使用调试方式启动,由于设置了断点,程序并没有进行更新操作。此时,我们在数据库中运行下面的语句:

代码语言:javascript
复制
update products 
set unitsinstock = 2, unitprice= unitprice + 1
where categoryid = 1

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

当客户端提交的修改对象自读取之后已经在数据库中发生改动,就产生了修改并发。解决并发的包括两步,一是查明哪些对象发生并发,二是解决并发。如果你仅仅是希望更新时不考虑并发的话可以关闭相关列的更新验证,这样在这些列上发生并发就不会出现异常:

代码语言:javascript
复制
[global::System.Data.Linq.Mapping.ColumnAttribute(Storage="_UnitsInStock", DbType="SmallInt",UpdateCheck= UpdateCheck.Never)]
public System.Nullable<short> UnitsInStock
{
    get
    {
        return this._UnitsInStock;
    }
    set
    {
        if ((this._UnitsInStock != value))
        {
            this.OnUnitsInStockChanging(value);
            this.SendPropertyChanging();
            this._UnitsInStock = value;
            this.SendPropertyChanged("UnitsInStock");
            this.OnUnitsInStockChanged();
        }
    }
}

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

解决并发

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

代码语言:javascript
复制
            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)
                {
                    Products p = (Products)cc.Object;
                    Console.Write(p.ProductID + "<br/>");
                    cc.Resolve(RefreshMode.OverwriteCurrentValues); // 放弃当前更新,所有更新以原先更新为准
                }
            }
            ctx.SubmitChanges();

            Console.ReadLine();

首先可以看到,我们使用try{}catch{}来捕捉并发冲突的异常。在SubmitChanges的时候,我们选择了ConflictMode.ContinueOnConflict选项。也就是说遇到并发了还是继续。在catch{}中,我们从ChangeConflicts中获取了并发的对象,然后经过类型转化后输出了产品ID,然后选择的解决方案是RefreshMode.OverwriteCurrentValues。也就是说,放弃当前的更新,所有更新以原先更新为准。

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

代码语言:javascript
复制
    // 摘要:
    //     定义 Overload:System.Data.Linq.DataContext.Refresh 方法如何处理开放式并发冲突。
    public enum RefreshMode
    {
        // 摘要:
        //     强制 Overload:System.Data.Linq.DataContext.Refresh 方法使用从数据库检索的值替换原始值。不会修改当前值。
        KeepCurrentValues = 0,
        //
        // 摘要:
        //     强制 Overload:System.Data.Linq.DataContext.Refresh 方法保留已更改的当前值,但将其他值更新为数据库值。
        KeepChanges = 1,
        //
        // 摘要:
        //     强制 Overload:System.Data.Linq.DataContext.Refresh 方法使用数据库中的值重写所有当前值。
        OverwriteCurrentValues = 2,
    }

处理并发时,从这三个选择合适的就可以了。

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

代码语言:javascript
复制
            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)
                {
                    Products p = (Products)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;
                        Console.WriteLine(p.ProductID + " " + mi.Name + " " + currVal + " " + origVal + " " + databaseVal);
                        if (memberName == "UnitsInStock")
                            mc.Resolve(RefreshMode.KeepCurrentValues); // 放弃原先更新,所有更新以当前更新为准
                        else if (memberName == "UnitPrice")
                            mc.Resolve(RefreshMode.OverwriteCurrentValues); // 放弃当前更新,所有更新以原先更新为准
                        else
                            mc.Resolve(RefreshMode.KeepChanges); // 原先更新有效,冲突字段以当前更新为准

                    }
                }

            }
            Console.ReadLine();

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

ctx.SubmitChanges(ConflictMode.FailOnFirstConflict);

事务处理

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

代码语言:javascript
复制
            ctx.Products.InsertOnSubmit(new Products { ProductID =1, ProductName = "zhuye1" });
            ctx.Products.InsertOnSubmit(new Products { ProductID = 2, ProductName = "zhuye" });
            ctx.SubmitChanges();

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

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

代码语言:javascript
复制
            if (ctx.Connection != null) ctx.Connection.Open();
            DbTransaction tran = ctx.Connection.BeginTransaction();
            ctx.Transaction = tran;
            try
            {
                CreateProduct(new Products { ProductID = 1, ProductName = "zhuye1" });
                CreateProduct(new Products { ProductID = 2, ProductName = "zhuye2" });
                tran.Commit();
            }
            catch
            {
                tran.Rollback();
            }
代码语言:javascript
复制
        private static void CreateProduct(Products c)
        {
            ctx.Products.InsertOnSubmit(c);
            ctx.SubmitChanges();
        }

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

代码语言:javascript
复制
            //使用之前必须添加对 System.Transactions.dll 的引用。 
            using (TransactionScope scope = new TransactionScope())
            {
                CreateProduct(new Products { ProductID = 1, ProductName = "zhuye1" });
                CreateProduct(new Products { ProductID = 2, ProductName = "zhuye2" });
                scope.Complete();
            }

示例代码下载地址http://files.cnblogs.com/aehyok/LinqApp.rar

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2013-04-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
数据库
云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档