Voglio davvero creare un codice bello, pulito e corretto, quindi ho poche domande di base. All'inizio ho un servizio con GetName
. _dbContext
da altri servizi DI come _cache
public Task<string> GetName(string title)
{
var articleList = await _cache.GetOrCreate("CacheKey", entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(60 * 60);
return _dbContext.Articles.ToListAsync;
});
var article = articleList.Where(c => c.Title == title).FirstOrDefault()
if(article == null)
{
return "Non exist"
}
return article.Name();
}
await
dopo il ritorno in GetOrCreate
? Singleton
e usarlo nelle azioni e nelle visualizzazioni del rasoio? Il fatto che il contesto EF non sia thread-safe non è collegato ad async \ await, quindi la tua prima domanda non ha molto senso. La migliore pratica generale è comunque quella di non riutilizzare il contesto EF singolo per più operazioni logiche. Nelle applicazioni multithreading, la violazione di questa regola di solito porta immediatamente al disastro. Se si utilizza lo stesso contesto EF da un singolo thread alla volta, la violazione potrebbe portare a determinati comportamenti imprevisti, in particolare se si utilizza lo stesso contesto per molte operazioni per un lungo periodo di tempo.
In ASP.NET ogni richiesta ha un thread dedicato, ogni richiesta non dura per molto tempo e ogni richiesta di solito rappresenta una singola operazione logica. Per questo motivo, il contesto EF viene solitamente registrato con durata scoped, il che significa che ogni richiesta ha un contesto separato che viene eliminato al termine della richiesta. Per la maggior parte degli usi questo modello funziona bene (sebbene si possa ancora ottenere un comportamento inaspettato se si esegue una logica complessa con molte richieste di database in una singola richiesta http). Personalmente preferisco ancora usare una nuova istanza di contesto EF per ogni operazione (quindi nel tuo esempio avrei iniettato il factory di contesto EF e creato una nuova istanza all'interno del corpo di GetOrCreate
, che poi GetOrCreate
immediatamente smaltito). Tuttavia, questa non è un'opinione molto popolare.
Quindi, ogni richiesta ha un contesto separato (se è registrato con durata scoped), quindi non si dovrebbe (in generale, se non si generano i thread di sfondo da soli) preoccuparsi dell'accesso multithread a quell'istanza.
Il tuo metodo però è ancora errato, perché ciò che ora salvi nella cache non è List<Article>
. Memorizzate invece Task<List<Acticle>>
. Con la memoria cache funziona, perché l'archiviazione nella memoria cache non comporta alcuna serializzazione. Ma non appena cambi il tuo provider di cache, le cose si romperanno, perché Task
è ovviamente serializzabile. Utilizzare invece GetOrCreateAsync
(nota che non vi è ancora alcun motivo per aggiungere l' await
dopo il return
):
var articleList = await _cache.GetOrCreateAsync("CacheKey", entry =>
{
entry.SlidingExpiration = TimeSpan.FromSeconds(60 * 60);
return _dbContext.Articles.ToListAsync();
});
Nota che se l'elemento è già presente nella cache - non ci sarà alcun lavoro asincrono, l'intera operazione verrà eseguita in modo completamente sincrono (non lasciarti await
ingannare te - se await
qualcosa non significa che ci saranno dei thread in background o operazioni asincrone implicate).
Generalmente non è buono memorizzare entità EF direttamente nella cache. È meglio convertirli in entità DTO e memorizzarli. Tuttavia, se si archiviano entità EF, almeno non dimenticare di disabilitare il caricamento lento e la creazione del proxy per il proprio contesto.
Non sarà appropriato registrare tale servizio come singleton perché dipende dal contesto EF che ha ambito la durata (di default). Registralo con la stessa durata che hai per il contesto EF.
Per quanto riguarda evitare oggetti "titolare di dati" - non vedo come è collegato qui. Il tuo servizio ha una logica (recupera i dati dalla cache o dal database) - non esiste solo per consentire l'accesso ad alcuni altri oggetti.