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方法就是去数据库查询一次实体的最新值更新到缓存中,在数据库中的操作可以看出来

 引用:https://www.cnblogs.com/wcrBlog/p/12050336.html

posted @   遨游天际  阅读(279)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
· 使用C#创建一个MCP客户端
点击右上角即可分享
微信分享提示