文章内容

2017/2/5 16:11:45,作 者: 黄兵

如何处理Entity Framework中的DbUpdateConcurrencyException异常

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(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. 
        }
分享到:

发表评论

评论列表