一步一步学Linq to sql(七):并发与事务
??
?
檢測(cè)并發(fā)
?
?????? 首先使用下面的SQL語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù)的產(chǎn)品表:
| select * from products where categoryid=1 |
?????? 查詢(xún)結(jié)果如下圖:
?
?????? 為了看起來(lái)清晰,我已經(jīng)事先把所有分類(lèi)為1產(chǎn)品的價(jià)格和庫(kù)存修改為相同值了。然后執(zhí)行下面的程序:
| ??????? 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(); // 在這里設(shè)斷點(diǎn) |
?????? 我們使用調(diào)試方式啟動(dòng),由于設(shè)置了斷點(diǎn),程序并沒(méi)有進(jìn)行更新操作。此時(shí),我們?cè)跀?shù)據(jù)庫(kù)中運(yùn)行下面的語(yǔ)句:
| update products set unitsinstock = unitsinstock -2, unitprice= unitprice + 1 where categoryid = 1 |
?????? 然后在繼續(xù)程序,會(huì)得到修改并發(fā)(樂(lè)觀并發(fā)沖突)的異常,提示要修改的行不存在或者已經(jīng)被改動(dòng)。當(dāng)客戶端提交的修改對(duì)象自讀取之后已經(jīng)在數(shù)據(jù)庫(kù)中發(fā)生改動(dòng),就產(chǎn)生了修改并發(fā)。解決并發(fā)的包括兩步,一是查明哪些對(duì)象發(fā)生并發(fā),二是解決并發(fā)。如果你僅僅是希望更新時(shí)不考慮并發(fā)的話可以關(guān)閉相關(guān)列的更新驗(yàn)證,這樣在這些列上發(fā)生并發(fā)就不會(huì)出現(xiàn)異常:
| [Column(Storage="_UnitsInStock", DbType="SmallInt", UpdateCheck = UpdateCheck.Never)] [Column(Storage="_UnitPrice", DbType="Money", UpdateCheck = UpdateCheck.Never)] |
?????? 為這兩列標(biāo)注不需要進(jìn)行更新檢測(cè)。假設(shè)現(xiàn)在產(chǎn)品價(jià)格和庫(kù)存分別是27和32。那么,我們啟動(dòng)程序(設(shè)置端點(diǎn)),然后運(yùn)行UPDATE語(yǔ)句,把價(jià)格+1,庫(kù)存-2,然后價(jià)格和庫(kù)存分別為28和30了,繼續(xù)程序可以發(fā)現(xiàn)價(jià)格和庫(kù)存分別是28和31。價(jià)格+1是之前更新的功勞,庫(kù)存最終是-1是我們程序之后更新的功勞。當(dāng)在同一個(gè)字段上(庫(kù)存)發(fā)生并發(fā)沖突的時(shí)候,默認(rèn)是最后的那次更新獲勝。
?
解決并發(fā)
?
?????? 如果你希望自己處理并發(fā)的話可以把前面對(duì)列的定義修改先改回來(lái),看下面的例子:
| ??????? 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); // 放棄當(dāng)前更新,所有更新以原先更新為準(zhǔn) ??????????? } ??????? } ??????? ctx.SubmitChanges(); |
?????? 首先可以看到,我們使用try{}catch{}來(lái)捕捉并發(fā)沖突的異常。在SubmitChanges的時(shí)候,我們選擇了ConflictMode.ContinueOnConflict選項(xiàng)。也就是說(shuō)遇到并發(fā)了還是繼續(xù)。在catch{}中,我們從ChangeConflicts中獲取了并發(fā)的對(duì)象,然后經(jīng)過(guò)類(lèi)型轉(zhuǎn)化后輸出了產(chǎn)品ID,然后選擇的解決方案是RefreshMode.OverwriteCurrentValues。也就是說(shuō),放棄當(dāng)前的更新,所有更新以原先更新為準(zhǔn)。
?????? 我們來(lái)測(cè)試一下,假設(shè)現(xiàn)在產(chǎn)品價(jià)格和庫(kù)存分別是27和32。那么,我們啟動(dòng)程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)這里設(shè)置端點(diǎn)),然后運(yùn)行UPDATE語(yǔ)句,把價(jià)格+1,庫(kù)存-2,然后價(jià)格和庫(kù)存分別為28和30了,繼續(xù)程序可以發(fā)現(xiàn)價(jià)格和庫(kù)存分別是28和30。之前SQL語(yǔ)句庫(kù)存-2生效了,而我們程序的更新(庫(kù)存-1)被放棄了。在頁(yè)面上也顯示了所有分類(lèi)為1的產(chǎn)品ID(因?yàn)槲覀冎暗?/span>SQL語(yǔ)句是對(duì)所有分類(lèi)為1的產(chǎn)品都進(jìn)行修改的)。
?????? 然后,我們來(lái)修改一下解決并發(fā)的方式:
| cc.Resolve(RefreshMode.KeepCurrentValues); // 放棄原先更新,所有更新以當(dāng)前更新為準(zhǔn) |
?????? 來(lái)測(cè)試一下,假設(shè)現(xiàn)在產(chǎn)品價(jià)格和庫(kù)存分別是27和32。那么,我們啟動(dòng)程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)這里設(shè)置端點(diǎn)),然后運(yùn)行UPDATE語(yǔ)句,把價(jià)格+1,庫(kù)存-2,然后價(jià)格和庫(kù)存分別為28和30了,繼續(xù)程序可以發(fā)現(xiàn)價(jià)格和庫(kù)存分別是27和31。產(chǎn)品價(jià)格沒(méi)有變化,庫(kù)存-1了,都是我們程序的功勞,SQL語(yǔ)句的更新被放棄了。
?????? 然后,我們?cè)賮?lái)修改一下解決并發(fā)的方式:
| cc.Resolve(RefreshMode.KeepChanges); // 原先更新有效,沖突字段以當(dāng)前更新為準(zhǔn) |
?????? 來(lái)測(cè)試一下,假設(shè)現(xiàn)在產(chǎn)品價(jià)格和庫(kù)存分別是27和32。那么,我們啟動(dòng)程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)這里設(shè)置端點(diǎn)),然后運(yùn)行UPDATE語(yǔ)句,把價(jià)格+1,庫(kù)存-2,然后價(jià)格和庫(kù)存分別為28和30了,繼續(xù)程序可以發(fā)現(xiàn)價(jià)格和庫(kù)存分別是28和31。這就是默認(rèn)方式,在保持原先更新的基礎(chǔ)上,對(duì)于發(fā)生沖突的字段以最后更新為準(zhǔn)。
?????? 我們甚至還可以針對(duì)不同的字段進(jìn)行不同的處理策略:
| 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); // 放棄原先更新,所有更新以當(dāng)前更新為準(zhǔn) ??????? else if (memberName == "UnitPrice") ??????????? mc.Resolve(RefreshMode.OverwriteCurrentValues); // 放棄當(dāng)前更新,所有更新以原先更新為準(zhǔn) ??????? else ??????????? mc.Resolve(RefreshMode.KeepChanges); // 原先更新有效,沖突字段以當(dāng)前更新為準(zhǔn) ? ??? } } |
?????? 比如上述代碼就對(duì)庫(kù)存字段作放棄原先更新處理,對(duì)價(jià)格字段作放棄當(dāng)前更新處理。我們來(lái)測(cè)試一下,假設(shè)現(xiàn)在產(chǎn)品價(jià)格和庫(kù)存分別是27和32。那么,我們啟動(dòng)程序(在ctx.SubmitChanges(ConflictMode.ContinueOnConflict)這里設(shè)置端點(diǎn)),然后運(yùn)行UPDATE語(yǔ)句,把價(jià)格+1,庫(kù)存-2,然后價(jià)格和庫(kù)存分別為28和30了,繼續(xù)程序可以發(fā)現(xiàn)價(jià)格和庫(kù)存分別為28和31了。說(shuō)明對(duì)價(jià)格的處理確實(shí)保留了原先的更新,對(duì)庫(kù)存的處理保留了當(dāng)前的更新。頁(yè)面上顯示的結(jié)果如下圖:
?
?
最后,我們把提交語(yǔ)句修改為:
| ctx.SubmitChanges(ConflictMode.FailOnFirstConflict); |
?????? 表示第一次發(fā)生沖突的時(shí)候就不再繼續(xù)了,然后并且去除最后的ctx.SubmitChanges();語(yǔ)句。來(lái)測(cè)試一下,在執(zhí)行了SQL后再繼續(xù)程序可以發(fā)現(xiàn)界面上只輸出了數(shù)字1,說(shuō)明在第一條記錄失敗后,后續(xù)的并發(fā)沖突就不再處理了。
?
事務(wù)處理
?
?????? Linq to sql在提交更新的時(shí)候默認(rèn)會(huì)創(chuàng)建事務(wù),一部分修改發(fā)生錯(cuò)誤的話其它修改也不會(huì)生效:
| ??????? ctx.Customers.Add(new Customer { CustomerID = "abcdf", CompanyName = "zhuye" }); ??????? ctx.Customers.Add(new Customer { CustomerID = "abcde", CompanyName = "zhuye" }); ??????? ctx.SubmitChanges(); |
?????? 假設(shè)數(shù)據(jù)庫(kù)中已經(jīng)存在顧客ID為“abcde”的記錄,那么第二次插入操作失敗將會(huì)導(dǎo)致第一次的插入操作失效。執(zhí)行程序后會(huì)得到一個(gè)異常,查詢(xún)數(shù)據(jù)庫(kù)發(fā)現(xiàn)“abcdf”這個(gè)顧客也沒(méi)有插入到數(shù)據(jù)庫(kù)中。
?????? 如果每次更新后直接提交修改,那么我們可以使用下面的方式做事務(wù):
| ??????? 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(); ??? } |
?????? 運(yùn)行程序后發(fā)現(xiàn)增加顧客abcdf的操作并沒(méi)有成功?;蛘?#xff0c;我們還可以通過(guò)TransactionScope實(shí)現(xiàn)事務(wù):
| ??????? using (TransactionScope scope = new TransactionScope()) ??????? { ??????????? CreateCustomer(new Customer { CustomerID = "abcdf", CompanyName = "zhuye" }); ??????????? CreateCustomer(new Customer { CustomerID = "abcde", CompanyName = "zhuye" }); ??????????? scope.Complete(); ??????? } |
??????
總結(jié)
以上是生活随笔為你收集整理的一步一步学Linq to sql(七):并发与事务的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 解决EclipseSDK3.3.0无法启
- 下一篇: WINDOWS SERVER 2003从