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

 

posted on 2019-12-16 17:42  扯。  阅读(1434)  评论(0编辑  收藏  举报

导航