一步一步學Linq to sql(七) 併發與事務

2021-05-28 08:39:06 字數 4045 閱讀 4971

測併發

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

trycatch (changeconflictexception)

}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)

比如上述**就對庫存欄位作放棄原先更新處理,對**欄位作放棄當前更新處理。我們來測試一下,假設現在產品**和庫存分別是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 );

ctx.customers.add(new customer );

ctx.submitchanges();

假設資料庫中已經存在顧客id為「abcde」的記錄,那麼第二次插入操作失敗將會導致第一次的插入操作失效。執行程式後會得到乙個異常,查詢資料庫發現「abcdf」這個顧客也沒有插入到資料庫中。

如果每次更新後直接提交修改,那麼我們可以使用下面的方式做事務:

if (ctx.connection != null) ctx.connection.open();

dbtransaction tran = ctx.connection.begintransaction();

ctx.transaction = tran;

try);

createcustomer(new customer );

tran.commit();

}catch

private void createcustomer(customer c)

執行程式後發現增加顧客abcdf的操作並沒有成功。或者,我們還可以通過transactionscope實現事務:

using (transactionscope scope = new transactionscope())

);createcustomer(new customer );

scope.complete();

一步一步學Linq to sql

一步一步學linq to sql 一 預備知識 一步一步學linq to sql 二 datacontext與實體 一步一步學linq to sql 三 增刪改 一步一步學linq to sql 四 查詢句法 一步一步學linq to sql 五 儲存過程 一步一步學linq to sql 六 特性...

一步一步學Linq to sql(七) 併發與事務

檢測併發 首先使用下面的 sql語句查詢資料庫的產品表 select from products where categoryid 1 查詢結果如下圖 為了看起來清晰,我已經事先把所有分類為 1產品的 和庫存修改為相同值了。然後執行下面的程式 var query from p in ctx.prod...

一步一步學Linq to sql(七) 併發與事務

檢測併發 首先使用下面的sql語句查詢資料庫的產品表 select from products where categoryid 1 查詢結果如下圖 為了看起來清晰,我已經事先把所有分類為1產品的 和庫存修改為相同值了。然後執行下面的程式 var query from p in ctx.produc...