• EFCore 并发冲突


    一、前言

    首先我们来了解一下什么是并发冲突。

    所谓的并发冲突就是,多个线程同时执行一个操作,例如同时修改数据表,导致数据变更后无法正常保存。

    并发分为:悲观并发和乐观并发

    悲观并发:两个线程同时修改数据库的同一张表,A进入修改,B就不能修改,只能等待A改完,B才能进入修改。

    乐观并发:A修改,B也可以修改,如果在A保存之后B再保存他的修改,此时系统检测到数据库中记录与B刚进入时不一致,B保存时会抛出异常,修改失败。

    乐观并发的基本出发点是:当保存数据的时候抱着一种乐观的态度,不期望发生并发冲突,即使万一发生并发冲突,也能捕捉到冲突异常,然后根据策略解决冲突。

    EF使用的就是乐观并发,所以在做数据处理时我们特别注意。

    接下来以我自己遇到的问题为例。

    二、场景

    在我的项目里有一个功能,第一步先删除数据,再添加数据,然后模拟两次使用这个功能,很不巧的是模拟的两次存在操作相同的数据,其中一次能够操作成功,但是另一次由于前面有写数据被删了,导致我第二次再想删就出现并发冲突了。

    调用的时候出现这个错:Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.

    官网给出的解决方案是通过捕获 DbUpdateConcurrencyException 异常来解决,下面是官网给出的例子

    官网:https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency

    using (var context = new PersonContext())
    {
        // Fetch a person from database and change phone number
        var person = context.People.Single(p => p.PersonId == 1);
        person.PhoneNumber = "555-555-5555";
    
        // Change the person's name in the database to simulate a concurrency conflict
        context.Database.ExecuteSqlRaw(
            "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
    
        var saved = false;
        while (!saved)
        {
            try
            {
                // Attempt to save changes to the database
                context.SaveChanges();
                saved = true;
            }
            catch (DbUpdateConcurrencyException ex)
            {
                foreach (var entry in ex.Entries)
                {
                    if (entry.Entity is Person)
                    {
                        var proposedValues = entry.CurrentValues;  //当前值
                        var databaseValues = entry.GetDatabaseValues();  //数据库中的值
    
                        foreach (var property in proposedValues.Properties)
                        {
                            var proposedValue = proposedValues[property];
                            var databaseValue = databaseValues[property];
    
                            // TODO: decide which value should be written to database
                            // proposedValues[property] = <value to be saved>;
                        }
    
                        // Refresh original values to bypass next concurrency check
                        entry.OriginalValues.SetValues(databaseValues);  //将数据库中的值更新到内存原始数据中,这里可能会有疑问,为什么不是把当前值跟你进去,这是因为OriginalValues存的是原始值,也就是所有改动之前的数据
                    }
                    else
                    {
                        throw new NotSupportedException(
                            "Don't know how to handle concurrency conflicts for "
                            + entry.Metadata.Name);
                    }
                }
            }
        }
    }

    要根据自己的实际情况去改造,因为我的操作中包含删除数据,所以GetDatabaseValues会出现为空的情况,所以我采用的是另一种方式

     bool saved = false;
     while (!saved)
     {
       try
       {
           context.SaveChanges();
           saved = true;
       }
       catch(DbUpdateConcurrencyException ex)
       {
           var s = ex.Entries.Single();
           s.Reload();  //刷新数据,直到更新成功
       }
     }
    

     

    上段代码中的Reload方法就是去数据库查询一次实体的最新值更新到缓存中,在数据库中的操作可以看出来

  • 相关阅读:
    C语言学习笔记-静态库、动态库的制作和使用
    各种消息队列的对比
    如何使用Jupyter notebook
    Ubuntu16.04配置OpenCV环境
    Docker容器发展历史
    Ubuntu OpenSSH Server
    SpringBoot 系列文章
    SpringBoot 模板 Thymeleaf 的使用
    18、spring注解学习(AOP)——AOP功能测试
    17、spring注解学习(自动装配)——@Profile根据当前环境,动态的激活和切换一系列组件的功能
  • 原文地址:https://www.cnblogs.com/wcrBlog/p/12050336.html
Copyright © 2020-2023  润新知