EF6學習筆記二十八:併發衝突(二)
要專業系統地學習EF推薦《你必須掌握的Entity Framework 6.x與Core 2.0》。這本書作者(汪鵬,Jeffcky)的部落格:https://www.cnblogs.com/CreateMyself/
繼續來弄EF中的併發,雖然上一篇也弄了,但是總覺得不得要領,這次繼續書中的學習,回顧上次的學習,可能還是會理解的不太準確。
併發分為樂觀併發和悲觀併發,樂觀就不用管了,客戶端隨便去修改。所以我們一直弄的是悲觀併發。
這次所學的東西主要是針對悲觀併發我們所採取的有哪些策略。
客戶端獲勝
資料庫獲勝
客戶端資料庫合併獲勝
首先我有這樣一個疑問,這幾個概念是程式設計師給的,還是官方就有這個說法,乍一聽感覺“獲勝”帶有很強烈的感情色彩啊,計算機術語一般給人的感覺比較高冷吧。
幸好我在看他人部落格的時候發現了EF中有一個列舉,呵呵,還真是
在ObjectContext定義了一個方法Refresh,就是要求傳遞上面列舉作為引數。之前我們瞭解到早起的EF提供的是ObjectContext供程式設計師們去派生,現在是改良後的DbContext,他們之間可以互相轉換,來眼熟下
//這裡寫法可能毫無意義,純粹只是溫習下DbContext轉換ObjectContext,認識下這個列舉 EFDbContext ctx = new EFDbContext(); var context = ((IObjectContextAdapter)ctx).ObjectContext; var stu1 = context.CreateQuery<Student3>("select * from tb_Students3"); context.Refresh(RefreshMode.ClientWins, stu1);
關於ObjectContext和這個列舉就到這裡了,後面我沒有去弄到,那到底有沒有必要用到,我覺得沒有,並且作者在書中也是如此
繼續說回上面三個概念,其實理解起來非常簡單。客戶端獲勝就是隨便客戶端請求更新資料;資料庫獲勝,那就是資料庫說了算的,比如資料庫中某張表某個欄位設定了閾值;客戶端資料庫合併獲勝,那就是商量著來,下面的內容主要說這個。
回顧一下上次的內容,要想捕獲併發衝突需要做配置,兩種方式:併發Token、行版本(RowVersion)
其實這裡就有兩個問題
1、為什麼要額外配置才能捕獲到併發異常,我用1除以0,就會報異常: System.DivideByZeroException: 嘗試除以零。 這個為什麼就不需要配置才能捕獲呢?
2、併發Token和RowVersion有什麼區別?
第一個問題,如果我們不為屬性或者實體配置併發的話,那麼其實就是屬於樂觀併發,資料庫中保留最後一次更新的內容,這個完全沒有問題,對吧?所以如果你不需要做併發處理的話,那麼對你來說併發就不算異常。
我想想看我上一家公司有沒有做併發異常的處理,應該是沒有。最主要的是使用者少,而且還有許可權,各個功能模組都由不同的角色去操作,這就又降低了併發的可能。
第二個問題,其實我是沒太弄清楚。併發Token是為某個屬性配置,而RowVersion是在實體中新新增的一個屬性,那是不是rowVersion是作用這個實體的所有屬性呢?
前面用到EF提供的DbUpdateConcurrencyException類來處理併發衝突。那客戶端獲勝和資料庫獲勝我就不說了,下面來說客戶端資料庫合併獲勝。
怎麼合併,來看看作者怎麼說
1、如果原始值與資料庫中的值不同,意味著資料庫中的值已被其他併發客戶端更新,就放棄更新此屬性,並保留資料庫中的值。
2、如果原始值與資料庫中的值相同,意味著此屬性不會產生併發衝突,就會正常處理
我知道這兩句話肯定是重點,但是我理解不了,而且我原封不動地按照作者的程式碼寫並沒有得到想要的結果,所以我就不去理會了,來說說我自己的理解
這裡有一個student4類,我為Name屬性配置了併發Token
public class Student4:BaseEntity { public string Name { get; set; } public int Score { get; set; } }
還有一個Score屬性沒有配置併發Token,資料庫中student原始值為{name:"張三",score=100},併發進來修改的內容分別為{name:"李四",score=99}、{name:"王五",score=88},最後我想要的結果是資料庫中修改為{name:"李四",score=88}
如果有兩個請求進來同時修改同一個Student,我想要的結果就是Name屬性只會第一次更新成功,Score因為沒有做併發,兩次更新都可以。
那麼思路是不是就是,找到實體中設定了併發的屬性,將它的IsModified = false,我是這樣想的,我覺得就應該就這樣啊,併發的不更新,非併發的更新,有毛病嗎?
但是如何知道這個屬性有沒有設定併發,這真的很讓人抓狂。因為我沒找到,按理說EF應該提供了某個方法的。
你說我們用Fluent API對實體或者屬性做的那些配置,那到底怎麼去獲取這些配置呢?
我只能想到DataAnnotations方式配置,因為是通過特性的方式來的,那麼首先就要知道如何通過DataAnnotations的方式來配置併發屬性,還好我找到了
public class Student4:BaseEntity { [ConcurrencyCheck] public string Name { get; set; } public int Score { get; set; } }
那麼最後我是這樣寫的
using (EFDbContext ctx1 = new EFDbContext()) using (EFDbContext ctx2 = new EFDbContext()) { var stu1 = ctx1.Students4.FirstOrDefault(); var stu2 = ctx2.Students4.FirstOrDefault(); stu1.Name = "李四"; stu1.Score = 99; stu2.Name = "王五"; stu2.Score = 88; ctx1.SaveChanges(); try { ctx2.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { var tracking = ex.Entries.Single(); var originalValues = tracking.OriginalValues; var databaseValues = tracking.GetDatabaseValues(); var currentValues = tracking.CurrentValues; Console.WriteLine($"original-name:{originalValues.GetValue<string>("Name")},original-score:{originalValues.GetValue<int>("Score")}");//original-name:張三,original-score:100 Console.WriteLine($"database-name:{databaseValues.GetValue<string>("Name")},database-score:{databaseValues.GetValue<int>("Score")}");//database-name:李四,database-score:99 Console.WriteLine($"current-name:{currentValues.GetValue<string>("Name")},current-score:{currentValues.GetValue<int>("Score")}");//current-name:王五,current-score:88 originalValues.SetValues(databaseValues); var tracking2 = ex.Entries.Single(); var originalValues2 = tracking2.OriginalValues; var databaseValues2 = tracking2.GetDatabaseValues(); var currentValues2 = tracking2.CurrentValues; Console.WriteLine($"original2-name:{originalValues2.GetValue<string>("Name")},original2-score:{originalValues2.GetValue<int>("Score")}");//original2-name:李四,original2-score:99 Console.WriteLine($"database2-name:{databaseValues2.GetValue<string>("Name")},database2-score:{databaseValues2.GetValue<int>("Score")}");//database2-name:李四,database2-score:99 Console.WriteLine($"current2-name:{currentValues2.GetValue<string>("Name")},current2-score:{currentValues2.GetValue<int>("Score")}");//current2-name:王五,current2-score:88 var concurrencyProp = ((Student4)databaseValues.ToObject()).GetType().GetProperties().Where(x => Attribute.IsDefined(x, typeof(ConcurrencyCheckAttribute))).Single(); Console.WriteLine(concurrencyProp.Name); tracking.Property(concurrencyProp.Name).IsModified = false; ctx2.SaveChanges(); } }
最後雖然得到了想要的結果但還是感覺不行 ,後面還有一節高階版的解析,利用Polly庫來實現重試策略。後面接著學。