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