2010-09-05 16 views
6

Hay dos clases: de usuario y la preguntaAlgunos problemas con MapperExtension de sqlalchemy

Un usuario puede tener muchas preguntas, y también contiene un question_count para grabar el conde de preguntas pertenecen a él.

Por lo tanto, cuando agregue una nueva pregunta, quiero actualizar el número de pregunta del usuario . Al principio, hago como:

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


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

Todo va bien.

Pero no quiero usar la devolución de eventos por evento para hacer lo mismo. Como siguiente:

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 en el método "after_insert":

    instance.user # -> Get None!!!

    ¿Por qué?

  2. Si cambio a esa línea:

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

    puedo conseguir el usuario correctamente, pero: el usuario no puede actualizarse!

    Look Me han modificado el usuario:

    user.question_count += 1

    Pero no hay sql 'actualización' impreso en la consola, y la question_count no se actualizan.

  3. intento agregar Session.flush() o Session.commit() en el método after_insert(), pero ambos causan errores.

¿Hay algo importante que me falta? Por favor, ayúdenme, gracias

Respuesta

7

El autor de sqlalchemy me dio una respuesta útil en un foro, lo copio aquí:

Además, un concepto clave de la unidad del patrón de trabajo es que organiza una lista completa de todos INSERTAR, ACTUALIZAR y ELIMINAR las declaraciones que se emitirán, así como el orden en el que se emiten, antes de que ocurra algo. Cuando se llaman los ganchos de evento before_insert() y after_insert() , esta estructura se ha determinado y no puede ser modificada de ninguna manera. La documentación para before_insert() y before_update() menciona que el plan de descarga no puede verse afectada en este punto - sólo atributos individuales en el objeto en la mano, y los que no se han insertado o todavía actualizada, puede ser afectado aquí. Cualquier esquema que desee cambiar el plano debe usar SessionExtension.foreforeflush. Sin embargo, hay varias maneras de de lograr lo que desee aquí sin modificar el plan de color.

Lo más simple es lo que ya se me sugirió . Use MapperExtension.before_insert() en la clase "Usuario" y establezca user.question_count = len (user.questions). Esto asume que está mutando la colección user.questions , en lugar de trabajando con Question.user a establezca la relación. Si estaba usando una relación "dinámica" (que no es el caso aquí), debe extraer el historial de user.questions y contar qué se ha anexado y eliminado.

La siguiente forma, es hacer más o menos lo que usted piensa que usted quiere aquí, es decir implemento after_insert en cuestión, pero emitir la instrucción UPDATE mismo. Es por eso que la "conexión" es uno de los argumentos a los métodos de extensión asignador :

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)) 

no preferiría que el enfoque desde es muy derrochador para muchos nuevos preguntas que se añaden a un solo usuario. Así que otra opción, si User.questions no se puede confiar en y que le gustaría evitar muchos ad-hoc UPDATE, es en realidad afectan el plan ras mediante el uso de SessionExtension.before_flush:

clase MySessionExtension (SessionExtension): def before_flush (self, session, flush_context): para obj en session.new: if isinstance (obj, Pregunta): obj.user.question_count + = 1

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

Para combinar el enfoque "agregado" de el método "before_flush" con el "emitir el SQL usted mismo" enfoque de el método after_insert(), puede también utilizar SessionExtension. after_flush, para contar todo y emitir una declaración UPDATE de masa única con muchos parámetros . Que es probable bien en el ámbito de la exageración para este particular situación, pero yo presentamos un ejemplo de tal esquema en Pycon año pasado, que se puede ver en http://bitbucket.org/zzzeek/pycon2010/src/tip/chap5/sessionextension.py .

Y, como he intentado, me pareció que deberíamos actualizar el user.question_count en after_flush

2

user, suponiendo que RelationshipProperty, solo se rellena después de la descarga (ya que es solo este punto que el ORM sabe cómo relacionar las dos filas).

Parece que question_count es en realidad una propiedad derivada, que es el número de filas de preguntas para ese usuario. Si el rendimiento no es una preocupación, se puede utilizar una propiedad de sólo lectura y dejar que el asignador de hacer el trabajo:

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

De lo contrario usted está buscando en la implementación de un gatillo, ya sea a nivel de base de datos o en Python (que modifica el plan de descarga, por lo que es más complicado).

+0

gracias. Entonces, ¿es una tarea complicada? Pero el requisito es común, si hay una buena solución, será genial – Freewind

Cuestiones relacionadas