Sto cercando di implementare un blocco ottimistico quando si inseriscono righe in una tabella di database.
Quello che sto cercando di fare è acquisire una riga dal database. Se una riga con una determinata chiave non esiste, viene inserita per prima. Attualmente sto testando questo per scenari di concorrenza elevati.
L'idea è: se la riga è già stata aggiunta da un altro thread, l'eccezione viene catturata e l'entità viene riletta dal database. Tuttavia, non riesco a percepire l'eccezione.
Ecco il metodo in questione
private async Task<Counter> AcquireCounterAsync(string resType)
{
var repo = _dbContext.Set<Counter>();
while (true)
{
var ctr = await repo.FindAsync(resType);
if (ctr != null)
return ctr;
ctr = new Counter(resType);
try
{
Console.WriteLine("Trying to add " + resType);
repo.Add(ctr);
await _dbContext.SaveChangesAsync();
Console.WriteLine("Created " + resType);
return ctr;
}
catch (SqlException ex)
{
// this block is never entered
Console.WriteLine("SqlException was thrown: " + ex.Message);
continue;
}
}
}
Ecco il mio log, l'eccezione è in tedesco ma è solo la violazione prevista del vincolo di chiave primaria:
Trying to add test/test0
Trying to add test/test0
Created test/test0
fail: Microsoft.EntityFrameworkCore.Database.Command[20102]
Failed executing DbCommand (157ms) [Parameters=[@p0='?' (Size = 128), @p1='?' (DbType = Int32)], CommandType='Text', CommandTimeout='30']
SET NOCOUNT ON;
INSERT INTO [counters] ([ResourceType], [Value])
VALUES (@p0, @p1);
System.Data.SqlClient.SqlException (0x80131904): Verletzung der PRIMARY KEY-Einschränkung 'PK_counters'. Ein doppelter Schlüssel kann in das dbo.counters-Objekt nicht eingefügt werden. Der doppelte Schlüsselwert ist (test/test0).
Die Anweisung wurde beendet.
at System.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__122_0(Task`1 result)
at System.Threading.Tasks.ContinuationResultTaskFromResultTask`2.InnerInvoke()
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location where exception was thrown ---
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
--- End of stack trace from previous location where exception was thrown ---
at Microsoft.EntityFrameworkCore.Storage.Internal.RelationalCommand.ExecuteAsync(IRelationalConnection connection, DbCommandMethod executeMethod, IReadOnlyDictionary`2 parameterValues, CancellationToken cancellationToken)
ClientConnectionId:d1f14d55-c95b-4c19-b1bc-468241df225d
Error Number:2627,State:1,Class:14
La classe in questione viene iniettata transitoriamente in un hub signalr. Sento che potrei fare qualcosa di fondamentalmente sbagliato, ma non riesco ancora a vederlo.
EDIT: ho provato a rendere questo metodo anche sincrono, lo stesso risultato, solo una pila-traccia leggermente più lunga che scende alla chiamata all'hub SignalR. Quindi non è sicuramente catturato.
MY FIX: Per chiarire: ho risolto questo problema rilevando "DbUpdateException" e rendendo tutti i metodi sincroni. Non riesco ancora a rilevare "DbUpdateException" o persino "Exception" se utilizzo i metodi asincroni. Quindi questo è solo parzialmente correlato a questa domanda: Impossibile intercettare SqlException in Entity Framework
Ho creato una piccola demo come questa
var uid = Guid.NewGuid();
using (var context = new DemoContext())
{
context.Stuff.Add(new Stuff { Uid = uid, Data = "stuff1" });
await context.SaveChangesAsync();
}
using (var context = new DemoContext())
{
context.Stuff.Add(new Stuff { Uid = uid, Data = "stuff2" });
await context.SaveChangesAsync();
}
Questo è racchiuso in un try ... catch
block e posso catturare l'eccezione generata, ma è di tipo Microsoft.EntityFrameworkCore.DbUpdateException
con System.Data.SqlClient.SqlException
come se fosse la tua dentro.
Ti capita da qualche altra parte e riponi solo l'eccezione interiore? Prova ad individuare Exception
nel codice, solo per essere sicuro.