• 如何处理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异常

  • 相关阅读:
    Python 生产者与消费者模型
    Python 进程队列
    Python 进程
    Python Socket网络编程
    Python 异常处理
    C# .net 下载了个.dll的文件,怎么用啊?
    ScriptManager.RegisterStartupScript用法详解
    ScriptManager.RegisterStartupScript与ClientScript.RegisterStartupScript区别
    IDisposable接口
    Guid排序问题
  • 原文地址:https://www.cnblogs.com/OpenCoder/p/9847517.html
Copyright © 2020-2023  润新知