EntityFramework进阶——数据变更冲突
TimeStamp
更新操作可能伴隨數據沖突,我們可以通過并發(fā)處理妥善解決這一方面的問題。避免數據沖突比較方便的做法是自動加入字節(jié)數組(byte[])類型的TimeStamp屬性,對應到數據表中的rowvewsion類型字段,自動監(jiān)控數據的更新操作。
下面通過一個例子來說明:
新建一個項目,名稱為TimeStampDemo,新增實體類如下圖所示:
public class Product{public int Id { get; set; }public string Name { get; set; }public int Price { get; set; }public int Quantity { get; set; }1741660715public byte[] Timestamp { get; set; }}上下文代碼如下圖所示:
public class TimeStampModel : DbContext{public TimeStampModel(): base("name=TimeStampModel"){}public virtual DbSet<Product> Product { get; set; }}首次運行項目,在數據庫中生成了如下表結構:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
下面編寫程序來查看其中的Timestamp字段值:
static void Main(string[] args) {using (TimeStampModel model = new TimeStampModel()){Product product = model.Product.Where(p => p.Id == 1).First();long ts = BitConverter.ToInt64(product.Timestamp,0);Console.WriteLine("Quantity:{0} \t Timestamp:{1}",product.Quantity,ts);product.Quantity = 150;model.SaveChanges();ts = BitConverter.ToInt64(product.Timestamp,0);Console.WriteLine("Quantity:{0} \t Timestamp:{1}",product.Quantity,ts);Console.ReadKey();} }運行效果下圖所示:
? ? ? ? ? ? ? ? ?
可以看到,每一次更新數據時,EF都會修改Timestamp的值。如果期間有其他程序搶先完成更新造成數據變更,那么EF就會報錯。
我們重新執(zhí)行程序,在首次出現Timestamp語句時,打上斷點。然后在SQL查詢界面直接更新用SQL語句更新數據:
UPDATE dbo.Products SET Quantity = 200 WHERE Id = 1
SQL執(zhí)行之前Timestamp的值:?
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
SQL執(zhí)行之后Timestamp的值:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
可以看到每次更新數據后,Timestamp的數值會發(fā)生改變, 這時繼續(xù)運行程序,就會報錯:
?
此信息描述找不到要更新的數據,這是因為EF在更新每一項數據時,除了主鍵之外,還會對比當時取出的TimeStamp的值。由于我們使用外部SQL更新,EF無法檢測這個字段就出現報錯。
異常DbUpdateConcurrencyException派生自DbUpdateException,表示一個并發(fā)更新沖突,當這個異常出現時,顯示SaveChanges更新失敗,通過DbUpdateException.Entries屬性可以獲取其返回的IEnumerable<DbEntityEntry>對象,其中存取未成功更新的實體數據對象。?
重新調整Main方法中的代碼如下圖所示:
static void Main(string[] args) {using (TimeStampModel model = new TimeStampModel()){Product product = model.Product.Where(p => p.Id == 1).First();long ts = BitConverter.ToInt64(product.Timestamp, 0);Console.WriteLine("Quantity:{0} \t TimeStamp:{1}", product.Quantity, ts);try{Console.WriteLine("新的 Quantity 值:");int quantity = 500;product.Quantity = quantity;model.SaveChanges();ts = BitConverter.ToInt64(product.Timestamp, 0);Console.WriteLine("Quantity:{0} \t TimeStamp:{1}", product.Quantity, ts);}catch (DbUpdateConcurrencyException ex){DbEntityEntry entry = ex.Entries.Single();DbPropertyValues current = entry.CurrentValues;//當前實體數據int quantity = current.GetValue<int>("Quantity");long timestamp = BitConverter.ToInt64(current.GetValue<byte[]>("Timestamp"),0);DbPropertyValues dbvalue = entry.GetDatabaseValues();//數據庫中的實體數據int dbquantity = dbvalue.GetValue<int>("Quantity");long dbtimestamp = BitConverter.ToInt64(dbvalue.GetValue<byte[]>("Timestamp"),0);Console.WriteLine("DbUpdateConcurrentException.....");Console.WriteLine("當前實體的屬性值: Quantity:{0} \t TimeStamp:{1}",quantity,timestamp);Console.WriteLine("數據庫中的字段值:Quantity:{0} \t TimeStamp:{1}",dbquantity,dbtimestamp);}} }再次在首次出現Timestamp語句時,打上斷點。然后在SQL查詢界面直接更新用SQL語句更新數據后,繼續(xù)運行程序,如下圖所示:
? ? ? ? ? ? ? ?
?
并發(fā)沖突處理——Database Wins 或者 Client Wins?
處理更新沖突有幾種方式,想以數據庫中的值來覆蓋當前數據對象的值,則可以調用DbEntityEntry類定義的Reload方法,此方法執(zhí)行的是Database Wins(數據庫優(yōu)先)策略,執(zhí)行完畢之后,數據對象當前的值將與數據庫同步。
entry.Relaod(); entry.SaveChanges();另一種方式則是反向以當前的值覆蓋數據庫的值,稱為Clinet? Wins(客戶優(yōu)先)策略。
entry.OriginalValues.SetValues(entry.GetDatabaseValues()); model.SaveChanges()?
添加一個ResolveConcurrency方法來支持并發(fā)沖突處理:
public static void ResolveConcurrency(TimeStampModel model, DbEntityEntry entry) {Console.WriteLine("1.Database wins 2.Client wins:");int i = int.Parse(Console.ReadLine());if (i == 1){entry.Reload();model.SaveChanges();Console.WriteLine("與數據庫同步完成——DataBase Wins");}else{entry.OriginalValues.SetValues(entry.GetDatabaseValues());model.SaveChanges();Console.WriteLine("重新更新數據庫完成——Client Wins");} }將ResolveConcurrency方法插入上述例子的catch語句中,并在并發(fā)沖突中選擇與數據庫同步完成,結果如下圖所示:
? ? ? ? ? ? ? ? ? ? ? ? ??
?
ConcurrencyCheck注解?
TimeStamp注解屬性會導致EF在更新時監(jiān)控整項數據的更新狀態(tài),如果對特定字段進行數據沖突的監(jiān)控,則可以通過ConcurrencyCheck注解來達到目的。
下面通過程序來說明,新建一個控制臺應用程序ConcurrencyCheckDemo,有如下一個實體類:
public class Product{public int Id { get; set; }public string Name { get; set; }public int Price { get; set; }[ConcurrencyCheck]public int SPrice { get; set; }}在特價字段(SPrice)設置【ConcurrencyCheck】,當程序更新任何一項Product數據時,SPrice字段將受到監(jiān)控,期間不受EF監(jiān)控的程序變更了SPrice字段的值時,便會產生沖突。其他字段沒有設置,因此數據的更新不會有沖突。
在Main函數中增加如下圖代碼:
static void Main(string[] args) {using (ConcurrencyCheckModel db = new ConcurrencyCheckModel()){try{Console.WriteLine("指定更新操作:A.商品名稱 B.商品特價");string ab = Console.ReadLine();if (ab == "A"){Console.WriteLine("輸入第一項商品的新名稱:");string name = Console.ReadLine();db.Product.First().Name = name;}if (ab == "B"){Console.WriteLine("輸入第一項商品的新特價:");int price = int.Parse(Console.ReadLine());db.Product.First().SPrice = price;}Console.WriteLine("按任意鍵完成更熱");Console.ReadKey();db.SaveChanges();Console.WriteLine("完成更新");}catch (Exception ex){Console.WriteLine(ex.ToString());Console.WriteLine(ex.Message);}} }當修改商品特價時,去數據庫修改SPrice字段的值,便會報錯:
? ? ? ?
?
?
?
?
?
?
?
總結
以上是生活随笔為你收集整理的EntityFramework进阶——数据变更冲突的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: XShell远程连接LInux服务器(地
- 下一篇: 水星路由器DNS服务器未响应,水星路由器