浅析Entity Framework Core中的并发处理
前言
Entity Framework Core 2.0更新也已經有一段時間了,園子里也有不少的文章..
本文主要是淺析一下Entity Framework Core的并發處理方式.?
1.常見的并發處理策略
要了解如何處理并發,就要知道并發的一般處理策略
悲觀并發策略
悲觀并發策略,正如其名,它指的是對數據被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守悲觀的態度,因此,在整個數據處理過程中,將數據處于鎖定狀態。悲觀并發策略大多數情況下依靠數據庫的鎖機制實現,以保證操作最大程度的獨占性。但隨之而來的就是數據庫性能的巨大開銷,特別是對長事務而言,這樣的開銷在大量的并發情況下往往無法承受。
樂觀并發策略
樂觀并發策略,一般是基于數據版本 Version記錄機制實現。何謂數據版本?即為數據增加一個版本標識,在基于數據庫表的版本解決方案中,一般是通過為數據庫表增加一個 “version” 字段來實現.讀取出數據時,將此版本號一同讀出,之后更新時,對此版本號加一。此時,將提交數據的版本數據與數據庫表對應記錄的當前版本信息進行比對,如果提交的數據版本號大于數據庫表當前版本號,則予以更新,否則認為是過期數據。需要注意的是,樂觀并發策略機制往往基于系統中的數據存儲邏輯,因此也具備一定的局限性.
本篇就是講解,如何在我們的Entity Framework Core中來使用和自定義我們的并發策略?
2.Entity Framework Core并發令牌
要使用Entity Framework Core中的并發策略,就需要使用我們的并發令牌(ConcurrencyCheck)
在Entity Framework Core中,并發的默認處理方式是無視并發沖突的,任何修改語句在條件符合的情況下,都可以修改成功.
在高并發的情況下這種處理方式,肯定會給我們的數據庫帶來很多臟數據,所以,Entity Framework Core提供了并發令牌(ConcurrencyCheck)這個特性.
如果一個屬性被配置為并發令牌,則EF將在保存這條記錄時,會檢查沒有其他用戶修改過數據庫中的這個屬性的值。EF使用了樂觀并發策略,這意味著它將假定值沒有改變,并嘗試保存數據,但如果發現值已更改,則拋出異常。
舉個例子,我們有一個用戶類(User),我們配置?User中的 Name為并發令牌。這意味著,如果一個用戶試圖保存一個有些變化的?User,但另一個用戶已經改變了?Name那么將拋出一個異常。這在應用中一般是可取的,以便我們的應用程序可以提示用戶,在保存他們的改變之前,以確保此記錄仍然代表同一個姓名的人。
2.1并發令牌在EF中工作的原理
當我們配置User中的Name為令牌的時候,EF會將并發令牌包含在Where、Update或delete命令的子句中并檢查受影響的行數來實現驗證。如果并發令牌仍然匹配,則一行將被更新。如果數據庫中的值已更改,則不會更新任何行。
比如,當我們設置Name為并發令牌,然后通過ID來修改User的PassWord的時候,EF會生成如下的修改語句:
UPDATE [User] SET [PassWord] = @p1WHERE [ID] = @p0 AND [Name] = @p2;當然,這時候,Name不匹配了,受影響的行數返回為0.
2.2并發令牌的使用約定
? ? 屬性默認不被配置為并發令牌。
?
2.3并發令牌的使用方式
1.直接使用特性,如下配置UserName為并發令牌:
? ? ?public int Id { get; set; }[ConcurrencyCheck] ? ?
? ??public string UserName { get; set; } ?
? ? ?public string PassWord { get; set; } ? ?
? ? ?public int? ClassId { get; set; } }
?2.使用FluentAPI配置屬性為并發令牌
? ?public DbSet<UserTable> People { get; set; } ?
? ?protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<UserTable>().Property(p => p.UserName).IsConcurrencyToken();} }
以上2種方式,效果是一樣的.
?
2.4使用時間戳和行級版本號
我們知道,SQL Server給我們提供了時間戳的屬性(當然,幾乎所有的關系數據庫都有這個).下面舉個SQL Server的例子
我們加一個時間戳字段為TimestampV,加上特性Timestamp,實體代碼如下:
public partial class UserTable{ ? ? ?? ? ? ??public int Id { get; set; } ? ? ?
? ? ? ??public string UserName { get; set; } ?
? ? ? ? ?public string PassWord { get; set; } ? ?
? ? ? ??public int? ClassId { get; set; } ?
? ? ? ? ?public ClassTable Class { get; set; }1701679282 ? ? ?
? ? ? ?public byte[] TimestampV { get; set; }}
?
CodeFrist生成的表如下:
自動幫我們生成的Timestamp類型的一個字段.
配置時間戳屬性的方式也有2種,上面已經說了一種..特性的..
同樣我們也可以使用Fluent API配置屬性為時間戳,代碼如下:
?public DbSet<UserTable> Blogs { get; set; } ? ?
?
?protected override void OnModelCreating(ModelBuilder modelBuilder){modelBuilder.Entity<UserTable>().Property(p => p.TimestampV).ValueGeneratedOnAddOrUpdate().IsConcurrencyToken();} }
3.如何根據需求自定義處理并發沖突
上面,我們已經配置好了需要并發處理的表,也配置好了相關的特性,下面我們就來講講如何使用它.
使用之前,我們先來了解一下,并發過程中所產生的3個值,也是我們需要處理的3個值
? ? ? ?1.當前值是應用程序嘗試寫入數據庫的值。
? ? ? ?2.原始值是在進行任何編輯之前最初從數據庫檢索的值。
? ? ? ?3.數據庫值是當前存儲在數據庫中的值。
當我們配置好上面的并發令牌時,在EF執行SaveChanges()操作并產生并發的時候,我們會得到DbUpdateConcurrencyException的異常信息,(注意:在不配置并發令牌時,這個異常一般不會觸發)
前面,我們已經講過樂觀并發策略是一種性能較高,也比較實用的處理方式,所以我們就通過時間戳來處理這個并發的問題.
示例測試代碼如下:
public void Test(){ ? ? ? ? ? ?//重新創建數據庫,并新增一條數據using (var context = new School_TestContext()){context.Database.EnsureDeleted();context.Database.EnsureCreated();context.UserTable.Add(new UserTable { UserName = "John", PassWord = "Doe" });context.SaveChanges();} ? ? ? ? ? ?using (var context = new School_TestContext()){ ? ? ? ? ? ? ? ?// 修改id為1的用戶名稱var person = context.UserTable.Single(p => p.Id == 1);person.UserName = "555-555-5555"; ? ? ? ? ? ? ? ?// 直接通過訪問數據庫來修改同一條數據 (這里是為了模擬并發)context.Database.ExecuteSqlCommand("UPDATE dbo.UserTable SET UserName = 'Jane' WHERE ID = 1"); ? ? ? ? ?? ? ? ? ? ? ? ?try{ ? ? ? ? ? ? ? ? ? ?//嘗試保存修改int a = context.SaveChanges();} ? ? ? ? ? ? ? ?//獲取并發異常catch (DbUpdateConcurrencyException ex){ ? ? ? ? ? ? ? ? ? ?foreach (var entry in ex.Entries){ ? ? ? ? ? ? ? ?
?? ? ? ?if (entry.Entity is UserTable){ ? ? ? ? ? ? ? ? ? ?
?? ? ? ?var databaseEntity = context.UserTable.AsNoTracking().Single(p => p.Id == ((UserTable)entry.Entity).Id); ? ? ? ? ? ? ? ? ? ? ? ? ? ?var databaseEntry = context.Entry(databaseEntity); ? ? ? ? ? ? ? ? ? ? ? ? ? ?//當前上下文時間戳var date = ConvertToTimeSpanString(entry.Property("TimestampV").CurrentValue); ? ? ? ? ? ? ? ? ? ? ? ? ? ?var dateint = Int32.Parse(date, System.Globalization.NumberStyles.HexNumber); ? ? ? ? ? ? ? ? ? ? ? ? ? ?//數據庫時間戳var datebase = ConvertToTimeSpanString(databaseEntry.Property("TimestampV").CurrentValue); ? ? ? ? ? ? ? ? ? ? ? ? ? ?var dateint2 = Int32.Parse(datebase, System.Globalization.NumberStyles.HexNumber); ? ? ? ? ? ? ? ? ? ? ? ? ? ?//如果當前上下文時間戳與數據庫相同,或者更加新,則使用當前
if (dateint >= dateint2){ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?foreach (var property in entry.Metadata.GetProperties()){ ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//當前值
var proposedValue = entry.Property(property.Name).CurrentValue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//原始值
var originalValue = entry.Property(property.Name).OriginalValue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?//數據庫值
var databaseValue = databaseEntry.Property(property.Name).CurrentValue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?//更新當前值entry.Property(property.Name).CurrentValue = proposedValue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
??//更新原始值來保證修改成功entry.Property(property.Name).OriginalValue = databaseEntry.Property(property.Name).CurrentValue; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?// 嘗試重新保存數據int aa = context.SaveChanges();}}} ? ? ? ? ? ? ? ? ?
? ? ? ? ? ? ? ? ?? ? ?else{ ? ? ? ? ? ? ? ? ? ? ? ? ? ?throw new NotSupportedException("無法處理并發," + entry.Metadata.Name);}}}}}
執行這段代碼,會發現,符合我們樂觀并發策略的要求.
值為最后修改的UserName,為Jane,如圖:
解釋一下,為何最終結果為Jane.
首先,我們添加了一條UserName為John的數據,我們在上下文中修改它為"555-555-5555",
這時候,產生并發,另一個上下文在這個SaveChang之前,就執行完成了,把值修改為了Jane,所以EF通過并發令牌發現匹配失敗.則會觸發異常.
在異常中,我們將當前上下文的版本號和數據庫現有的版本號進行對比,發現當前上下文的版本號為過期數據,則不更新,并返回失敗.
請仔細看代碼中的注釋.
注意:這里的例子是根據樂觀并發處理策略要進行處理的.你可以根據你的業務,來任意處理當前值,原始值和數據庫值,選擇你需要的值保存.?
寫在最后
.net core已經2.0版本了,Asp.net Core也2.0了..EFcore也2.0了..功能已經越來越強大,越來越完善.完全可以投入生產了.園子里對這些新技術也很關注,真的...我感覺很棒..從未如此的棒!!!!
原文地址:http://www.cnblogs.com/GuZhenYin/p/7761352.html
.NET社區新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的浅析Entity Framework Core中的并发处理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 本土开源、立足全球 | COSCon'1
- 下一篇: Julia女神告诉我任何一家企业本质上都