2010-09-05 2 views
6

Есть два класса: пользователь и вопросНекоторых проблемы с MapperExtension из SQLAlchemy

Пользователь может иметь много вопросов, и он также содержит question_count для записи граф вопросов принадлежат ему.

Итак, когда я добавляю новый вопрос, я хочу обновить 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()

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

    Look Я модифицировал пользователя:

    user.question_count += 1

    Но нет никакого 'обновление' SQL печататься в консоли, а question_count не обновляются.

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

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

ответ

7

Автор 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 на вопрос, но испускают изменять Политику себя. Вот почему «соединение» является один из аргументов в методах расширения картографа :

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 нельзя полагаться на , и вы хотели бы избежать многих Времнной заявления UPDATE, является на самом деле влияют на флеш-план с помощью SessionExtension.before_flush:

класс MySessionExtension (SessionExtension): защиту before_flush (самоощущение, сеансы, 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 с множеством параметров . Мы, вероятно, хорошо в области избыточна для данной конкретной ситуации , но я представил пример такой схемы на PyCon в прошлом году, , который вы можете увидеть на http://bitbucket.org/zzzeek/pycon2010/src/tip/chap5/sessionextension.py .

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

2

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

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

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

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

+0

спасибо. Так что это сложная задача? Но это требование является общим, если есть хорошее решение, это будет здорово – Freewind

Смежные вопросы