Archiviazione di dati temporanei all'esterno di DbContext in Entity Framework

0

Ho un problema di prestazioni con alcuni metodi nel mio livello di servizio. I metodi che mi causano problemi hanno una certa logica, ma è la connessione db che richiede più del 95% di tempo di esecuzione. Il problema è sfortunatamente hardware, la macchina è solo lenta. Ma lasciatemi spiegare prima il problema del software:

Lo scenario è come questo (semplificato): La società vende automobili, c'è un'entità Car e un'entità PurchaseOrder . Nell'applicazione, c'è una visualizzazione Car con tutto PurchaseOrders . C'è una funzione per determinare PurchaseOrdersStatus . Non sarebbe lento, ma deve interrogare il database "lento" alcune volte. Le query sono molto semplici, come:

"Verifica se FooId esiste nella tabella bar ".

Se l'auto ha pochi ordini di acquisto, la query è veloce. Ma quando la macchina ha centinaia di ordini, sta diventando molto lento. Perché questa query deve essere eseguita molte volte. Ho trovato la soluzione:

  1. Esamina tutti FooId s dalla tabella bar
  2. Salva questo elenco di ID nella memoria
  3. In quelle centinaia di chiamate sto verificando se l'id esiste nella memoria, il che rende il modo più veloce.

La mia domanda è, c'è uno schema di "prequering" dei dati dal database, mettendolo in memoria?

Nelle mie classi di persistenza dei dati, come CarData , ho letteralmente il metodo "Inizializzazione" e gli elenchi dei membri con ID. Ma non sono sicuro se questo è il modo giusto per farlo.

Modifica

Sto facendo una grande query nel database veloce, è molto veloce anche per molti record. Per ottenere "lo stato" ho bisogno di fare qualche altra query, che sono in tabelle totalmente non collegate tra loro, alcune delle quali sono in db molto lento (btw. L'architettura db è davvero terribile, ma è un db legacy, quindi tutto ciò che posso fare è lamentarmi). È possibile eseguire una query tra database (costituita da sottoquery, ad esempio ... WHERE carId IN (select cardId from ... - ma è molto lenta.

EDIT2: la mia soluzione proposta

Nel mio progetto ho alcune classi FooData , alcune hanno la corrispondente FooCache class. La cache ha un aspetto simile a Data, ha DataContext, Collections (elenco di id memorizzato nella cache o qualsiasi altra cosa ho bisogno) e un metodo Initialize . Inserisco FooCache in FooData tramite IoC (quindi ogni volta che viene creato FooData ), MA non lo inizializzo. Solo nei metodi in cui ho bisogno del caching, chiamo FooCache.Initialize(); all'inizio. FooData prima di andare al database, controlla se FooCache è inizializzato. Se non lo è, va al db.

    
posta Ish Thomas 13.03.2018 - 05:23
fonte

2 risposte

2

is there a pattern of "prequering" data from database, putting it into memory?

, viene comunemente chiamato "caching" ed è usato per questo scopo.

when the car has hundreds of orders ... this query must be performed many many times.

Si sta verificando il problema di prestazioni più comune riscontrato dai programmatori che utilizzano un Mappatore relazionale di oggetti come Entity Framework, chiamato SELEZIONA N + 1 . In breve: questo è il momento in cui il numero di query eseguite dall'applicazione aumenta in modo lineare rispetto al numero di record nel database (o in qualche sottoinsieme).

Il modo migliore per evitare i problemi di SELECT N + 1 è produrre un SQL migliore, scrivendo direttamente una vista o una stored procedure nel database, oppure modificando il modo in cui si utilizza l'ORM in modo da generare SQL migliore.

In Entity Framework, le proprietà di navigazione rendono difficile apprezzare pienamente quando il codice sta colpendo il database. La mia preferenza personale è non utilizzare mai le proprietà di navigazione in Entity Framework . Introducono un sacco di problemi e questo è uno di questi. Voglio che il mio ORM sia un modo per pensare in SQL mentre scrivere in C #, ma le proprietà di navigazione sembrano progettate specificamente per nascondere tutto ciò che è "spaventoso" SQL e farti pensare in C #, che rende molto difficile contenere problemi come SELECT N + 1. Invece di utilizzare le proprietà di navigazione, componi gli oggetti IQueryable come necessario in LINQ.

Se ti impegni a utilizzare le proprietà di navigazione, la tua prossima scommessa migliore è utilizzare Include per caricare con entusiasmo le entità correlate in proprietà che sai che stai per attraversare, come spiegato su MSDN .

Quello che stai proponendo è il caching in-memory , che può essere o non essere un'ottima soluzione a seconda dei tuoi vincoli. I principali problemi da considerare con la memorizzazione nella cache sono:

  1. Mantenere i dati freschi. Assicurati che la cache scada più frequentemente di quanto i tuoi dati stiano cambiando, oppure puoi finire con l'uso di dati obsoleti. Se stai preparando il caching per un ciclo di elaborazione dei dati e poi butti via la cache, questo non è un problema. Se è improbabile che i dati vengano modificati durante il runtime dell'applicazione (ad esempio le regole di conversione delle unità), è possibile ottenere una lunga scadenza. Se si tratta di dati primari che gli utenti modificano continuamente, probabilmente non è la soluzione giusta.

  2. Non stai interrogando più del necessario. In genere è molto difficile limitare la query che popola la cache solo ai dati necessari, senza collegarli direttamente alla query primaria (e quindi perché non risolvere il problema con LINQ?), Specialmente se si sta tentando di riutilizzare il cache. Se la quantità di dati possibili che avrai mai una volta è abbastanza piccola rispetto alla tua memoria, non è un problema, ma assicurati di non indovinare solo .

Finché hai considerato i punti precedenti, usare una cache è certamente meglio che tollerare SELECT N + 1s. Per quanto siano comuni, non ho mai visto una situazione in cui un SELECT N + 1 non può essere refactored in un numero costante di query relative al numero di record nel database.

    
risposta data 13.03.2018 - 08:12
fonte
0

fammi districare tutte le barre e le barre. Stai chiamando sql in un ciclo:

foreach(var purchaseOrder in CarPurchaseOrders)
{
    //check if a payment exists
    //select * from payments where id=@id"
}

la risposta è "NON QUERY SQL IN UN LOOP"

invece:

select * 
from 
    purchaseOrder po
left join
    payments p
on 
    p.purchaseOrderId = po.id

riporta tutti gli ordini di acquisto, con il pagamento se sono stati pagati. in una singola query.

Se per qualche motivo non puoi partecipare ai tavoli. (forse sono in database diversi) che puoi fare:

select * from payments where id in ('a','b','c'.....)
    
risposta data 13.03.2018 - 13:36
fonte