修改/插入

时间:2016-06-28 10:53:37

标签: python sqlalchemy

我有两个映射对象,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解决这个问题?

2 个答案:

答案 0 :(得分:4)

SQLAlchemy eventshere之前,此网站上已涵盖使用here进行基本更新 - 父级更改时的子项更改,但在您的情况下,您尝试更新父级在同花顺期间,可能使用来自孩子的update default value,其可见after the update,或完全是新值。修改事件处理程序中的父级并不像您可能想象的那样简单:

  

警告

     

映射器级别刷新事件仅允许非常有限的操作,仅对正在操作的行的本地属性,以及允许在给定Connection上发出任何SQL。 请完整阅读<{3}}上的说明,了解有关使用这些方法的指导原则;通常,Mapper-level Events方法应该优先用于一般的冲洗时更改。

正如您所注意到的那样,简单

target.parent.date_modified = DateTime.now()
事件处理程序中的

警告:

  

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

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

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

  

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

  

要检测对象上基于列的属性是否有净更改,从而产生UPDATE语句,请使用object_session(instance).is_modified(instance, include_collections=False)

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

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)

答案 1 :(得分:0)

你试过了吗?

@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