SQLAlchemy 2.0 文档
级联¶
映射器支持在 relationship()
结构上配置 级联 行为的概念。这指的是对相对于特定 Session
的“父”对象执行的操作如何传播到该关系引用的项(例如,“子”对象),并且受 relationship.cascade
选项影响。
级联的默认行为仅限于所谓的 save-update 和 merge 设置的级联。级联的典型“替代”设置是添加 delete 和 delete-orphan 选项;这些设置适用于仅在与父级关联时才存在的相关对象,否则将被删除。
级联行为使用 relationship.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")
)
relationship.cascade
的默认值为 save-update, merge
。此参数的典型替代设置是 all
或更常见的是 all, delete-orphan
。符号 all
是 save-update, merge, refresh-expire, expunge, delete
的同义词,在与 delete-orphan
结合使用时,表示子对象应在所有情况下都与其父对象保持一致,并在不再与该父对象关联时被删除。
警告
级联选项 all
意味着 refresh-expire 级联设置,这在使用 异步 I/O (asyncio) 扩展时可能不理想,因为它会比在显式 IO 上下文中通常合适的更积极地使相关对象失效。有关更多背景信息,请参见 在使用 AsyncSession 时防止隐式 IO 中的说明。
以下小节描述了可以为 relationship.cascade
参数指定的可用值列表。
save-update¶
save-update
级联表示当通过 Session.add()
将对象放入 Session
时,通过此 relationship()
与之关联的所有对象也应该添加到同一 Session
中。假设我们有一个对象 user1
,它有两个相关对象 address1
、address2
>>> user1 = User()
>>> address1, address2 = Address(), Address()
>>> user1.addresses = [address1, address2]
如果我们将 user1
添加到 Session
中,它也会隐式添加 address1
、address2
>>> sess = Session()
>>> sess.add(user1)
>>> address1 in sess
True
save-update
级联还会影响已存在于 Session
中的对象的属性操作。如果我们将第三个对象 address3
添加到 user1.addresses
集合中,它将成为该 Session
状态的一部分。
>>> address3 = Address()
>>> user1.addresses.append(address3)
>>> address3 in sess
True
当从集合中删除项目或从标量属性中取消关联对象时,save-update
级联可能会表现出令人惊讶的行为。在某些情况下,孤立的对象可能仍会拉入前父级的 Session
中;这样做是为了使 flush 过程能够适当地处理该相关对象。这种情况通常只发生在从一个 Session
中删除对象并将其添加到另一个 Session
中时。
>>> user1 = sess1.scalars(select(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
级联默认启用,通常被视为理所当然;它通过允许使用单次对 Session.add()
的调用来立即在该 Session
中注册整个对象结构,从而简化代码。虽然可以禁用它,但通常没有必要这样做。
双向关系中 save-update 级联的行为¶
在双向关系的上下文中,save-update
级联是单向的,即在使用 relationship.back_populates
或 relationship.backref
参数创建两个分别引用彼此的 relationship()
对象时。
当一个未与 Session
关联的对象被分配到一个与 Session
关联的父对象的属性或集合中时,该对象将自动添加到同一个 Session
中。但是,相反的操作不会产生这种效果;一个未与 Session
关联的对象,在其被分配了与 Session
关联的子对象后,不会自动将该父对象添加到 Session
中。这种行为的总体主题被称为“级联反向引用”,它代表了从 SQLAlchemy 2.0 开始标准化的行为变化。
为了说明,假设一个 Order
对象的映射,它们通过关系 Order.items
和 Item.order
双向关联一系列 Item
对象。
mapper_registry.map_imperatively(
Order,
order_table,
properties={"items": relationship(Item, back_populates="order")},
)
mapper_registry.map_imperatively(
Item,
item_table,
properties={"order": relationship(Order, back_populates="items")},
)
如果一个 Order
已经与 Session
关联,然后创建一个 Item
对象并将其追加到该 Order
的 Order.items
集合中,该 Item
将自动级联到同一个 Session
中。
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> o1.items.append(i1)
>>> o1 is i1.order
True
>>> i1 in session
True
上面,Order.items
和 Item.order
的双向性质意味着追加到 Order.items
也意味着分配到 Item.order
。同时,允许 Item
对象添加到与父 Order
关联的同一个 Session
中的 save-update
级联。
但是,如果上面的操作以 **相反** 方向执行,其中 Item.order
被分配而不是直接追加到 Order.item
,即使对象分配 Order.items
和 Item.order
处于与先前示例相同的状态,级联操作到 Session
也不会自动发生。
>>> o1 = Order()
>>> session.add(o1)
>>> o1 in session
True
>>> i1 = Item()
>>> i1.order = o1
>>> i1 in order.items
True
>>> i1 in session
False
在上面的情况下,在创建 Item
对象并为其设置所有期望状态之后,应该将其显式添加到 Session
中。
>>> session.add(i1)
在早期版本的 SQLAlchemy 中,save-update
级联会在所有情况下双向发生。然后使用一个名为 cascade_backrefs
的选项使其成为可选的。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,cascade_backrefs
选项在 SQLAlchemy 2.0 中被移除。这样做的理由是,用户通常不会觉得将对象上的属性分配给另一个对象(如上面的 i1.order = o1
的分配)会改变该对象 i1
的持久性状态,使其现在在一个 Session
中处于挂起状态,并且经常会存在后续问题,即自动刷新会过早地刷新对象并导致错误,在那些给定对象仍在构造中且未准备好刷新状态的情况下。选择单向和双向行为之间的选项也被移除,因为该选项创建了两种略微不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。
另请参阅
cascade_backrefs 行为在 2.0 中被弃用 - “级联反向引用”行为变化的背景
delete¶
delete
级联表示当一个“父”对象被标记为删除时,与其相关的“子”对象也应该被标记为删除。例如,如果我们有一个关系 User.addresses
,并配置了 delete
级联
class User(Base):
# ...
addresses = relationship("Address", cascade="all, delete")
如果使用上面的映射,我们有一个 User
对象和两个相关的 Address
对象
>>> user1 = sess1.scalars(select(User).filter_by(id=1)).first()
>>> address1, address2 = user1.addresses
如果我们标记 user1
为删除,在刷新操作完成之后,address1
和 address2
也将被删除
>>> sess.delete(user1)
>>> sess.commit()
DELETE FROM address WHERE address.id = ?
((1,), (2,))
DELETE FROM user WHERE user.id = ?
(1,)
COMMIT
或者,如果我们的 User.addresses
关系没有 delete
级联,SQLAlchemy 的默认行为是通过将其外键引用设置为 NULL
来将 address1
和 address2
与 user1
解除关联。使用以下映射
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 一对多关系的级联通常与 delete-orphan 级联结合使用,如果“子”对象与父对象解除关联,则将为相关行发出 DELETE。 delete
和 delete-orphan
级联的组合涵盖了 SQLAlchemy 必须在设置外键列为 NULL 与完全删除行之间做出决定的两种情况。
默认情况下,该功能完全独立于数据库配置的 FOREIGN KEY
约束,这些约束本身可能配置 CASCADE
行为。为了更有效地与这种配置集成,应该使用在 使用外键 ON DELETE 级联与 ORM 关系 中描述的其他指令。
警告
请注意,ORM 的“delete”和“delete-orphan”行为 **仅** 适用于使用 Session.delete()
方法在 工作单元 过程中标记单个 ORM 实例进行删除。它 **不** 适用于“批量”删除,批量删除将使用 delete()
结构发出,如 使用自定义 WHERE 条件的 ORM UPDATE 和 DELETE 所示。有关更多背景信息,请参阅 ORM 启用更新和删除的重要说明和注意事项 。
在多对多关系中使用删除级联¶
cascade="all, delete"
选项同样适用于多对多关系,这种关系使用 relationship.secondary
来指示关联表。当一个父对象被删除,因此与相关对象解除关联时,工作单元过程通常会从关联表中删除行,但保持相关对象完整。当与 cascade="all, delete"
结合使用时,将为子行本身执行额外的 DELETE
语句。
以下示例改编了 多对多 的示例,以说明在关联的 **一侧** 上设置 cascade="all, delete"
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id")),
Column("right_id", Integer, ForeignKey("right.id")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
)
上面,当使用 Session.delete()
将 Parent
对象标记为删除时,刷新过程将像往常一样删除与 association
表关联的行,但是根据级联规则,它还将删除所有相关的 Child
行。
警告
如果上述cascade="all, delete"
设置在**两个**关系中都被配置,那么级联操作将继续级联所有Parent
和Child
对象,加载遇到的每个children
和parents
集合,并删除所有连接的对象。通常不希望“delete”级联双向配置。
在 ORM 关系中使用外键 ON DELETE 级联¶
SQLAlchemy 的“delete”级联的行为与数据库FOREIGN KEY
约束的ON DELETE
功能重叠。SQLAlchemy 允许使用DDL构造的ForeignKey
和ForeignKeyConstraint
来配置这些模式级行为;这些对象与Table
元数据的结合使用在ON UPDATE and ON DELETE中描述。
为了在relationship()
中使用ON DELETE
外键级联,首先要注意的是relationship.cascade
设置必须仍然配置为匹配所需的“delete”或“set null”行为(使用delete
级联或省略它),以便无论 ORM 还是数据库级约束处理实际修改数据库中数据的任务,ORM 仍然能够适当地跟踪可能受影响的本地存在对象的狀態。
然后,relationship()
上还有一个额外的选项,它指示 ORM 应该在多大程度上尝试自行对相关行运行 DELETE/UPDATE 操作,以及它应该在多大程度上依赖于期望数据库端的 FOREIGN KEY 约束级联来处理任务;这是relationship.passive_deletes
参数,它接受选项False
(默认值)、True
和"all"
。
最典型的例子是当父行被删除时子行被删除,并且在相关FOREIGN KEY
约束上也配置了ON DELETE CASCADE
class Parent(Base):
__tablename__ = "parent"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
back_populates="parent",
cascade="all, delete",
passive_deletes=True,
)
class Child(Base):
__tablename__ = "child"
id = mapped_column(Integer, primary_key=True)
parent_id = mapped_column(Integer, ForeignKey("parent.id", ondelete="CASCADE"))
parent = relationship("Parent", back_populates="children")
当父行被删除时上述配置的行为如下
应用程序调用
session.delete(my_parent)
,其中my_parent
是Parent
的实例。当
Session
下次将更改刷新到数据库时,ORM 将删除my_parent.children
集合中的所有当前加载的项目,这意味着将为每个记录发出DELETE
语句。如果
my_parent.children
集合被卸载,则不会发出任何DELETE
语句。如果在这个relationship()
上没有设置relationship.passive_deletes
标志,那么将为卸载的Child
对象发出SELECT
语句。然后,为
my_parent
行本身发出DELETE
语句。数据库级
ON DELETE CASCADE
设置确保child
中所有引用parent
中受影响行的行也被删除。由
my_parent
引用的Parent
实例,以及与该对象相关的所有已加载的Child
实例(即上面的步骤 2 已经完成),都将与Session
分离。
注意
要使用“ON DELETE CASCADE”,底层数据库引擎必须支持FOREIGN KEY
约束,并且它们必须在执行中
使用 MySQL 时,必须选择合适的存储引擎。有关详细信息,请参见CREATE TABLE arguments including Storage Engines。
使用 SQLite 时,必须显式启用外键支持。有关详细信息,请参见Foreign Key Support。
在多对多关系中使用外键 ON DELETE¶
如 在多对多关系中使用删除级联 所述,“删除”级联也适用于多对多关系。要在多对多中使用 ON DELETE CASCADE
外键,FOREIGN KEY
指令在关联表上配置。这些指令可以处理自动从关联表中删除的任务,但不能适应自动删除相关对象本身。
在这种情况下, relationship.passive_deletes
指令可以为我们节省一些额外的 SELECT
语句,但在删除操作期间,ORM 仍然会继续加载一些集合,以便定位受影响的子对象并正确处理它们。
注意
对这一点的假设优化可能包括对关联表中的所有父级关联行执行一次 DELETE
语句,然后使用 RETURNING
定位受影响的相关子行,但是这目前不属于 ORM 工作单元实现的一部分。
在此配置中,我们在关联表的两个外键约束上都配置了 ON DELETE CASCADE
。我们在关系的父级->子级端配置 cascade="all, delete"
,然后可以在双向关系的另一侧配置 passive_deletes=True
,如下所示
association_table = Table(
"association",
Base.metadata,
Column("left_id", Integer, ForeignKey("left.id", ondelete="CASCADE")),
Column("right_id", Integer, ForeignKey("right.id", ondelete="CASCADE")),
)
class Parent(Base):
__tablename__ = "left"
id = mapped_column(Integer, primary_key=True)
children = relationship(
"Child",
secondary=association_table,
back_populates="parents",
cascade="all, delete",
)
class Child(Base):
__tablename__ = "right"
id = mapped_column(Integer, primary_key=True)
parents = relationship(
"Parent",
secondary=association_table,
back_populates="children",
passive_deletes=True,
)
使用上述配置,Parent
对象的删除过程如下
使用
Session.delete()
将Parent
对象标记为删除。当发生刷新时,如果
Parent.children
集合未加载,ORM 将首先发出 SELECT 语句,以加载对应于Parent.children
的Child
对象。然后它将为
association
中对应于该父级行的行发出DELETE
语句。对于受此立即删除影响的每个
Child
对象,因为配置了passive_deletes=True
,工作单元不需要尝试为每个Child.parents
集合发出 SELECT 语句,因为它假设association
中的对应行将被删除。然后为从
Parent.children
加载的每个Child
对象发出DELETE
语句。
delete-orphan¶
delete-orphan
级联在 delete
级联中添加了行为,这样当子对象与父对象分离时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。当处理由其父对象“拥有”的关联对象时,这是一种常见的功能,该关联对象具有一个 NOT NULL 外键,因此从父对象集合中删除该项目会导致该项目的删除。
delete-orphan
级联意味着每个子对象一次只能有一个父对象,并且在绝大多数情况下,它仅在“一对多”关系上配置。对于在多对一或多对多关系上设置它的非常不常见的情况,可以强制“多”端一次只允许一个对象,方法是配置 relationship.single_parent
参数,它建立 Python 端验证以确保该对象一次只与一个父对象相关联,但这极大地限制了“多”关系的功能,通常不是想要的。
另请参阅
对于关系 <relationship>,delete-orphan 级联通常仅在“一对多”关系的“一”端配置,而不是在多对一或多对多关系的“多”端配置。 - 关于涉及 delete-orphan 级联的常见错误场景的背景信息。
merge¶
merge
级联表示 Session.merge()
操作应该从作为 Session.merge()
调用的主题的父对象传播到引用的对象。此级联在默认情况下也是开启的。
refresh-expire¶
refresh-expire
是一个不常见的选项,表示 Session.expire()
操作应该从父对象传播到引用的对象。当使用 Session.refresh()
时,引用的对象仅过期,但不会真正刷新。
expunge¶
expunge
级联表示当父对象使用 Session.expunge()
从 Session
中删除时,该操作应该传播到引用的对象。
关于删除 - 删除从集合和标量关系引用的对象¶
ORM 通常不会在刷新过程中修改集合或标量关系的内容。这意味着,如果你的类具有一个 relationship()
,它引用对象集合,或者引用单个对象(例如多对一),那么当刷新过程发生时,该属性的内容将不会被修改。相反,预计 Session
最终会过期,无论是通过 Session.commit()
的提交后过期行为,还是通过显式使用 Session.expire()
。在那时,与该 Session
相关联的任何引用对象或集合都将被清除,并且将在下一次访问时重新加载自身。
关于这种行为,一个常见的误解与使用 Session.delete()
方法有关。当对一个对象调用 Session.delete()
并刷新 Session
时,该行将从数据库中删除。通过外键引用目标行的行,假设它们使用两个映射对象类型之间的 relationship()
进行跟踪,它们的外部键属性也将被更新为 null,或者如果设置了级联删除,相关行也将被删除。但是,即使与已删除对象相关的行本身可能也被修改,在刷新范围内的操作中,与关系绑定的集合或对象上的对象引用都不会发生变化。这意味着,如果该对象是相关集合的成员,那么在该集合过期之前,它仍然会出现在 Python 端。类似地,如果该对象通过多对一或一对一关系从另一个对象引用,那么该引用将保留在该对象上,直到该对象过期。
下面,我们说明在标记 Address
对象以供删除后,它仍然存在于父 User
关联的集合中,即使在刷新后也是如此
>>> address = user.addresses[1]
>>> session.delete(address)
>>> session.flush()
>>> address in user.addresses
True
当上述会话提交时,所有属性都会过期。下次访问 user.addresses
将重新加载集合,显示所需的状态
>>> session.commit()
>>> address in user.addresses
False
有一个食谱可以拦截 Session.delete()
并自动调用此过期;请参阅 ExpireRelationshipOnFKChange 以了解这一点。但是,在集合中删除项目的通常做法是放弃直接使用 Session.delete()
,而是使用级联行为自动调用删除,作为从父集合中删除对象的结果。 delete-orphan
级联可以实现这一点,如下面的示例所示
class User(Base):
__tablename__ = "user"
# ...
addresses = relationship("Address", cascade="all, delete-orphan")
# ...
del user.addresses[1]
session.flush()
在上面,当从 User.addresses
集合中删除 Address
对象时, delete-orphan
级联的作用是标记 Address
对象以供删除,与将其传递给 Session.delete()
一样。
delete-orphan
级联也可以应用于多对一或一对一关系,这样当对象与父对象分离时,也会自动标记其为删除。在多对一或一对一关系上使用 delete-orphan
级联需要一个额外的标志 relationship.single_parent
,它会调用一个断言,表明这个相关对象不能与任何其他父对象同时共享
class User(Base):
# ...
preference = relationship(
"Preference", cascade="all, delete-orphan", single_parent=True
)
在上面,如果假设的 Preference
对象从 User
中删除,它将在刷新时被删除
some_user.preference = None
session.flush() # will delete the Preference object
另请参阅
级联 以了解级联的详细信息。
flambé! 龙和 炼金术士 图像设计由 Rotem Yaari 创建并慷慨捐赠。
使用 Sphinx 7.2.6 创建。文档最后生成时间:Fri 08 Nov 2024 08:41:19 AM EST