DbUpdateException-我在捕获所有异常时无法处理重复的密钥错误

时间:2019-02-24 12:08:32

标签: c# mysql entity-framework-core

我有以下方法

public async Task Foo()
    {
        try
        {

            //Do stuff
            bool inserted = false;
            int tries=0;
            while (!inserted && tries<2)
            {
                try
                {
                    inserted = await Bar();                        
                }
                catch (Exception ex)
                {
                    //log ex and continue
                }
                finally
                {
                  if(!inserted)
                  {
                     tries++;
                  }
                }
            }
        }
        catch (Exception ex)
        {
            //log ex and continue
        }
    }

public async Task<bool> Bar()
    {
        //setup opbject to be inserted to database

        try
        {
            //the table can not have auto incrememnt so we read the max value
            objectToBeAdded.id = Context.Set<object>().Max(o => o.id) + 1;
            await Context.Set<object>().AddAsync(objectToBeAdded);
            await Context.SaveChangesAsync();
            return true;
        }
        catch (Exception ex) {
            return false;
        }
    }

该代码在多线程环境中运行,并且每分钟运行很多次,因此总是有机会发生以下异常。

  

Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。有关详细信息,请参见内部异常。 ---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'重复---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'的重复

不幸的是,这是一个很难重现的错误,我们的问题是它使应用程序崩溃而不是重试并继续前进。

我们无法更改表格以支持自动递增主键。

编辑:按要求的完整堆栈跟踪

  

-错误-无法执行DbCommand(8ms)[Parameters = [@ p0 ='?' (DbType = Int64),@ p1 ='?' (DbType =布尔),.....,@ pN ='?' (DbType =十进制)],CommandType ='文本',CommandTimeout ='600']插入tableidcol1,.... colN)值(@ p0,@ p1,.... @pN);   -错误-保存上下文类型“实体”的更改时,数据库中发生异常。 Microsoft.EntityFrameworkCore.DbUpdateException:更新条目时发生错误。有关详细信息,请参见内部异常。 ---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXX'重复---> MySql.Data.MySqlClient.MySqlException:键'PRIMARY'的条目'XXXXXX'的重复   在MySqlConnector.Core.ServerSession.TryAsyncContinuation(Task 1 task) in C:\.......\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1248 at System.Threading.Tasks.ContinuationResultTaskFromResultTask 2.InnerInvoke()   在System.Threading.ExecutionContext.RunInternal(ExecutionContext executeContext,ContextCallback回调,对象状态)   ---从之前引发异常的位置开始的堆栈结束跟踪---   在System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task&currentTaskSlot)   ---从之前引发异常的位置开始的堆栈结束跟踪---   在C:........ \ mysqlconnector \ src \ MySqlConnector \ Core \ ResultSet.cs:line 42中的MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync(IOBehavior ioBehavior)   ---内部异常堆栈跟踪的结尾---   在C:........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs:line 80处的MySql.Data.MySqlClient.MySqlDataReader.ActivateResultSet(ResultSet resultSet)   在C中的MySql.Data.MySqlClient.MySqlDataReader.ReadFirstResultSetAsync(IOBehavior ioBehavior)中:...   在C:.............. :第287行   在C:..............   在Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection连接,DbCommandMethod executeMethod,IReadOnlyDictionary 2 parameterValues, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) --- End of inner exception stack trace --- at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.ExecuteAsync(IRelationalConnection connection, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.ExecuteAsync(DbContext _, ValueTuple 2个参数,CancellationToken cancelledToken)处   在Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState,TResult](Func 4 operation, Func 4验证成功,TState状态,CancellationToken cancelledToken)处   在Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState,TResult](Func 4 operation, Func 4验证成功,TState状态,CancellationToken cancelledToken)处   在Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(IReadOnlyList`1 entryToSave,CancellationToken cancelledToken)   在Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync(Boolean acceptAllChangesOnSuccess,CancellationToken cancelToken)   在Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync(Boolean acceptAllChangesOnSuccess,CancellationToken cancelToken)

2 个答案:

答案 0 :(得分:0)

据我所知,您没有从DbContext中删除添加的对象,因此重复键仍然存在。

您应该

  1. remove(detach)要么
  2. 或者每次都要从头开始创建新的DbContext

答案 1 :(得分:0)

我猜想,导致此错误难以重现的原因是您的代码异步运行。 您可以通过查询数据上下文中的当前最大ID来获取新的最大ID,由于查询的另一个线程也可能创建新实体,因此可以在查询后立即更改它。

您应该做的是在Bar()方法中的逻辑周围添加一个锁语句。这样,您的第二个插入内容将在您的第一个插入完成后进行处理,因此所有项目共享相同的最大ID。

下面的内容应该会有所帮助,如果它确实可以编译,我没有在Visual Studio中对其进行检查,但是您明白了。

private object _lockObject = new object();
public async Task<bool> Bar()
{
   //setup object to be inserted to database

    try
    {
        // lock your changes, so they run in a safe order
        lock (_lockObject)
        {
            //the table can not have auto incrememnt so we read the max value
            objectToBeAdded.id = Context.Set<object>().Max(o => o.id) + 1;
            await Context.Set<object>().AddAsync(objectToBeAdded);
            await Context.SaveChangesAsync();
        }
        return true;
    }
    catch (Exception ex) {
        return false;
    }
}