2014-09-08 9 views
5

Sto usando Pony ORM per una soluzione di pallone e ho trovato quanto segue.DatabaseSessionIsOver con Pony ORM a causa del caricamento lento?

Si consideri il seguente:

@db_session 
def get_orders_of_the_week(self, user, date): 
    q = select(o for o in Order for s in o.supplier if o.user == user) 
    q2 = q.filter(lambda o: o.date >= date and o.date <= date+timedelta(days=7)) 
    res = q2[:] 

    #for r in res: 
    # print r.supplier.name 

    return res 

Quando ho bisogno il risultato in Jinja2 - che è simile a questa

{% for order in res %} 
    Supplier: {{ order.supplier.name }} 
{% endfor %} 

ho un

DatabaseSessionIsOver: Cannot load attribute Supplier[3].name: the database session is over 

Se togliere il commento alla for r in res parte, funziona bene. Sospetto che ci sia una sorta di caricamento pigro che non viene caricato con res = q2[:]. Mi manca completamente un punto o cosa sta succedendo qui?

risposta

5

Questo accade perché si sta tentando di accedere all'oggetto correlato che non è stato caricato e poiché si sta tentando di accedervi al di fuori della sessione del database (la funzione decorata con db_session), Pony solleva questa eccezione.

L'approccio consigliato è quello di utilizzare il db_session decoratore al livello superiore, nello stesso luogo dove si mette app.route decoratore del Flask:

@app.route('/index') 
@db_session 
def index(): 
    .... 
    return render_template(...) 

In questo modo tutte le chiamate al database saranno spostati con il database sessione, che verrà completata dopo la generazione di una pagina Web.

Se esiste un motivo per cui si desidera restringere la sessione del database a una singola funzione, è necessario iterare gli oggetti di ritorno all'interno della funzione decorata con db_session e accedere a tutti gli oggetti correlati necessari. Pony utilizzerà il modo più efficace per caricare gli oggetti correlati dal database, evitando il problema della query N + 1. In questo modo Pony estrae tutti gli oggetti necessari all'interno dell'ambito db_session, mentre la connessione al database è ancora attiva.

--- aggiornamento:

In questo momento, per caricare gli oggetti correlati, si dovrebbe iterare il risultato della query e chiamare il relativo attributo dell'oggetto:

for r in res: 
    r.supplier.name 

E 'simile al codice a il tuo esempio, ho appena rimosso l'istruzione print. Quando si "tocca" l'attributo r.supplier.name, Pony carica tutti gli attributi non pigri dell'oggetto correlato supplier. Se è necessario caricare attributi pigri, è necessario toccare ciascuno di essi separatamente.

Sembra che sia necessario introdurre un modo per specificare quali oggetti correlati devono essere caricati durante l'esecuzione della query. Aggiungeremo questa funzionalità in una delle versioni future.

+0

Nel mio caso, il metodo decorato con 'db_session' si trova in un modulo di repository e avere un decoratore' db_session' al di fuori di questo repository annulla il suo scopo (aumentando l'accoppiamento tra la funzione route e l'implementazione del repository). C'è un modo più elegante di iterare attraverso tutti gli oggetti di ritorno di quello che ho tentato di fare? Vorrei che tutti i dati di tutti gli oggetti secondari disponibili uno il metodo restituisca il risultato. – kasperhj

+0

@lejon Ho appena aggiornato la risposta –

+0

Che suona alla grande! Nel frattempo intendo "toccare" dove necessario. Sono stato indotto in errore dal '[:]' come pensavo avrebbe congelato tutto nella query in una lista. – kasperhj

6

Ho appena aggiunto la funzionalità di prelettura che dovrebbe risolvere il tuo problema. Puoi prendere codice funzionante dal GitHub repository. Questa funzione farà parte della prossima versione Pony ORM 0.5.4.

Ora si può scrivere:

q = q.prefetch(Supplier) 

o

q = q.prefetch(Order.supplier) 

e Pony caricherà automaticamente connessi supplier oggetti.

Qui di seguito mostrerò diverse query con il prefetching, utilizzando l'esempio standard di Pony con studenti, gruppi e reparti. Oggetti

from pony.orm.examples.presentation import * 

Caricamento Student solo, senza precaricamento:

students = select(s for s in Student)[:] 

Caricamento studenti insieme con i gruppi e dipartimenti:

students = select(s for s in Student).prefetch(Group, Department)[:] 

for s in students: # no additional query to the DB is required 
    print s.name, s.group.major, s.group.dept.name 

lo stesso come sopra, ma specificando gli attributi al posto di entità:

students = select(s for s in Student).prefetch(Student.group, Group.dept)[:] 

for s in students: # no additional query to the DB is required 
    print s.name, s.group.major, s.group.dept.name 

Caricamento studenti e suoi corsi (molti-a-molti):

students = select(s for s in Student).prefetch(Student.courses) 

for s in students: 
    print s.name 
    for c in s.courses: # no additional query to the DB is required 
     print c.name 

Come un parametri del metodo prefetch() è possibile specificare entità e/o attributi. Se hai specificato un'entità, allora tutti gli attributi a con questo tipo verranno precaricati. Se hai specificato un attributo, allora questo attributo specifico sarà precaricato. Gli attributi to-many vengono precaricati solo se specificati esplicitamente (come nell'esempio Student.courses). Il prefetching va in modo ricorsivo, quindi puoi caricare una lunga catena di attributi, come ad esempio student.group.dept.

Quando l'oggetto è precaricato, per impostazione predefinita vengono caricati tutti gli attributi, ad eccezione degli attributi pigri e di molti attributi. È possibile precaricare esplicitamente gli attributi lazy e to-many se necessario.

Spero che questo nuovo metodo copra completamente il vostro caso d'uso. Se qualcosa non funziona come previsto, per favore start new issue on GitHub. Inoltre puoi discutere delle funzionalità e fare richieste di funzionalità allo Pony ORM mailing list.

P.S. Non sono sicuro che il pattern di repository che usi offra i tuoi seri benefici. Penso che in realtà aumenti l'accoppiamento tra il rendering del modello e l'implementazione del repository, perché potrebbe essere necessario modificare l'implementazione del repository (ad esempio aggiungere nuove entità all'elenco di precaricamento) quando il codice del modello inizia a utilizzare nuovi attributi. Con il decoratore di livello superiore @db_session è sufficiente inviare il risultato della query al modello e tutto avviene automaticamente, senza necessità di preconfigurazione esplicita. Ma forse mi manca qualcosa, quindi sarò interessato a vedere ulteriori commenti sui vantaggi dell'uso del pattern di repository nel tuo caso.

+0

Questo è fantastico! Impiegherò il 'prefetch' molto. Il repository è un modo per astrarre via Pony. Era principalmente all'inizio del mio progetto in cui non sapevo se impiegare Pony o qualcos'altro. Con le mie crescenti capacità di guida su Pony e i tuoi continui sforzi di sviluppo, la necessità di un repository sembra davvero svanire quando le domande diventano così pitoni- che ha poco senso nasconderle in un repository. – kasperhj