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. }