2010-09-05 11 views
6

Ci sono due classi: Utente e domandaAlcuni problemi con MapperExtension di sqlalchemy

Un utente può avere molte domande, e contiene anche un question_count per registrare il conte di domande gli appartiene.

Quindi, quando aggiungo una nuova domanda, desidero aggiornare il question_count dell'utente . Inizialmente, faccio come:

question = Question(title='aaa', content='bbb') 
Session.add(question) 
Session.flush() 


user = question.user 
### user is not None 
user.question_count += 1 
Session.commit() 

Tutto va bene.

Ma non voglio usare callback di eventi per fare la stessa cosa. Come segue:

from sqlalchemy.orm.interfaces import MapperExtension 
class Callback(MapperExtension): 
    def after_insert(self, mapper, connection, instance): 
     user = instance.user 
     ### user is None !!! 
     user.question_count += 1 


class Question(Base): 
    __tablename__ = "questions" 
    __mapper_args__ = {'extension':Callback()} 
    .... 
  1. Nota nel metodo "after_insert":

    instance.user # -> Get None!!!

    Perché?

  2. Se cambio quella linea a:

    Session.query(User).filter_by(id=instance.user_id).one()

    posso ottenere l'utente con successo, ma: l'utente non può essere aggiornato!

    Guardate Ho modificato l'utente:

    user.question_count += 1

    ma non c'è nessuna sql 'update' stampati nella console, e il question_count non vengono aggiornati.

  3. cerco di aggiungere Session.flush() o Session.commit() nel metodo after_insert(), ma entrambi gli errori di causa.

C'è qualche cosa importante che mi manca? Per favore aiutatemi, grazie

risposta

7

L'autore di sqlalchemy mi ha dato una risposta utile in un forum, copio qui:

Inoltre, un concetto chiave della unità di modello di lavoro è che organizza un elenco completo di tutti istruzioni INSERT, UPDATE e DELETE che verranno emesse, così come l'ordine in cui vengono emesse, prima che qualcosa accada. Quando vengono chiamati i hook evento before_insert() e after_insert() , questa struttura è stata determinata e non può essere modificata in alcun modo . La documentazione per before_insert() e before_update() afferma che il piano a filo non può essere influenzato in questo punto - solo i singoli attributi sul l'oggetto a portata di mano, e quelli che non sono state inserite o ancora aggiornato, può essere colpiti qui. Qualsiasi schema che desidera modificare il piano di svuotamento deve utilizzare SessionExtension.before_flush. Tuttavia, ci sono diversi modi per eseguire ciò che si desidera qui senza modificare il piano di risciacquo.

Il più semplice è quello che ho già suggerito . Utilizzare MapperExtension.before_insert() nella classe "Utente" e impostare user.question_count = len (user.questions). Ciò presuppone che si sta modificando la raccolta user.questions, anziché con Question.user su stabilire la relazione. Se hai utilizzando una relazione "dinamica" qui (che non è il caso ), devi inserire la cronologia per user.questions e contare ciò che è stato aggiunto e rimosso.

Il modo più prossimo, è quello di fare più o meno ciò che si pensa si vuole qui, cioè attrezzo after_insert sulla domanda, ma emettono l'istruzione UPDATE te stesso. Ecco perché "connessione" è uno degli argomenti per i metodi di estensione mapper :

def after_insert(self, mapper, connection, instance): 
    connection.execute(users_table.update().\ 
     values(question_count=users_table.c.question_count +1).\ 
      where(users_table.c.id==instance.user_id)) 

non preferirei che l'approccio in quanto è abbastanza dispendioso per molti nuovi Domande che viene aggiunto a una singola utente. Così ancora un'altra opzione, se User.questions non possono essere invocate e vuoi evitare molti-ad hoc istruzioni UPDATE, è in realtà influenzare il piano a filo utilizzando SessionExtension.before_flush:

classe MySessionExtension (SessionExtension): def before_flush (self, session, flush_context): per obj in session.new: se isinstance (obj, domanda): obj.user.question_count + = 1

for obj in session.deleted: 
     if isinstance(obj, Question): 
      obj.user.question_count -= 1 

Per combinare l'approccio "aggregato" di metodo "before_flush" con il "emettere SQL te stesso" approccio metodo after_insert(), è possibile utilizzare anche SessionExtension. after_flush, per contare tutto ed emettere un'istruzione UPDATE singola massa con molti parametri . Siamo probabilmente bene nel regno della eccessivo per questa particolare situazione, ma ho presentato un esempio di tale regime in PyCon l'anno scorso, che si può vedere a http://bitbucket.org/zzzeek/pycon2010/src/tip/chap5/sessionextension.py .

E, come ho cercato, ho trovato che dovremmo aggiornare il user.question_count in after_flush

2

user, poiché presumo una proprietà di relazione, viene popolata solo dopo lo svuotamento (poiché solo questo punto l'ORM sa come correlare le due righe).

Sembra che question_count sia effettivamente una proprietà derivata, essendo il numero di righe Domanda per quell'utente. Se le prestazioni non è un problema, è possibile utilizzare un solo leggere la proprietà e lasciare che il mapper fare il lavoro:

@property 
def question_count(self): 
    return len(self.questions) 

Altrimenti stai cercando di implementare un trigger, sia a livello di database o in python (che modifica il piano di risciacquo, quindi è più complicato).

+0

grazie. Quindi è un compito complicato? Ma il requisito è comune, se c'è una buona soluzione sarà grandioso – Freewind

Problemi correlati