级联¶

映射器支持可配置的概念 cascade 行为对 relationship() 构造。这是指相对于特定对象在“父”对象上执行的操作 Session 应传播到该关系引用的项(例如“子”对象),并受 relationship.cascade 选择权。

层叠的默认行为仅限于所谓的层叠 保存更新合并 设置。级联的典型“可选”设置是添加 删除删除孤儿 选项;这些设置适用于相关对象,这些对象仅在附加到其父对象时存在,否则将被删除。

层叠行为是使用 cascade 选择权 relationship() ::

class Order(Base):
    __tablename__ = 'order'

    items = relationship("Item", cascade="all, delete-orphan")
    customer = relationship("User", cascade="save-update")

要在backref上设置层叠,可以将同一标志与 backref() 函数,最终将其参数反馈到 relationship() ::

class Item(Base):
    __tablename__ = 'item'

    order = relationship("Order",
                    backref=backref("items", cascade="all, delete-orphan")
                )

默认值为 cascadesave-update, merge . 此参数的典型替代设置为 all 或者更常见 all, delete-orphan . 这个 all 符号是 save-update, merge, refresh-expire, expunge, delete ,并将其与 delete-orphan 指示子对象在所有情况下都应跟随其父对象,并且在不再与该父对象关联后将其删除。

可以为指定的可用值列表 cascade 参数在下面的小节中描述。

保存更新

save-update Cascade表示当一个对象被放置到 Session 通过 Session.add() ,所有与此关联的对象 relationship() 也应该添加到同一个 Session . 假设我们有一个对象 user1 有两个相关对象 address1address2 ::

>>> user1 = User()
>>> address1, address2 = Address(), Address()
>>> user1.addresses = [address1, address2]

如果我们增加 user1 到A Session ,它还将添加 address1address2 隐含地:

>>> sess = Session()
>>> sess.add(user1)
>>> address1 in sess
True

save-update 层叠还影响已存在于 Session . 如果我们添加第三个对象, address3user1.addresses 集合,它成为状态的一部分 Session ::

>>> address3 = Address()
>>> user1.append(address3)
>>> address3 in sess
>>> True

save-update 有可能令人惊讶的行为,那就是 远离的 从集合或在某些情况下,也可以将标量属性拉入 Session 对于父对象;这是为了刷新过程可以适当地处理相关的对象。这种情况通常只有当一个对象从一个对象上移开时才会发生。 Session 并添加到另一个:

>>> user1 = sess1.query(User).filter_by(id=1).first()
>>> address1 = user1.addresses[0]
>>> sess1.close()   # user1, address1 no longer associated with sess1
>>> user1.addresses.remove(address1)  # address1 no longer associated with user1
>>> sess2 = Session()
>>> sess2.add(user1)   # ... but it still gets added to the new session,
>>> address1 in sess2  # because it's still "pending" for flush
True

这个 save-update 默认情况下,cascade是打开的,通常被认为是理所当然的;它通过允许对 Session.add() 在其中注册对象的整个结构 Session 马上。虽然它可以被禁用,但通常不需要这样做。

其中一例 save-update 层叠有时也会妨碍双向关系的发生,例如backrefs,这意味着子对象与特定父对象的关联可能具有父对象与子对象的隐式关联的效果。 Session ;此模式以及如何使用 cascade_backrefs 标志,在本节中讨论 在backrefs上控制级联 .

删除

这个 delete Cascade表示当“父”对象被标记为删除时,其相关的“子”对象也应标记为删除。例如,如果我们有关系 User.addresses 具有 delete 配置的层叠:

class User(Base):
    # ...

    addresses = relationship("Address", cascade="save-update, merge, delete")

如果使用上面的映射,我们有一个 User 对象和两个相关 Address 对象::

>>> user1 = sess.query(User).filter_by(id=1).first()
>>> address1, address2 = user1.addresses

如果我们做了记号 user1 对于删除,在执行刷新操作之后, address1address2 也将被删除:

>>> sess.delete(user1)
>>> sess.commit()
DELETE FROM address WHERE address.id = ? ((1,), (2,)) DELETE FROM user WHERE user.id = ? (1,) COMMIT

或者,如果我们的 User.addresses 关系确实如此 notdelete 层叠,sqlAlchemy的默认行为是取消关联 address1address2user1 通过将其外键引用设置为 NULL . 使用如下映射:

class User(Base):
    # ...

    addresses = relationship("Address")

删除父级时 User 对象中的行 address 不删除,而是取消关联:

>>> sess.delete(user1)
>>> sess.commit()
UPDATE address SET user_id=? WHERE address.id = ? (None, 1) UPDATE address SET user_id=? WHERE address.id = ? (None, 2) DELETE FROM user WHERE user.id = ? (1,) COMMIT

delete 层叠通常与 删除孤儿 层叠,如果“子”对象从父对象中取消关联,将为相关行发出删除。结合 deletedelete-orphan cascade包括两种情况,其中sqlAlchemy必须决定将外键列设置为空,而不是完全删除行。

ORM-level "delete" cascade vs. FOREIGN KEY level "ON DELETE" cascade

SQLAlchemy的“删除”级联的行为与 ON DELETE CASCADE 数据库外键的特性,以及 ON DELETE SET NULL 未指定“删除”层叠时的外键设置。数据库级别“on delete”级联特定于关系数据库的“外键”构造;SQLAlchemy允许在 DDL 级别使用选项打开 ForeignKeyConstraint 其描述见 更新和删除时 .

重要的是要注意ORM和关系数据库的“级联”概念之间的差异以及它们如何集成:

  • 数据库级别 ON DELETE 层叠在 many-to-one 关系的一方;也就是说,我们相对于 FOREIGN KEY 约束是关系的“多”方面。在ORM级别, 这个方向是反的 . sqlAlchemy处理从“parent”端相对于“parent”删除“child”对象,这意味着 deletedelete-orphan 层叠配置在 one-to-many 一边。

  • 没有的数据库级外键 ON DELETE 设置通常用于 防止 不删除父行,因为它必然会留下未处理的相关行。如果在一对多关系中需要此行为,则SQLAlchemy的默认行为是将外键设置为 NULL 可以通过以下两种方式之一捕获:

    • 最简单也是最常见的是将外键保持列设置为 NOT NULL 在数据库架构级别。SQLAlchemy尝试将列设置为空将失败,并出现简单的非空约束异常。

    • 另一种更特殊的方法是 passive_deletes 标记到字符串 "all" . 这会完全禁用SQLAlchemy将外键列设置为空的行为,并且会对父行发出删除操作,而不会对子行产生任何影响,即使子行存在于内存中。在数据库级别的外键触发器(无论是特殊的还是特殊的)的情况下,这可能是可取的。 ON DELETE 如果删除父行,则需要在所有情况下激活设置或其他设置。

  • 数据库级 ON DELETE 级联是 效率大大提高 而不是SQLAlchemy。数据库可以一次跨多个关系链接一系列级联操作;例如,如果删除了A行,则可以删除表B中的所有相关行,以及与每个B行相关的所有C行,以及在单个DELETE语句范围内的所有ON和ON。另一方面,为了完全支持级联删除操作,SQLAlchemy必须单独加载每个相关的集合,以便将所有可能具有进一步相关集合的行作为目标。也就是说,sqlAlchemy不够复杂,无法在该上下文中同时为所有相关行发出删除。

  • SQLAlchemy没有 need 为了做到这一点,我们提供了与数据库本身的平滑集成 ON DELETE 功能,通过使用 passive_deletes 选项与正确配置的外键约束一起使用。在此行为下,sqlAlchemy只对已经在本地存在于 Session ;对于任何已卸载的集合,它都会将它们留给数据库进行处理,而不是为它们发出一个选择。断面 使用被动删除 提供了此用法的示例。

  • 当数据库级别 ON DELETE 功能只在关系的“多”方面起作用,SQLAlchemy的“delete”cascade具有 有限的 能够在 颠倒 方向,也就是说,当删除“多”侧的引用时,可以在“多”侧配置为删除“一”侧的对象。但是,如果有其他对象引用“多”中的“一”面,这很容易导致约束冲突,因此通常只有当关系实际上是“一对一”时才有用。这个 single_parent 应该使用标志为此案例建立一个in-python断言。

当使用 relationship() 它还包括使用 secondary 选项,sqlachemy的delete cascade自动处理多对多表中的行。就像,如中所述 从多对多表中删除行 ,从多对多集合中添加或删除对象将导致在多对多表中插入或删除一行, delete 当作为父对象删除操作的结果被激活时,CASCADE不仅会删除“子”表中的行,还会删除多对多表中的行。

删除孤儿

delete-orphan 层叠将行为添加到 delete 层叠,这样当子对象与父对象取消关联时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。这是处理父集合“拥有”的相关对象时的一个常见功能,该对象具有非空的外键,因此从父集合中删除该项会导致删除该项。

delete-orphan 层叠意味着每个子对象一次只能有一个父对象,因此在绝大多数情况下配置为一对多关系。将其设置为多对一或多对多关系更为尴尬;对于这个用例,SQLAlchemy要求 relationship() 配置为 single_parent 参数,建立Python端验证,以确保对象一次只与一个父级关联。

合并

merge 层叠表示 Session.merge() 操作应该从父级传播,父级是 Session.merge() 调用引用的对象。默认情况下,此级联也处于启用状态。

refresh-expire

refresh-expire 是一个不常见的选项,表示 Session.expire() 操作应该从父对象传播到被引用的对象。使用时 Session.refresh() ,引用的对象仅过期,但实际未刷新。

删去

expunge cascade表示当父对象从 Session 使用 Session.expunge() ,该操作应向下传播到引用的对象。

在backrefs上控制级联

这个 保存更新 默认情况下,层叠发生在从backrefs发出的属性更改事件上。这可能是一个更容易通过演示描述的令人困惑的语句;这意味着,给定这样的映射:

mapper(Order, order_table, properties={
    'items' : relationship(Item, backref='order')
})

如果一个 Order 已在会话中,并分配给 order AN属性 Item ,backref附加了 Itemitems 收集 Order ,导致 save-update 级联发生:

>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True

>>> i1 = Item()
>>> i1.order = o1
>>> i1 in o1.items
True
>>> i1 in session
True

可以使用禁用此行为 cascade_backrefs 标志:

mapper(Order, order_table, properties={
    'items' : relationship(Item, backref='order',
                                cascade_backrefs=False)
})

所以上面的任务 i1.order = o1 将追加 i1items 收藏 o1 ,但不会添加 i1 参加会议。当然,你可以, add() i1 稍后再参加会议。此选项可能有助于在对象构造完成之前将其从会话中保留出来,但仍需要将其与目标会话中已持久存在的对象关联。