Ho i seguenti metodi
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
}
}
e
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;
}
}
Il codice viene eseguito in un ambiente multi-thread e molte volte al minuto, quindi c'è sempre una possibilità per la seguente eccezione.
Microsoft.EntityFrameworkCore.DbUpdateException: si è verificato un errore durante l'aggiornamento delle voci. Vedi l'eccezione interna per i dettagli. ---> MySql.Data.MySqlClient.MySqlException: voce duplicata 'XXXXX' per la chiave 'PRIMARY' ---> MySql.Data.MySqlClient.MySqlException: voce duplicata 'XXXXX' per la chiave 'PRIMARY'
Sfortunatamente è un errore molto difficile da riprodurre e il nostro problema è che si blocca l'applicazione invece di riprovare e andare avanti.
Non possiamo cambiare la tabella per supportare la chiave primaria di incremento automatico.
Modifica: la traccia dello stack completo come richiesto
-Errore- Esecuzione impossibile DbCommand (8ms) [Parametri = [@ p0 = '?' (DbType = Int64), @ p1 = '?' (DbType = Boolean), ....., @ pN = '?' (DbType = Decimal)], CommandType = 'Text', CommandTimeout = '600'] INSERT INTO
table
(id
,col1
, ....colN
) VALUES (@ p0, @ p1, .... @pN); -Errore- Si è verificata un'eccezione nel database durante il salvataggio delle modifiche per il tipo di contesto "Entità". Microsoft.EntityFrameworkCore.DbUpdateException: si è verificato un errore durante l'aggiornamento delle voci. Vedi l'eccezione interna per i dettagli. ---> MySql.Data.MySqlClient.MySqlException: voce duplicata 'XXXXX' per la chiave 'PRIMARY' ---> MySql.Data.MySqlClient.MySqlException: voce duplicata 'XXXXXX' per la chiave 'PRIMARY' su MySqlConnector.Core.ServerSession .TryAsyncContinuation (attività Task1 task) in C:\.......\mysqlconnector\src\MySqlConnector\Core\ServerSession.cs:line 1248 at System.Threading.Tasks.ContinuationResultTaskFromResultTask
2.InnerInvoke () in System.Threading .ExecutionContext.RunInternal (ExecutionContext executionContext, ContextCallback callback, Object state) --- Fine dello stack trace dalla posizione precedente in cui è stata generata un'eccezione --- su System.Threading.Tasks.Task.ExecuteWithThreadLocal (Task & currentTaskSlot) --- Fine di traccia dello stack dalla posizione precedente in cui è stata generata un'eccezione --- su MySqlConnector.Core.ResultSet.ReadResultSetHeaderAsync (IOBehavior ioBehavior) in C: ........ \ mysqlconnector \ src \ MySqlConnector \ Core \ ResultSet.cs: riga 42 --- Fine della traccia dello stack di eccezioni interne --- su MySql.Data.MySqlClient.MySqlDataReader.Ac tivateResultSet (ResultSet resultSet) in C: ........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs: riga 80 su MySql.Data.MySqlClient.MySqlDataReader.ReadFirstResultSetAsync (IOBehavior ioBehavior) in C: ........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs: riga 302 su MySql.Data.MySqlClient.MySqlDataReader.CreateAsync (comando MySqlCommand, comportamento CommandBehavior, ResultSetProtocol resultSetProtocol, IOBehavior ioBehavior) in C: ........ \ mysqlconnector \ src \ MySqlConnector \ MySql.Data.MySqlClient \ MySqlDataReader.cs: riga 287 su MySqlConnector.Core.TextCommandExecutor.ExecuteReaderAsync (comando StringText, parametro MySqlParameterCollectionCollection, Comportamento Comportamento comportamento, IOBehavior ioBehavior, CancellationToken cancellationToken) in C: ........ \ mysqlconnector \ src \ MySqlConnector \ Core \ TextCommandExecutor.cs: riga 37 su Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync (connessione IRelationalConnection, DbC ommandMethod executeMethod, IReadOnlyDictionary2 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 parameters, CancellationToken cancellationToken) su Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState, TResult] (Func4 operation, Func
4 verifySucceeded, TState state, CancellationToken cancellationToken) su Microsoft.EntityFrameworkCore.Storage.ExecutionStrategy.ExecuteImplementationAsync [TState, TResult] (4 operation, Func
44 operation, Func
4 verifySucceeded, TState state, CancellationToken cancellationToken) at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync (IReadOnlyList`1 entriesToSave, CancellationToken CancellationToken) a Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChangesAsync (booleano acceptAllChangesOnSuccess, CancellationToken CancellationToken) a Microsoft.EntityFrameworkCore.DbContext.SaveChangesAsync (booleano acceptAllChangesOnSuccess , CancellazioneToken annullamentoToken)
Immagino che ciò che rende questo errore difficile da riprodurre sia che i tuoi codici vengano eseguiti in modo asincrono. Stai ottenendo il nuovo ID massimo interrogando l'ID max corrente nel datacontext, che, a sua volta, può essere modificato subito dopo averlo interrogato, poiché un altro thread potrebbe anche creare una nuova entità.
Quello che dovresti fare, è mettere un'asserzione di blocco attorno alla logica nel tuo metodo Bar()
. In questo modo, il tuo secondo inserto viene elaborato dopo che il primo è stato completato e quindi tutti gli articoli condividono lo stesso ID massimo.
Qualcosa che segue le linee qui sotto dovrebbe aiutare, non l'ho controllato in Visual Studio, se è stato compilato in realtà, ma tu hai l'idea.
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;
}
}