Linq to Sql事务提交

前言:

    检测并发
    首先使用下面的SQL语句查询数据库的产品表:
select UnitPrice,UnitsInStock,* from Products where categoryID=1

在这里插入图片描述

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

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(); // 在这里设断点
        }

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

update products 
set unitsinstock = 2, unitprice= unitprice + 1
where categoryid = 1

然后在继续程序,会得到修改并发(乐观并发冲突)的异常,提示要修改的行不存在或者已经被改动。
在这里插入图片描述
当客户端提交的修改对象自读取之后已经在数据库中发生改动,就产生了修改并发。解决并发的包括两步,一是查明哪些对象发生并发,二是解决并发。如果你仅仅是希望更新时不考虑并发的话可以关闭相关列的更新验证,这样在这些列上发生并发就不会出现异常:

[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是我们程序之后更新的功劳。当在同一个字段上(库存)发生并发冲突的时候,默认是最后的那次更新获胜。

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的产品都进行修改的)。
// 摘要:
// 定义 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,
}
处理并发时,从这三个选择合适的就可以了。

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

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在提交更新的时候默认会创建事务,一部分修改发生错误的话其它修改也不会生效:

 ctx.Products.InsertOnSubmit(new Products { ProductID =1, ProductName = "zhuye1" });
ctx.Products.InsertOnSubmit(new Products { ProductID = 2, ProductName = "zhuye" });
ctx.SubmitChanges();

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

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

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();
            }
  private static void CreateProduct(Products c)
        {
            ctx.Products.InsertOnSubmit(c);
            ctx.SubmitChanges();
        }

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

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

参考网址:
https://www.cnblogs.com/happyhippy/archive/2010/01/27/1657552.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值