SqlAlchemy 2.0 中文文档(十一)(3)

简介: SqlAlchemy 2.0 中文文档(十一)

SqlAlchemy 2.0 中文文档(十一)(2)https://developer.aliyun.com/article/1562988


声明式 vs. 命令式形式

随着 SQLAlchemy 的发展,不同的 ORM 配置风格已经出现。对于本节及其他使用带注解的声明式映射的示例,相应的非带注解形式应使用所需的类或字符串类名作为传递给relationship()的第一个参数。下面的示例说明了本文档中使用的形式,这是一个完全声明式的示例,使用PEP 484注解,其中relationship()构造还从Mapped注解中推断目标类和集合类型,这是 SQLAlchemy 声明式映射的最现代形式:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship(back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="children")

相比之下,使用不带注解的声明式映射更像是更“经典”的映射形式,其中relationship()需要直接传递所有参数,如下例所示:

class Parent(Base):
    __tablename__ = "parent_table"
    id = mapped_column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id = mapped_column(Integer, primary_key=True)
    parent_id = mapped_column(ForeignKey("parent_table.id"))
    parent = relationship("Parent", back_populates="children")

最后,使用命令式映射,这是在声明式之前 SQLAlchemy 的原始映射形式(尽管仍然被少数用户偏爱),以上配置如下所示:

registry.map_imperatively(
    Parent,
    parent_table,
    properties={"children": relationship("Child", back_populates="parent")},
)
registry.map_imperatively(
    Child,
    child_table,
    properties={"parent": relationship("Parent", back_populates="children")},
)

此外,对于非带注解的映射,默认的集合样式是list。要使用set或其他集合而不带注解,可以使用relationship.collection_class参数进行指示:

class Parent(Base):
    __tablename__ = "parent_table"
    id = mapped_column(Integer, primary_key=True)
    children = relationship("Child", collection_class=set, ...)

有关relationship()的集合配置详细信息,请参阅自定义集合访问。

根据需要,将注意到注释和非注释/命令式样式之间的其他差异。

一对多

一对多关系在子表上放置一个外键,引用父表。然后在父表上指定relationship(),作为对由子表表示的项目集合的引用:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship()
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))

在一对多关系中建立双向关系时,其中“反向”端是多对一,需要指定一个额外的relationship()并使用relationship.back_populates参数将两者连接起来,其中使用每个relationship()的属性名称作为另一个relationship.back_populates的值:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[List["Child"]] = relationship(back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="children")

Child将获得一个具有多对一语义的parent属性。

使用集合、列表或其他集合类型进行一对多关系

使用带注释的声明性映射时,用于relationship()的集合类型是从传递给Mapped容器类型的集合类型派生的。上一节的示例可以编写为使用set而不是list来表示Parent.children集合,使用Mapped[Set["Child"]]

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[Set["Child"]] = relationship(back_populates="parent")

当使用非注释形式,包括命令式映射时,可以使用relationship.collection_class参数传递要用作集合的 Python 类。

另请参见

自定义集合访问 - 包含了更多关于集合配置的细节,包括一些将relationship()映射到字典的技术。

配置一对多关系的删除行为

通常情况下,当其所属的Parent被删除时,所有Child对象都应该被删除。为了配置这种行为,使用 delete 中描述的delete级联选项。另一个选项是,当Child对象与其父对象解除关联时,可以删除Child对象本身。这种行为在 delete-orphan 中描述。

另请参见

delete

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

delete-orphan

使用集合、列表或其他集合类型进行一对多关系

使用带注释的声明性映射,从传递给Mapped容器类型的集合类型派生出用于relationship()的集合类型。可以写一个例子,以使用set而不是list来表示Parent.children集合,使用Mapped[Set["Child"]]

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    children: Mapped[Set["Child"]] = relationship(back_populates="parent")

在使用非注释形式,包括命令式映射时,可以使用relationship.collection_class参数传递要用作集合的 Python 类。

另请参阅

自定义集合访问 - 包含有关集合配置的更多详细信息,包括一些将relationship()映射到字典的技术。

配置一对多的删除行为

通常情况下,当其所属的Parent被删除时,所有的Child对象都应该被删除。要配置此行为,可以使用在删除中描述的delete级联选项。另一个选项是当Child对象与其父对象解除关联时,它本身也可以被删除。这种行为在删除孤儿中描述。

另请参阅

删除

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

删除孤儿

多对一

多对一在父表中放置了一个引用子表的外键。在父级上声明relationship(),将创建一个新的标量持有属性:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped["Child"] = relationship()
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)

上面的示例显示了一个假定为非空的多对一关系;下一节,可空多对一,介绍了一个可空版本。

通过在两个方向添加第二个relationship()并在两个方向上应用relationship.back_populates参数,使用每个relationship()的属性名称作为另一个relationship.back_populates的值,实现了双向行为:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[int] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped["Child"] = relationship(back_populates="parents")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List["Parent"]] = relationship(back_populates="child")

可空多对一

在上述示例中,Parent.child 的关系未被类型化为允许 None;这是因为 Parent.child_id 列本身不可为空,因为它被类型化为 Mapped[int]。如果我们想要 Parent.child 成为一个可为空的多对一关系,我们可以将 Parent.child_idParent.child 都设置为 Optional[],在这种情况下,配置将如下所示:

from typing import Optional
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped[Optional["Child"]] = relationship(back_populates="parents")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List["Parent"]] = relationship(back_populates="child")

在上述代码中,用于 Parent.child_id 的列将在 DDL 中被创建以允许 NULL 值。在使用 mapped_column() 进行显式类型声明时,对 child_id: Mapped[Optional[int]] 的规定等效于将 Column.nullable 设置为 TrueColumn 上,而 child_id: Mapped[int] 等效于将其设置为 False。有关此行为的背景信息,请参阅 mapped_column() 从 Mapped 注释中推导出数据类型和可为空性。

提示

如果使用 Python 3.10 或更高版本,PEP 604 语法更方便,可使用 | None 来指示可选类型,结合 PEP 563 推迟注释评估,以便不需要字符串引号类型,这将如下所示:

from __future__ import annotations
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped[Child | None] = relationship(back_populates="parents")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List[Parent]] = relationship(back_populates="child")
```### 可为空的多对一关系
在上述示例中,`Parent.child` 的关系未被类型化为允许 `None`;这是因为 `Parent.child_id` 列本身不可为空,因为它被类型化为 `Mapped[int]`。如果我们想要 `Parent.child` 成为一个**可为空的**多对一关系,我们可以将 `Parent.child_id` 和 `Parent.child` 都设置为 `Optional[]`,在这种情况下,配置将如下所示:
```py
from typing import Optional
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[Optional[int]] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped[Optional["Child"]] = relationship(back_populates="parents")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List["Parent"]] = relationship(back_populates="child")

在上述代码中,用于 Parent.child_id 的列将在 DDL 中被创建以允许 NULL 值。在使用 mapped_column() 进行显式类型声明时,对 child_id: Mapped[Optional[int]] 的规定等效于将 Column.nullable 设置为 TrueColumn 上,而 child_id: Mapped[int] 等效于将其设置为 False。有关此行为的背景信息,请参阅 mapped_column() 从 Mapped 注释中推导出数据类型和可为空性。

提示

如果使用 Python 3.10 或更高版本,PEP 604 语法更方便地使用| None来指示可选类型,与PEP 563推迟的注释评估结合使用,以便不需要字符串引号的类型,会是这样的:

from __future__ import annotations
class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child_id: Mapped[int | None] = mapped_column(ForeignKey("child_table.id"))
    child: Mapped[Child | None] = relationship(back_populates="parents")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parents: Mapped[List[Parent]] = relationship(back_populates="child")

一对一

One To One 本质上是一个从外键角度来看的一对多关系,但表示在任何时候只会有一行指向特定父行的行。

当使用带注释的映射和Mapped时,“一对一”约定通过在关系的两侧应用非集合类型到Mapped注释来实现,这将暗示 ORM 不应在任一侧使用集合,如下面的示例所示:

class Parent(Base):
    __tablename__ = "parent_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    child: Mapped["Child"] = relationship(back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="child")

上面的例子中,当我们加载一个Parent对象时,Parent.child属性将引用一个单个的Child对象而不是一个集合。如果我们用一个新的Child对象替换Parent.child的值,ORM 的工作单元过程将用新的对象替换之前的对象,将之前的child.parent_id列默认设置为 NULL,除非设置了特定的级联行为。

提示

正如前面提到的,ORM 将“一对一”模式视为一种约定,其中它假设当它加载Parent.child属性时,它只会得到一行返回。如果返回多行,ORM 将发出警告。

然而,上述关系中的Child.parent方面仍然是一个“多对一”的关系。独自使用时,它不会检测到多个Child的赋值,除非设置了relationship.single_parent参数,这可能是有用的:

class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="child", single_parent=True)

在设置此参数之外,“一对多”方面(这里按照惯例是一对一)也不会可靠地检测到多个Child关联到单个Parent的情况,比如多个Child对象是挂起的并且不是数据库持久的情况。

是否使用了relationship.single_parent,建议数据库模式包括一个唯一约束来指示Child.parent_id列应该是唯一的,以确保在数据库级别只有一行Child可能指向特定的Parent行(有关__table_args__元组语法的背景,请参阅声明式表配置):

from sqlalchemy import UniqueConstraint
class Child(Base):
    __tablename__ = "child_table"
    id: Mapped[int] = mapped_column(primary_key=True)
    parent_id: Mapped[int] = mapped_column(ForeignKey("parent_table.id"))
    parent: Mapped["Parent"] = relationship(back_populates="child")
    __table_args__ = (UniqueConstraint("parent_id"),)

新版本 2.0 中:relationship() 构造可以从给定的Mapped注解推导出relationship.uselist参数的有效值。

对于非注解配置设置 uselist=False

当在没有使用Mapped注解的情况下使用relationship()时,可以通过将通常是“多”端的relationship.uselist参数设置为False来启用一对一模式,如下面的非注解式声明配置所示:

class Parent(Base):
    __tablename__ = "parent_table"
    id = mapped_column(Integer, primary_key=True)
    child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id = mapped_column(Integer, primary_key=True)
    parent_id = mapped_column(ForeignKey("parent_table.id"))
    parent = relationship("Parent", back_populates="child")

对于非注解配置设置 uselist=False

当在没有使用Mapped注解的情况下使用relationship()时,可以通过将通常是“多”端的relationship.uselist参数设置为False来启用一对一模式,如下面的非注解式声明配置所示:

class Parent(Base):
    __tablename__ = "parent_table"
    id = mapped_column(Integer, primary_key=True)
    child = relationship("Child", uselist=False, back_populates="parent")
class Child(Base):
    __tablename__ = "child_table"
    id = mapped_column(Integer, primary_key=True)
    parent_id = mapped_column(ForeignKey("parent_table.id"))
    parent = relationship("Parent", back_populates="child")


SqlAlchemy 2.0 中文文档(十一)(4)https://developer.aliyun.com/article/1562990

相关文章
|
3月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(十一)(4)
SqlAlchemy 2.0 中文文档(十一)
38 11
|
3月前
|
存储 SQL Python
SqlAlchemy 2.0 中文文档(十一)(5)
SqlAlchemy 2.0 中文文档(十一)
39 10
|
3月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(十一)(1)
SqlAlchemy 2.0 中文文档(十一)
37 2
|
3月前
|
存储 SQL 数据库
SqlAlchemy 2.0 中文文档(十一)(2)
SqlAlchemy 2.0 中文文档(十一)
26 2
|
3月前
|
SQL 测试技术 数据库
SqlAlchemy 2.0 中文文档(十二)(5)
SqlAlchemy 2.0 中文文档(十二)
21 2
|
3月前
|
测试技术 Python 容器
SqlAlchemy 2.0 中文文档(十二)(2)
SqlAlchemy 2.0 中文文档(十二)
31 1
|
3月前
|
SQL 存储 关系型数据库
SqlAlchemy 2.0 中文文档(十二)(1)
SqlAlchemy 2.0 中文文档(十二)
20 1
|
3月前
|
测试技术 Python 容器
SqlAlchemy 2.0 中文文档(十二)(4)
SqlAlchemy 2.0 中文文档(十二)
30 1
|
3月前
|
API 数据库 Python
SqlAlchemy 2.0 中文文档(十四)(5)
SqlAlchemy 2.0 中文文档(十四)
35 1
|
3月前
|
数据库 Python 容器
SqlAlchemy 2.0 中文文档(十四)(3)
SqlAlchemy 2.0 中文文档(十四)
25 1