在Linq to Sql中管理并發更新時的沖突(3):使用記錄的時間戳進行檢測
在《在Linq to Sql中管理并發更新時的沖突(2):引發更新沖突》一文中,我們描述了Linq to Sql檢測在更新時是否產生了沖突的基本方法:將該記錄每個字段原來的值和更新時的值進行對比,如果稍有不同則意味著記錄被修改過,因此產生了更新沖突。不過您是否有這樣的感覺,這種方法實在累贅了一些?如果一個表中有數十個字段,那么更新就必須完整地檢測一遍(不過我會在今后的文章中提到這方面的控制)。再者,如果其中某一個字段儲存了洋洋灑灑上萬字的文章,那么在驗證時僅僅是將它從Web服務器發送到數據庫服務器就需要耗費可觀的帶寬與時間,這是不是顯得有些“得不償失”呢?
因此Linq to Sql提供了另外一種檢測并發更新沖突的方式:使用記錄的時間戳。這并不是Linq to Sql特有的功能,如果您了解其他的ORM框架的話,就會發現諸如Hibernate也提供了類似的機制——自然,在使用上不會像Linq to Sql那樣方便。
在Sql Server中設計數據表時,我們可以使用一個特殊的數據類型:timestamp。請不要將它與SQL-2003標準中的timestamp類型混淆起來,那里的timestamp和Sql Server中的datetime比較相似(Oracle中timestamp的概念符合SQL-2003標準,而MySql中timestamp的概念與Sql Server相同),而Sql Server中的timestamp與SQL-2003標準中的rowversion類型對應。Sql Server中的timestamp類型和binary(8)在存儲上非常類似(不過nullable的timestamp和nvarchar(8)類似),從類型名稱上我們就可以看出,這是一個“時間戳”字段:當數據表中的某一條記錄被添加或者修改之后,Sql Server會自動向類型為timestamp的字段寫入當前時間。換句話說,只要在更新時發現該字段的值沒有被修改過,就表明沒有產生并發沖突。
我們還是通過一個例子來體驗一下吧。
![]() |
![]() |
如上圖。我們定義了一個新的數據表,其中有個record_version字段為timestamp類型,這就是記錄的時間戳(record_version這個字段名似乎有點不太“雅觀”,我覺得我們不會去主動使用它,所以問題不大——當然一些靜態檢查工具可不這么認為:))。有了記錄的時間戳,我們就可以在檢測更新沖突時獲得更好的性能了。
try { LinqToSqlDemoDataContext dataContext = new LinqToSqlDemoDataContext(); Order order = dataContext.Orders.Single(o => o.OrderID == 1); order.Name = "New Order Name"; dataContext.Log = Console.Out; // 在下面的語句上設置一個斷點 dataContext.SubmitChanges(); } catch (ChangeConflictException e) { Console.WriteLine(e.Message); } Console.ReadLine();
在最后的語句上設置斷點,并且在程序運行至斷點后去數據庫里對OrderID為1的紀錄作任意更新。然后按F5繼續運行:
UPDATE [dbo].[Order] SET [Name] = @p2 WHERE ([OrderID] = @p0) AND ([record_version] = @p1) SELECT [t1].[record_version] FROM [dbo].[Order] AS [t1] WHERE ((@@ROWCOUNT) > 0) AND ([t1].[OrderID] = @p3) -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- @p1: Input Timestamp (Size = 8; Prec = 0; Scale = 0) [SqlBinary(8)] -- @p2: Input NVarChar (Size = 14; Prec = 0; Scale = 0) [New Order Name] -- @p3: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8
上面代碼中的UPDATE語句相信大家都很清楚其含義。不過這里可能還需要解釋其他兩個問題:
首先是那句SELECT語句。如果您去閱讀自動生成的Object Model的代碼時就會發現,record_version屬性上有一個ColumnAttribute標記(假設您使用了Attribute Based Mapping Source),其AutoSync屬性為Always,因此在任何操作之后,Linq to Sql都會補充一句SELECT語句,以此獲得新的數據并修改DataContext中的特定對象。其次,由于timestamp類型的數據在記錄被修改時就會設置,因此在更新時其他紀錄的值與之前相同,也會引發更新沖突,這一點和基于字段值比較的前一種方法是不同的。