DbUpdateException - ошибка дублирующего ключа не разобрана, пока я перехватываю все исключения - PullRequest
0 голосов
/ 24 февраля 2019

У меня есть следующие методы

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: дубликат записи «XXXXX» для ключа «PRIMARY» ---> MySql.Data.MySqlClient.MySqlException: дубликат записи «XXXXX» для ключа «PRIMARY»

К сожалению, это очень трудная ошибка для воспроизведения, и наша проблема заключается в том, что она не работает, а пытается продолжить работу.

Мы не можем изменить таблицу для поддержки автоинкрементного Первичного ключа.

Редактировать: полная трассировка стека по запросу

-Error- Не удалось выполнить команду DbCommand (8ms) [Parameters = [@ p0 = '?'(DbType = Int64), @ p1 = '?'(DbType = Boolean), ....., @ pN = '?'(DbType = Decimal)], CommandType = 'Text', CommandTimeout = '600'] INSERT INTO table (id, col1, .... colN) VALUES (@ p0, @ p1,.... @pN);-Ошибка- Исключение произошло в базе данных при сохранении изменений для типа контекста «Сущности».Microsoft.EntityFrameworkCore.DbUpdateException: при обновлении записей произошла ошибка.Смотрите внутреннее исключение для деталей.---> MySql.Data..TryAsyncContinuation (Задача 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) --- Конец трассировки стека из предыдущего местоположения, в которое было сгенерировано исключение --- в MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync (IOBehavior ioBehavior) в C: ........ \ mysqlconnector \ src \ MySqlConnector \ Core \ ResultSet.cs: строка 42 --- Конец трассировки стека внутренних исключений --- в MySql.Data.MySqlClient.MySqlDataReader.ActivateResultSet (ResultSet resultSet) в C: ........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs: строка 80 в MySql.Data.MySqlClient.CreateAsync (команда MySqlCommand, поведение CommandBehavior, ResultSetProtocol resultSetProtocol, IOBehavior ioBehavior) в C: ........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlCort.Connector.Connector.Reader.Reader.inSignSource.Reader.id.ExecuteReaderAsync (String commandText, MySqlParameterCollection parameterCollection, поведение CommandBehavior, IOBehavior ioBehavior, CancellationToken cancellationToken) в C: ........ \ mysqlconnector \ src \ MySqlConnector \ Core \ TextCore.IgnE.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 cancellationToken) в Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState, TResult] (Func 4 operation, Func 4 verifySucceeded, состояние TState, CancellationToken cancellationToken) в Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.) в Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync (IReadOnlyList`1 recordsToSave, CancellationToken cancellationToken)в Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync (Boolean acceptAllChangesOnSuccess, CancellationToken cancellationToken) в Microsoft.EntityFrameworkCore.DbContext.SaveChanges * * 10

Ответы [ 2 ]

0 голосов
/ 24 февраля 2019

Полагаю, что эту ошибку трудно воспроизвести, так это то, что ваш код работает асинхронно.Вы получаете новый максимальный идентификатор, запрашивая текущий максимальный идентификатор в текстовом тексте данных, который, в свою очередь, может быть изменен сразу после того, как вы его запросили, поскольку другой поток может также создать новую сущность.

Что выдолжен сделать, это поместить оператор блокировки вокруг логики в Bar() -метод.Таким образом, ваша вторая вставка обрабатывается после того, как ваша первая закончена, и, таким образом, все элементы имеют одинаковый максимальный идентификатор.

Что-то в соответствии с приведенными ниже линиями должно помочь, я не проверял это в 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;
    }
}
0 голосов
/ 24 февраля 2019

Насколько я вижу, вы не удаляете добавленный объект из DbContext, поэтому дубликат ключа все еще там.

Вы должны

  1. Либо удалить (отсоединить) это
  2. Или каждый раз создавать новый DbContext с нуля, просто чтобы быть уверенным.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...