È molto comune nelle nostre applicazioni Web avere bisogno di dati da una varietà di tabelle nel nostro database. Oggi è possibile trovare 5 o 6 query di database eseguite in serie per una singola richiesta. Nessuna di queste query dipende dai dati dell'altra, quindi sono candidati perfetti per essere eseguiti in parallelo. Il problema è il noto DbConcurrencyException
che viene generato quando più query vengono eseguite sullo stesso contesto.Parallelismo e Entity Framework
Generalmente utilizziamo un singolo contesto per richiesta e quindi abbiamo una classe di repository in modo da poter riutilizzare le query tra vari progetti. Quindi smaltiamo il contesto alla fine della richiesta quando il controller viene smaltito.
Di seguito è riportato un esempio che utilizza il parallelismo, ma c'è ancora un problema!
var fileTask = new Repository().GetFile(id);
var filesTask = new Repository().GetAllFiles();
var productsTask = AllProducts();
var versionsTask = new Repository().GetVersions();
var termsTask = new Repository().GetTerms();
await Task.WhenAll(fileTask, filesTask, productsTask, versionsTask, termsTask);
Ogni repository crea internamente proprio contesto, ma come è ora, essi non vengono smaltiti. Questo é un problema. So che potrei chiamare Dispose
su ogni repository che creo, ma che inizia a ingombrare il codice rapidamente. Potrei creare una funzione wrapper per ogni query che utilizza il proprio contesto, ma che si sente disordinato e non è una soluzione a lungo termine per il problema.
Quale sarebbe il modo migliore per risolvere questo problema? Vorrei che il cliente/consumatore non dovesse preoccuparsi di smaltire ogni repository/contesto nel caso in cui più query fossero eseguite in parallelo.
L'unica idea che ho adesso è quella di seguire un approccio simile a un modello di fabbrica, tranne che la mia fabbrica avrebbe tenuto traccia di tutti gli oggetti creati. Potrei quindi disporre della fabbrica una volta che avrò finito di sapere che le mie domande sono finite e che la fabbrica potrebbe disporre internamente di ogni repository/contesto.
Sono sorpreso di vedere così poco discussione intorno parallelismo e Entity Framework, quindi speriamo che qualche idea in più da parte della comunità verranno in.
Modifica
Ecco un semplice esempio di ciò che il nostro repository appare come:
public class Repository : IDisposable {
public Repository() {
this.context = new Context();
this.context.Configuration.LazyLoadingEnabled = false;
}
public async Task<File> GetFile(int id) {
return await this.context.Files.FirstOrDefaultAsync(f => f.Id == id);
}
private bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (!this.disposed) {
if (disposing) {
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
}
Come si può vedere, ogni repository ha il proprio contesto. Ciò significa che ogni repository deve essere eliminato. Nell'esempio che ho dato sopra, ciò significa che avrei bisogno di 4 chiamate allo Dispose()
.
I miei pensieri per un approccio fabbrica per il problema era simile al seguente:
public class RepositoryFactory : IDisposable {
private List<IRepository> repositories;
public RepositoryFactory() {
this.repositories = new List<IRepository>();
}
public IRepository CreateRepository() {
var repo = new Repository();
this.repositories.Add(repo);
return repo;
}
#region Dispose
private bool disposed = false;
protected virtual void Dispose(bool disposing) {
if (!this.disposed) {
if (disposing) {
foreach (var repo in repositories) {
repo.Dispose();
}
}
}
this.disposed = true;
}
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
}
Questa fabbrica sarebbe responsabile della creazione di istanze di mio repository, ma sarebbe anche tenere traccia di tutte le istanze che ha creato. Una volta che questa singola classe factory è stata eliminata, sarebbe internamente responsabile dello smaltimento di ogni repository creato.
credo un contesto EF non ha bisogno di essere smaltito se non si gestisce la connessione manualmente. Dovrebbe aprire e chiudere per ogni richiesta. Tuttavia, i contesti non disposti mi sembrano un approccio sporco. – usr
@usr Abbiamo codice scritto da qualcun altro in produzione che non elimina tutti i contesti. :) Funziona, ma non sono sicuro su quali saranno o saranno le conseguenze. Dal momento che il contesto implementa 'IDisposable', mi piacerebbe sviluppare un approccio che rimuova il mistero di ciò che potrebbe accadere. –
_ "Quale sarebbe il modo migliore per affrontare questo problema?" _ - Penso che dovrai essere più specifico riguardo alle tue obiezioni ai possibili approcci che hai già identificato. Qualcosa di meglio di "disordinato" e "disordine". Il fatto è che l'incapsulamento è una tecnica comune e valida per nascondere "disordinato" e "ingombrante", e un involucro di qualche tipo è una forma di incapsulamento. Senza ulteriori dettagli, tutto quello che otterrete sono risposte vaghe e supponenti. –