【问题标题】:Modify other objects on update/insert在更新/插入时修改其他对象
【发布时间】:2016-06-28 10:53:37
【问题描述】:

我有两个映射对象,Parent 和 Child。

class Parent(Base):
    __tablename__ = 'parent'
    id = ...
    name = ...
    date_modified = Column(SA_DateTime, default=DateTime.now,
                           onupdate=DateTime.now, nullable=False)

class Child(Base):
    __tablename__ = 'child'
    id = ...
    name = ...
    date_modified = Column(SA_DateTime, default=DateTime.now,
                           onupdate=DateTime.now, nullable=False)
    parent = relationship(Parent, backref='parent')

当一个孩子更新时,我不仅要更改Child.date_modified,还要更改Child.parent.date_modified。

我试着这样做:

@event.listens_for(Child, 'after_update')
def modified_listener(mapper, connection, target):
    if object_session(target).is_modified(target, include_collections=False):
        target.parent.date_modified = DateTime.now()

但这不起作用,因为我已经处于同花顺状态并且得到类似的东西

SAWarning: Attribute history events accumulated on 1 previously clean instance within inner-flush event handlers have been reset, and will not result in database updates. Consider using set_committed_value() within inner-flush event handlers to avoid this warning.

如何使用 SQLAlchemy 解决这个问题?

【问题讨论】:

    标签: python sqlalchemy


    【解决方案1】:

    在here 和here 之前,本网站已经介绍了使用SQLAlchemy events 的基本更新父级时子更改,但在您的情况下,您尝试在刷新期间更新父级,可能使用来自孩子的update default value,它将是可见的after the update,或者完全是一个新值。在事件处理程序中修改父级并不像您最初想象的那么简单:

    警告

    映射器级别的刷新事件只允许非常有限的操作,只允许在被操作的行的本地属性上,以及允许在给定的Connection 上发出任何 SQL。 请完整阅读Mapper-level Events 处的说明,了解使用这些方法的指南;一般来说,SessionEvents.before_flush() 方法应该是一般刷新更改的首选。

    如您所见,简单

    target.parent.date_modified = DateTime.now()
    

    在您的事件处理程序中警告:

    SAWarning:在内部刷新事件处理程序中的 1 个先前干净的实例上累积的属性历史事件已重置,不会导致数据库更新。考虑在内部刷新事件处理程序中使用 set_committed_value() 以避免此警告。

    set_committed_value() 允许设置没有历史事件的属性,就好像设置的值是原始加载状态的一部分。

    您还注意到,在更新后事件处理程序中接收目标并不能保证实际发出了 UPDATE 语句:

    对所有标记为“脏”的实例调用此方法,即使是那些对其基于列的属性没有净更改且未执行 UPDATE 语句的实例。

    和

    要检测对象上基于列的属性是否发生净变化并因此导致 UPDATE 语句,请使用 object_session(instance).is_modified(instance, include_collections=False)。

    因此,一种解决方案可能是使用事件目标中保存的信息,使用给定的连接在父表上发出 UPDATE 语句,然后检查会话中是否存在父对象并设置提交的值它:

    from sqlalchemy import event
    from sqlalchemy.orm.attributes import set_committed_value
    from sqlalchemy.orm.session import object_session
    
    @event.listens_for(Child, 'after_update')                
    def receive_child_after_update(mapper, connection, target):
        session = object_session(target)                       
    
        if not session.is_modified(target, include_collections=False):
            return                                                    
    
        new_date_modified = target.date_modified
    
        # Avoid touching the target.parent relationship attribute and
        # copy the date_modified value from the child to parent.
        # Warning: this will overwrite possible other updates to parent's
        # date_modified.
        connection.execute(
            Parent.__table__.
            update().        
            values(date_modified=new_date_modified).
            where(Parent.id == target.parent_id))
    
        parent_key = session.identity_key(Parent, target.parent_id)
    
        try:
            the_parent = session.identity_map[parent_key]
    
        except KeyError:
            pass
    
        else:
            # If the parent object is present in the session, update its
            # date_modified attribute **in Python only**, to reflect the
            # updated DB state local to this transaction.
            set_committed_value(
                the_parent, 'date_modified', new_date_modified)
    

    【讨论】:

      【解决方案2】:

      你试过了吗:

      @event.listens_for(Child, 'after_update')
      def modified_listener(mapper, connection, target):
          if object_session(target).is_modified(target, include_collections=False):
              sa.orm.attributes.set_committed_value(
                  target.parent,
                  'date_modified',
                  DateTime.now())
      

      根据:

      http://docs.sqlalchemy.org/en/latest/orm/session_api.html#sqlalchemy.orm.attributes.set_committed_value

      【讨论】:

      • 使用set_committed_value()实际上不会将值保存在数据库中,而是取消所有以前存在的历史记录。
      猜你喜欢
      • 2018-01-19
      • 2022-06-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-15
      • 1970-01-01
      相关资源
      最近更新 更多