Stiamo usando ExecutionStrategy e abbiamo questo metodo di supporto nel nostro contesto db:
public Task<T> ExecuteWithinTransactionAsync<T>(Func<IDbContextTransaction, Task<T>> operation, string operationInfo)
{
int counter = 0;
return Database.CreateExecutionStrategy().ExecuteAsync(RunOperationWithinTransaction);
async Task<T> RunOperationWithinTransaction()
{
counter++;
if (counter > 1)
{
Logger.Log(LogLevel.Warn, $"Executing ({counter}. time) transaction for {operationInfo}.");
ClearChangeTracker();
}
using (var transaction = await Database.BeginTransactionAsync(IsolationLevel.Serializable))
{
return await operation.Invoke(transaction);
}
}
}
Noi di utilizzare ExecuteWithinTransactionAsync
quando si chiama la logica di business complessa / fragile che deve essere eseguita in modo affidabile in una transazione serializzabile. Stiamo utilizzando Postgres in modo che possa accadere che la nostra transazione venga interrotta a causa di problemi di serializzazione. La strategia di esecuzione lo rileva e riprova l'operazione. Funziona bene Ma EF conserva ancora la vecchia cache dall'esecuzione precedente. Ecco perché abbiamo introdotto ClearChangeTracker
che assomiglia a questo:
private void ClearChangeTracker()
{
ChangeTracker.DetectChanges();
foreach (var entity in ChangeTracker.Entries().ToList())
{
entity.State = EntityState.Detached;
}
}
E questo sembrava aver funzionato correttamente, finché non abbiamo trovato un caso in cui non funzionava più. Quando aggiungiamo nuove entità a un elenco di proprietà di navigazione, queste entità non verranno rimosse al prossimo tentativo. Per esempio
var parent = context.Parents.FirstOrDefault(p => p.Id == 1);
if (parent.Children.Any())
{
throw new Exception("Parent already has a child"); // This exception is thrown on the second try
}
parent.Children.Add(new Child());
context.SaveChangesAsync();
Quindi, se l'ultima riga context.SaveChangesAsync()
fallisce e l'intera operazione viene rieseguita, parent.Children
contiene già il nuovo figlio aggiunto in parent.Children.Add(new Child());
e non ho trovato alcun modo per rimuovere quell'elemento da EF.
Tuttavia, se rimuoviamo il controllo ( if (parent.Children.Any())
), se l'elemento esiste già o meno, e prova ad aggiungerlo una seconda volta, viene memorizzato solo una volta nel DB.
Stavo cercando di capire come cancellare correttamente DbContext, ma la maggior parte delle volte, la risposta era solo per creare un nuovo DbContext. Tuttavia, non è un'opzione, poiché DbContext è necessario per ExecutionStrategy. Ecco perché volevo sapere, qual è il modo consigliato di usare ExecutionStrategy e avere un DbContext pulito su ogni nuovo tentativo.
In ef-core 2.0.0, è stata introdotta questa nuova funzionalità di pooling DbContext
. Affinché funzioni correttamente, DbContext
istanze di DbContext
sono ora in grado di ripristinare il loro stato interno, in modo che possano essere distribuite come "nuove". Il metodo di reset può essere chiamato così (all'interno del tuo DbContext
):
((IDbContextPoolable)this.ResetState();
Quindi, se è possibile effettuare l'aggiornamento a ef-core 2.0.0, provaci. Non solo per beneficiare di questa nuova funzionalità, è più maturo in molti modi.
Dichiarazione di non responsabilità: questo metodo è destinato all'uso interno, pertanto l'API potrebbe cambiare in futuro.