zoukankan      html  css  js  c++  java
  • 如何处理Entity Framework / Entity Framework Core中的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异常,避免错误提交。

    顺便说下,如果在实体类的属性上不用[ConcurrencyCheck]标签,在EF Core中上面这种情况是不会抛出异常的。不加[ConcurrencyCheck]标签时,EF Core中发生DbUpdateConcurrencyException异常的条件是,使用DbContext.SaveChanges方法时,生成的Update或Delete语句根据实体的Key属性值在数据库表中找不到对应行。

    不过实测EF Core的实体类属性加上[ConcurrencyCheck]标签后,也会出现上面说的问题。

    2. 如何正确处理DbUpdateConcurrencyException异常


    2.1 放弃更新或放弃删除数据库中已经不存在的数据

    原理就是,发生DbUpdateConcurrencyException异常时将实体的EntityState设置为Detached,放弃更新或放弃删除抛出DbUpdateConcurrencyException异常的实体,只更新或删除不抛出异常的实体:

    using (TestDBContext dbContext = new TestDBContext())
    {
        var jim = dbContext.Person.Where(p => p.Name == "Jim").First();
        var sam = dbContext.Person.Where(p => p.Name == "Sam").First();
        var jack = dbContext.Person.Where(p => p.Name == "Jack").First();
    
        jim.Age = jim.Age + 10;
        sam.Age = sam.Age + 10;
        jack.Age = jack.Age + 10;
    
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = true;
                // 获取抛出DbUpdateConcurrencyException异常的实体
                var entry = ex.Entries.Single();
    
                // 设置实体的EntityState为Detached,放弃更新或放弃删除抛出异常的实体
                entry.State = EntityState.Detached;
    
            }
        } while (saveFailed);
    }

    也可以重写DbContext的SaveChanges方法,加入上面的逻辑来安全使用DbContext.SaveChanges方法,当Entity Framework的实体类属性上不用[ConcurrencyCheck]标签时,这种方式就很适合(也就是说,使用DbContext.SaveChanges方法时,如果生成的Update或Delete语句根据实体的Key属性值在数据库表中找不到对应行,从而抛出了DbUpdateConcurrencyException异常,这种方式就能很好地进行处理,放弃更新或放弃删除抛出异常的实体):

    public override int SaveChanges()
    {
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                return base.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = true;
                // 获取抛出DbUpdateConcurrencyException异常的实体
                var entry = ex.Entries.Single();
    
                // 设置实体的EntityState为Detached,放弃更新或放弃删除抛出异常的实体
                entry.State = EntityState.Detached;
    
            }
        } while (saveFailed);
    
        return 0;
    }

    2.2 数据库优先方式

    原理是在出现异常的时候,重新加载数据库中的数据,覆盖DbContext本地数据

    using (TestDBContext dbContext = new TestDBContext())
    {
        var jim = dbContext.Person.Where(p => p.Name == "Jim").First();
        var sam = dbContext.Person.Where(p => p.Name == "Sam").First();
        var jack = dbContext.Person.Where(p => p.Name == "Jack").First();
    
        jim.Age = jim.Age + 10;
        sam.Age = sam.Age + 10;
        jack.Age = jack.Age + 10;
    
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                dbContext.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.3 客户端优先方式

    以DbContext保存的客户端数据为主,覆盖数据库中的数据

    using (TestDBContext dbContext = new TestDBContext())
    {
        var jim = dbContext.Person.Where(p => p.Name == "Jim").First();
        var sam = dbContext.Person.Where(p => p.Name == "Sam").First();
        var jack = dbContext.Person.Where(p => p.Name == "Jack").First();
    
        jim.Age = jim.Age + 10;
        sam.Age = sam.Age + 10;
        jack.Age = jack.Age + 10;
    
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = true;
                // Update original values from the database
                var entry = ex.Entries.Single();
                entry.OriginalValues.SetValues(entry.GetDatabaseValues());
            }
        } while (saveFailed);
    }

    2.4 根据情况判断做相应的处理
    综合前面几种方式的处理方法,根据抛出DbUpdateConcurrencyException异常实体的EntityState值做相应的处理

    using (TestDBContext dbContext = new TestDBContext())
    {
        var jim = dbContext.Person.Where(p => p.Name == "Jim").First();
        var sam = dbContext.Person.Where(p => p.Name == "Sam").First();
        var jack = dbContext.Person.Where(p => p.Name == "Jack").First();
    
        jim.Age = jim.Age + 10;
        sam.Age = sam.Age + 10;
        jack.Age = jack.Age + 10;
    
        bool saveFailed;
        do
        {
            saveFailed = false;
            try
            {
                dbContext.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)
            {
                saveFailed = true;
                var entry = ex.Entries.Single();
                //The MSDN examples use Single so I think there will be only one
                //but if you prefer - do it for all entries
                //foreach(var entry in ex.Entries)
                //{
                if (entry.State == EntityState.Deleted)
                    //When EF deletes an item its state is set to Detached
                    //http://msdn.microsoft.com/en-us/data/jj592676.aspx
                    entry.State = EntityState.Detached;
                else
                    entry.OriginalValues.SetValues(entry.GetDatabaseValues());
                //throw; //You may prefer not to resolve when updating
                //}
            }
        } while (saveFailed);
    }

    不过这种方式可能会造成死循环。。。还需要改进,原因如下:

    well according to msdn.microsoft.com/en-us/library/… ex.Entries contains all the entities that could not be saved. The only problem i have with the while loop is that it will spin forever if there is an entity that could not be saved for a reason other than entry.State == EntityState.Deleted. 

    来自链接

    参考文献:

    如何处理Entity Framework中的DbUpdateConcurrencyException异常

  • 相关阅读:
    springboot springcloud zuul 过滤器
    springboot springcloud eureka 熔断器
    javaweb servlet filter
    maven nexus 搭建私服(二)
    springboot springcloud zuul 网关入门
    springboot springcloud 配置中心
    springboot springcloud eureka 入门
    java rabbitmq
    java jvm调优
    maven nexus 搭建私服(一)
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9847517.html
Copyright © 2011-2022 走看看