1. Concurrency的作用
場景
有個修改用戶的頁面功能,我們有一條數據User, ID是1的這個User的年齡是20, 性別是female(數據庫中的原始數據)
正確的該User的年齡是25, 性別是male
這個時候A發現User的年齡不對, 就給改成25, 那么在Entity Framework中,我們會這樣做。
var user = dbConext.User.Find(1); //B用戶在這里完成修改了User的性別 user.age = 25; dbContext.SaveChanges();
但是加入在上面注釋處,有個B用戶發現性別不對,完成了對用戶性別的修改,改成male. 會出現什么結果呢。
var user = dbConext.User.Find(1);
當A執行這個代碼的時候,獲取的性別是female
user.age = 25;
當A執行這個代碼的時候, 不知道B已經修改了這個記錄的性別,這個時候A的user的性別還是female
dbContext.SaveChanges();
保存修改的時候,會把female覆蓋回去,這樣B用戶的修改就作廢了。
但這不是A的本意,A其實只是想修改年齡而已。
Entity Framework使用[ConcurrencyCheck] 來解決這種問題, 當標記為[ConcurrencyCheck] 的Entity屬性,如果發現在從數據庫中取下來和提交的時候不一致,就會出現DbUpdateConcurrencyException異常,避免錯誤提交。
2. 如何正確處理DbUpdateConcurrencyException異常
2.1 數據庫優先方式
原理是在出現異常的時候,重新加載數據庫中的數據,覆蓋Context本地數據
using (var context = new BloggingContext()) { var blog = context.Blogs.Find(1); blog.Name = "The New ADO.NET Blog"; bool saveFailed; do { saveFailed = false; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { saveFailed = true; // Update the values of the entity that failed to save from the store ex.Entries.Single().Reload(); } } while (saveFailed); }
2.2 客戶端優先方式
以Context保存的客戶端數據為主,覆蓋數據庫中的數據
using (var context = new BloggingContext()) { var blog = context.Blogs.Find(1); blog.Name = "The New ADO.NET Blog"; bool saveFailed; do { saveFailed = false; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { saveFailed = true; // Update original values from the database var entry = ex.Entries.Single(); entry.OriginalValues.SetValues(entry.GetDatabaseValues()); } } while (saveFailed); }
3.3 綜合方式
有時候,不是非A即B的關系,我們希望綜合數據庫中的數據和context中修改的數據,再保存到數據庫中
使用下面的CurrentValues, GetDatabaseValues(), 得到Context數據和數據庫數據,重新構建一個正確的Entity,再更新到數據庫中
using (var context = new BloggingContext()) { var blog = context.Blogs.Find(1); blog.Name = "The New ADO.NET Blog"; bool saveFailed; do { saveFailed = false; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { saveFailed = true; // Get the current entity values and the values in the database var entry = ex.Entries.Single(); var currentValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); // Choose an initial set of resolved values. In this case we // make the default be the values currently in the database. var resolvedValues = databaseValues.Clone(); // Have the user choose what the resolved values should be HaveUserResolveConcurrency(currentValues, databaseValues, resolvedValues); // Update the original values with the database values and // the current values with whatever the user choose. entry.OriginalValues.SetValues(databaseValues); entry.CurrentValues.SetValues(resolvedValues); } } while (saveFailed); }
public void HaveUserResolveConcurrency(DbPropertyValues currentValues, DbPropertyValues databaseValues, DbPropertyValues resolvedValues) { // Show the current, database, and resolved values to the user and have // them edit the resolved values to get the correct resolution. }
對上面方法的優化
使用DbPropertyValues總是別扭,使用Enttiy對象就會方便很多,下面就是轉換成Entity對象操作的方法
using (var context = new BloggingContext()) { var blog = context.Blogs.Find(1); blog.Name = "The New ADO.NET Blog"; bool saveFailed; do { saveFailed = false; try { context.SaveChanges(); } catch (DbUpdateConcurrencyException ex) { saveFailed = true; // Get the current entity values and the values in the database // as instances of the entity type var entry = ex.Entries.Single(); var databaseValues = entry.GetDatabaseValues(); var databaseValuesAsBlog = (Blog)databaseValues.ToObject(); // Choose an initial set of resolved values. In this case we // make the default be the values currently in the database. var resolvedValuesAsBlog = (Blog)databaseValues.ToObject(); // Have the user choose what the resolved values should be HaveUserResolveConcurrency((Blog)entry.Entity, databaseValuesAsBlog, resolvedValuesAsBlog); // Update the original values with the database values and // the current values with whatever the user choose. entry.OriginalValues.SetValues(databaseValues); entry.CurrentValues.SetValues(resolvedValuesAsBlog); } } while (saveFailed); }
public void HaveUserResolveConcurrency(Blog entity, Blog databaseValues, Blog resolvedValues) { // Show the current, database, and resolved values to the user and have // them update the resolved values to get the correct resolution. }