Некоторые проблемы с MapperExtension из sqlalchemy - PullRequest
6 голосов
/ 05 сентября 2010

Существует два класса: Пользователь и Вопрос

У пользователя может быть много вопросов, и он также содержит question_count для записи количества вопросов, принадлежащих ему.

Итак, когда я добавляю новый вопрос, я хочу обновить счетчик вопросов пользователя.Сначала я делаю так:

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


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

Все идет хорошо.

Но я не хочу использовать функцию обратного вызова события, чтобы сделать то же самое.Как показано ниже:

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. Примечание в методе after_insert:

    instance.user # -> Get None!!!

    Почему?

  2. Если я изменю эту строку на:

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

    Я могу успешно получить пользователя, но: пользователь не может быть обновлен!

    Смотри, я изменил пользователя:

    user.question_count += 1

    Но в консоли не выводится sql update, и question_count не обновляются.

  3. Я пытаюсь добавить Session.flush() или Session.commit() в методе after_insert(), но оба вызывают ошибки.

Есть что-то важное, что мне не хватает?Пожалуйста, помогите мне, спасибо

Ответы [ 2 ]

7 голосов
/ 06 сентября 2010

Автор sqlalchemy дал мне полезный ответ на форуме, я его копирую здесь:

Кроме того, ключевая концепция Единица работы шаблона заключается в том, что это организует полный список всех Операторы INSERT, UPDATE и DELETE который будет выпущен, а также порядок, в котором они испускаются, прежде чем что-то случится. Когда before_insert () и after_insert () Хуки событий называются, эта структура был определен, и не может быть изменилось никак. документация для before_insert () и before_update () упоминает, что на этот план нельзя повлиять точка - только отдельные атрибуты на объект под рукой, и те, которые еще не были вставлены или обновлены, могут быть затронуты здесь. Любая схема который хотел бы изменить флеш План должен использовать SessionExtension.before_flush. Тем не менее, есть несколько способов выполняя то, что вы хотите здесь без изменения плана сброса.

Самое простое, что я уже предложил. использование MapperExtension.before_insert () на «Пользовательский» класс и набор user.question_count = LEN (user.questions). Это предполагает что вы мутируете Коллекция user.questions, а не работа с Question.user для установить отношения. если ты случилось использовать "динамический" отношения (что не так здесь), вы бы вытащить историю для user.questions и подсчитать, что были добавлены и удалены.

Следующий способ - сделать что вы думаете, что вы хотите здесь, то есть реализовать after_insert в вопросе, но испускают оператор UPDATE сам. Вот почему «связь» один из аргументов к мапперу методы расширения:

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

Я бы не предпочел такой подход, так как это довольно расточительно для многих новых Вопросы добавляются в один Пользователь. Так что еще один вариант, если На User.questions нельзя положиться и вы хотели бы избежать многих специальных ОБНОВЛЕНИЕ заявления, на самом деле повлиять на план сброса с помощью SessionExtension.before_flush:

Класс MySessionExtension (SessionExtension): def before_flush (self, session, flush_context): для obj в session.new: если isinstance (объект, вопрос): obj.user.question_count + = 1

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

Объединить «совокупный» подход метод before_flush с Подход "испускай SQL сам" метод after_insert (), вы можете также используйте SessionExtension.after_flush, посчитать все и испустить единый массовый оператор UPDATE со многими параметры. Мы, вероятно, хорошо в царство излишества для этого конкретного ситуация, но я привел пример такой схемы в Пиконе в прошлом году, который вы можете увидеть на http://bitbucket.org/zzzeek/pycon2010/src/tip/chap5/sessionextension.py .

И, как я пытался, я обнаружил, что мы должны обновить user.question_count в after_flush

2 голосов
/ 06 сентября 2010

user, поскольку я предполагаю, что RelationshipProperty заполняется только после сброса (поскольку только в этой точке ORM знает, как связать две строки).

Похоже, question_count на самом деле является производным свойством, представляющим собой число строк Вопроса для этого пользователя. Если производительность не имеет значения, вы можете использовать свойство только для чтения и позволить маперу выполнить всю работу:

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

В противном случае вы смотрите на реализацию триггера, либо на уровне базы данных, либо в python (который изменяет план сброса, так что это более сложно).

...