SqlAlchemy 2.0 中文文档(二十三)(1)

简介: SqlAlchemy 2.0 中文文档(二十三)


原文:docs.sqlalchemy.org/en/20/contents.html

级联

原文:docs.sqlalchemy.org/en/20/orm/cascades.html

映射器支持在relationship()构造上配置可配置级联行为的概念。这涉及到相对于特定Session上执行的操作应如何传播到由该关系引用的项目(例如“子”对象),并且受到relationship.cascade选项的影响。

级联的默认行为仅限于所谓的 save-update 和 merge 设置的级联。级联的典型“替代”设置是添加 delete 和 delete-orphan 选项;这些设置适用于只有在附加到其父对象时才存在的相关对象,并且在其他情况下将被删除。

使用relationship()上的relationship.cascade选项配置级联行为:

class Order(Base):
    __tablename__ = "order"
    items = relationship("Item", cascade="all, delete-orphan")
    customer = relationship("User", cascade="save-update")

要在反向引用上设置级联,可以使用相同的标志与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-orphanall符号是save-update, merge, refresh-expire, expunge, delete的同义词,与delete-orphan结合使用表示子对象应在所有情况下跟随其父对象,并且一旦不再与该父对象关联就应该被删除。

警告

all级联选项意味着 refresh-expire 级联设置,当使用异步  I/O(asyncio)扩展时可能不可取,因为它将比在显式 I/O 上下文中通常适当地更积极地使相关对象过期。有关更多背景信息,请参阅在使用  AsyncSession 时防止隐式 I/O 中的注释。

可以为relationship.cascade参数指定的可用值列表在以下各小节中进行描述。

save-update

save-update级联指示当通过Session.add()将对象放入Session中时,通过这个relationship()与之关联的所有对象也应该添加到同一个Session中。假设我们有一个对象user1,其中包含两个相关对象address1address2

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

如果我们将user1添加到Session中,它也会隐式添加address1address2

>>> 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中;这是为了使刷新过程可以适当地处理相关对象。这种情况通常只会在一个对象从一个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 级联的行为

在双向关系的上下文中,即使用relationship.back_populatesrelationship.backref参数创建相互引用的两个独立的relationship()对象时,save-update级联是单向的

当一个未关联Session的对象被赋给与关联Session相关的父对象的属性或集合时,它将自动添加到同一Session中。然而,反向操作不会产生此效果;当一个未关联Session的对象被赋给与关联Session相关的子对象时,不会自动将该父对象添加到Session中。这种行为的总体主题称为“级联反向引用”,并代表了从 SQLAlchemy 2.0 开始标准化的行为变更。

以示例说明,假设给定了一个Order对象的映射,它与一系列Item对象通过关系Order.itemsItem.order双向关联:

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对象并将其附加到该OrderOrder.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.itemsItem.order的双向性意味着附加到Order.items也会赋值给Item.order。同时,save-update级联允许将Item对象添加到与父Order已关联的相同Session中。

然而,如果上述操作以反向方向执行,即赋值Item.order而不是直接附加到Order.item,则级联操作不会自动进行,即使对象赋值Order.itemsItem.order与上一个示例中的状态相同:

>>> 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 中,保存-更新级联在所有情况下都会双向发生。然后,使用一个称为cascade_backrefs的选项使其成为可选项。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs选项。其理由是用户通常不会觉得将对象的属性分配给对象上的属性是直观的,如上面所示的i1.order = o1的分配,会改变对象i1的持久状态,使其现在在Session中处于挂起状态,并且在那些给定对象仍在构建并且尚未准备好被刷新的情况下,自动刷新会过早地刷新对象并导致错误。选择在单向和双向行为之间选择的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。

另请参阅

在 2.0 中弃用以删除的 cascade_backrefs 行为 - 关于“级联反向引用”行为变更的背景 ## 删除

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进行删除,在刷新操作进行后,address1address2也将被删除:

>>> 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来解除user1address1address2的关联。使用以下映射:

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。deletedelete-orphan级联的组合涵盖了 SQLAlchemy 必须在将外键列设置为 NULL 与完全删除行之间做出决定的两种情况。

该功能默认完全独立于数据库配置的FOREIGN KEY约束,这些约束本身可能配置CASCADE行为。为了更有效地与此配置集成,应使用描述在使用 ORM 关系中的外键 ON DELETE 级联的附加指令。

警告

请注意,ORM 的“delete”和“delete-orphan”行为适用于使用Session.delete()方法在 unit of work 过程中标记单个 ORM 实例以进行删除。它适用于“批量”删除,这将使用delete()构造发出,如在 ORM UPDATE and DELETE with Custom WHERE Criteria 中所示。有关更多背景信息,请参阅 ORM 启用的更新和删除的重要说明和警告。

另请参阅

使用 ORM 关系的外键 ON DELETE 级联

使用删除级联处理多对多关系

delete-orphan

使用删除级联处理多对多关系

cascade="all, delete"选项在多对多关系中同样有效,该关系使用relationship.secondary指示一个关联表。当删除父对象,因此与其相关对象解除关联时,工作单元过程通常会从关联表中删除行,但保留相关对象。与cascade="all, delete"结合使用时,将为子行本身执行额外的DELETE语句。

以下示例将 Many To Many 的示例调整为说明关联的一侧设置为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"设置在两个关系上都配置了,则级联操作将继续通过所有ParentChild对象进行级联,加载遇到的每个childrenparents集合并删除所有连接的内容。通常不希望“delete”级联双向配置。

另请参阅

从多对多表中删除行

使用外键 ON DELETE 处理多对多关系 ### 使用 ORM 关系的外键 ON DELETE 级联处理

SQLAlchemy 的“delete”级联行为与数据库 FOREIGN KEY 约束的 ON DELETE 特性重叠。SQLAlchemy 允许使用 ForeignKeyForeignKeyConstraint 构造配置这些模式级 DDL 行为;如何在 Table 元数据与这些对象的使用一起配置,在 ON UPDATE and ON DELETE 中有描述。

为了将 ON DELETE 外键级联与 relationship() 结合使用,首先必须注意 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")

当删除父行时,上述配置的行为如下:

  1. 应用程序调用 session.delete(my_parent),其中 my_parentParent 的一个实例。
  2. Session 下次将更改刷新到数据库时,my_parent.children 集合中的所有 当前加载的 项目都将由 ORM 删除,这意味着为每条记录发出一个 DELETE 语句。
  3. 如果my_parent.children集合是未加载的,则不会发出DELETE语句。如果在此relationship()上未设置relationship.passive_deletes标志,则将为未加载的Child对象发出SELECT语句。
  4. 然后会为my_parent行本身发出DELETE语句。
  5. 数据库级别的ON DELETE CASCADE设置确保将删除所有引用受影响的parent行的child中的行。
  6. my_parent引用的Parent实例,以及所有与该对象相关联且已加载(即执行了步骤 2)的Child实例,将从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对象的操作如下:

  1. 使用Session.delete()标记要删除的Parent对象。
  2. 当发生刷新时,如果未加载Parent.children集合,则 ORM 将首先发出 SELECT 语句,以加载与Parent.children对应的Child对象。
  3. 然后,将发出针对与该父行对应的association中的行的DELETE语句。
  4. 对于每个受此立即删除影响的Child对象,由于配置了passive_deletes=True,工作单元将不需要尝试为每个Child.parents集合发出 SELECT 语句,因为假定将删除association中的相应行。
  5. 对于从Parent.children加载的每个Child对象,都会发出DELETE语句。 ## delete-orphan

delete-orphan级联会为delete级联添加行为,这样当子对象与父对象取消关联时,子对象将被标记为删除,而不仅仅是在父对象被标记为删除时。当处理一个与父对象“拥有”关系的相关对象时,这是一种常见的特性,该关系具有 NOT NULL 外键,因此从父集合中删除项目会导致其被删除。

delete-orphan级联意味着每个子对象一次只能有一个父对象,并且在绝大多数情况下,它只配置在一对多关系上。对于设置在多对一或多对多关系上的非常罕见的情况,可以通过配置relationship.single_parent参数来强制“多”端一次只允许一个对象,该参数建立了 Python 端验证,确保对象一次只与一个父对象关联,但这大大限制了“多”关系的功能,通常不是所需的。

另请参阅

对于关系,delete-orphan 级联通常仅配置在一对多关系的“一”端,而不是多对一或多对多关系的“多”端。 - 关于涉及 delete-orphan 级联的常见错误场景的背景。 ## merge

merge级联表示应该从Session.merge()操作从调用Session.merge()的主体父对象向下传播到引用的对象。这个级联也是默认开启的。 ## refresh-expire

refresh-expire 是一个不常见的选项,表示Session.refresh()操作应该从父对象传播到引用的对象。当使用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 级联的效果与将其传递给 Session.delete() 相同,标记了 Address 对象以删除。

delete-orphan 级联也可以应用于多对一或一对一关系,这样当一个对象从其父对象中取消关联时,它也会自动标记为删除。在多对一或一对一关系上使用delete-orphan级联需要额外的标志relationship.single_parent,该标志会触发一个断言,即此相关对象不应同时与任何其他父对象共享:

class User(Base):
    # ...
    preference = relationship(
        "Preference", cascade="all, delete-orphan", single_parent=True
    )

如果上面的情况下,一个假设的Preference对象从一个User中移除,它将在 flush 时被删除:

some_user.preference = None
session.flush()  # will delete the Preference object

另请参阅

有关级联的详细信息,请参阅 Cascades。## save-update

save-update 级联表示当通过Session.add()将对象放入Session时,通过此relationship()与之关联的所有对象也应该被添加到同一个Session中。假设我们有一个对象user1,它有两个相关对象address1address2

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

如果我们将user1添加到Session中,它也会隐式添加address1address2

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

save-update 级联也会影响已经存在于Session中的对象的属性操作。如果我们向user1.addresses集合添加第三个对象address3,它将成为该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一次注册整个对象结构的单个调用来简化代码。虽然它可以被禁用,但通常没有必要这样做。

具有双向关系的 save-update 级联的行为

save-update级联在双向关系的情况下单向发生,即当使用relationship.back_populatesrelationship.backref参数创建两个相互引用的relationship()对象时。

当将一个未与Session关联的对象分配给与Session关联的父对象的属性或集合时,该对象将自动添加到同一个Session中。然而,反向操作不会产生这种效果;当分配一个未与Session关联的对象时,分配给一个与Session关联的子对象,不会自动将父对象添加到Session中。这种行为的总体主题被称为“级联反向引用”,代表了作为 SQLAlchemy 2.0 的标准化行为的变化。

为了说明,假设有一系列通过关系Order.itemsItem.orderItem对象双向关联的Order对象的映射:

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对象并附加到该OrderOrder.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.itemsItem.order的双向性意味着附加到Order.items也会赋值给Item.order。同时,save-update级联允许将Item对象添加到与父Order已关联的同一个Session中。

然而,如果上述操作在反向方向进行,即将Item.order赋值而不是直接附加到Order.item,则级联操作不会自动进行到Session中,即使对象赋值Order.itemsItem.order的状态与前面的示例相同:

>>> 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 中,保存-更新级 learning method 会在所有情况下双向发生。然后,通过一个名为cascade_backrefs的选项将其变为可选。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs选项。其理由是,用户通常不会觉得在对象的属性上赋值(如上面所示的i1.order = o1的赋值)会改变该对象i1的持久化状态,使其现在处于Session中处于挂起状态,并且经常会出现自动刷新会过早刷新对象并导致错误的情况,在这些情况下,给定对象仍在构建中且尚未处于准备好刷新的状态状态。选择单向和双向行为之间的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了  ORM 的整体学习曲线以及文档和用户支持负担。

另请参阅

在 2.0 中弃用了 cascade_backrefs 行为 - 关于“级联 backrefs”行为变更的背景 ### 双向关系中保存-更新级联的行为

save-update级联在双向关系的上下文中单向发生,即在使用relationship.back_populatesrelationship.backref参数创建相互引用的两个单独的relationship()对象时。

一个未与Session相关联的对象,当分配给与Session相关联的父对象的属性或集合时,将自动添加到相同的Session中。但是,相反的操作不会产生这种效果;一个未与Session相关联的对象,其中一个与Session相关联的子对象被分配,将不会自动将该父对象添加到Session中。此行为的整体主题称为“级联反向引用”,并代表了作为 SQLAlchemy 2.0 的标准化行为的变化。

为了说明,假设有一系列通过关系Order.itemsItem.orderItem对象双向关联的Order对象的映射:

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对象并将其附加到该OrderOrder.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.itemsItem.order的双向性意味着附加到Order.items也会赋值给Item.order。同时,save-update级联允许将Item对象添加到与父Order已关联的相同Session中。

但是,如果上述操作是以相反方向执行的,即将Item.order赋值而不是直接附加到Order.item,则即使对象分配Order.itemsItem.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 中,保存-更新级联在所有情况下都会双向发生。然后,使用称为cascade_backrefs的选项将其变为可选。最后,在 SQLAlchemy 1.4 中,旧行为被弃用,并且在 SQLAlchemy 2.0 中删除了cascade_backrefs选项。其理由是用户通常不会觉得将对象的属性分配给对象上的属性是直观的,如上面所示的i1.order = o1的赋值会改变对象i1的持久化状态,使其现在处于Session中处于挂起状态,并且在那些给定对象仍在构建并且尚未准备好被刷新的情况下,会经常出现自动刷新会过早刷新对象并导致错误的情况。选择单向和双向行为之间的选项也被删除,因为此选项创建了两种略有不同的工作方式,增加了 ORM 的整体学习曲线以及文档和用户支持负担。

另请参阅

2.0 中将删除的 cascade_backrefs 行为已弃用 - 关于“级联反向引用”行为变更的背景信息


SqlAlchemy 2.0 中文文档(二十三)(2)https://developer.aliyun.com/article/1560516

相关文章
|
关系型数据库 Java MySQL
|
3月前
|
存储 人工智能 前端开发
从零构建智能对话助手:LangGraph + ReAct 实现具备记忆功能的 AI 智能体
本文系统介绍了基于 LangGraph 框架构建具备记忆能力的 ReAct(Reasoning + Action)智能体的技术实现方法。ReAct 智能体结合语言模型的推理能力与外部工具的执行能力,通过“思考-行动-观察”循环机制,实现复杂任务的自主处理。文章详细讲解了 LangGraph 的图结构设计、状态管理、工具集成与记忆系统等关键技术,并通过代码示例演示了从基础工作流到高级智能体系统的构建过程。最终实现的智能体具备多轮对话、工具调用、结果反馈与上下文记忆能力,为开发下一代智能应用提供了技术基础。
486 1
|
存储 SQL 关系型数据库
SqlAlchemy 2.0 中文文档(二十三)(2)
SqlAlchemy 2.0 中文文档(二十三)
164 0
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(二十三)(3)
SqlAlchemy 2.0 中文文档(二十三)
394 0
|
SQL 测试技术 API
SqlAlchemy 2.0 中文文档(一)(1)
SqlAlchemy 2.0 中文文档(一)
469 1
SqlAlchemy 2.0 中文文档(一)(1)
|
JSON API 数据库
探索FastAPI:不仅仅是一个Python Web框架,更是助力开发者高效构建现代化RESTful API服务的神器——从环境搭建到CRUD应用实战全面解析
【8月更文挑战第31天】FastAPI 是一个基于 Python 3.6+ 类型提示标准的现代 Web 框架,以其高性能、易用性和现代化设计而备受青睐。本文通过示例介绍了 FastAPI 的优势及其在构建高效 Web 应用中的强大功能。首先,通过安装 FastAPI 和 Uvicorn 并创建简单的“Hello, World!”应用入门;接着展示了如何处理路径参数和查询参数,并利用类型提示进行数据验证和转换。
576 0
|
SQL 数据处理 数据库
提升数据处理效率:深入探讨Entity Framework Core中的批量插入与更新操作及其优缺点
【8月更文挑战第31天】在软件开发中,批量插入和更新数据是常见需求。Entity Framework Core 提供了批处理功能,如 `AddRange` 和原生 SQL 更新,以提高效率。本文通过对比这两种方法,详细探讨它们的优缺点及适用场景。
474 0
|
SQL 数据库 数据库管理
SqlAlchemy 2.0 中文文档(一)(2)
SqlAlchemy 2.0 中文文档(一)
304 1