SqlAlchemy 2.0 中文文档(九)

2024-06-26 14:30:30 浏览数 (2)

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

映射类继承层次结构

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

SQLAlchemy 支持三种继承形式:

  • 单表继承 – 几种类别的类别由单个表表示;
  • 具体表继承 – 每种类别的类别都由独立的表表示;
  • 联接表继承 – 类层次结构在依赖表之间分解。每个类由其自己的表表示,该表仅包含该类本地的属性。

最常见的继承形式是单一和联接表,而具体继承则提出了更多的配置挑战。

当映射器配置在继承关系中时,SQLAlchemy 有能力以多态方式加载元素,这意味着单个查询可以返回多种类型的对象。

另请参见

为继承映射编写 SELECT 语句 - 在 ORM 查询指南 中

继承映射示例 - 联接、单一和具体继承的完整示例

联接表继承

在联接表继承中,沿着类层次结构的每个类都由一个不同的表表示。对类层次结构中特定子类的查询将作为 SQL JOIN 在其继承路径上的所有表之间进行。如果查询的类是基类,则查询基表,同时可以选择包含其他表或允许后续加载特定于子表的属性的选项。

在所有情况下,对于给定行要实例化的最终类由基类上定义的鉴别器列或 SQL 表达式确定,该列将生成与特定子类关联的标量值。

联接继承层次结构中的基类将配置具有指示多态鉴别器列以及可选地为基类本身配置的多态标识符的其他参数:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

    def __repr__(self):
        return f"{self.__class__.__name__}({self.name!r})"

在上面的示例中,鉴别器是 type 列,可以使用 Mapper.polymorphic_on 参数进行配置。该参数接受一个面向列的表达式,可以指定为要使用的映射属性的字符串名称,也可以指定为列表达式对象,如 Columnmapped_column() 构造。

鉴别器列将存储指示行内表示的对象类型的值。该列可以是任何数据类型,但字符串和整数是最常见的。要为数据库中的特定行应用到该列的实际数据值是使用下面描述的 Mapper.polymorphic_identity 参数指定的。

尽管多态鉴别器表达式不是严格必需的,但如果需要多态加载,则需要它。在基础表上建立列是实现这一点的最简单方法,然而非常复杂的继承映射可能会使用 SQL 表达式,例如 CASE 表达式,作为多态鉴别器。

注意

目前,整个继承层次结构只能配置一个鉴别器列或 SQL 表达式,通常在层次结构中最基本的类上。暂时不支持“级联”多态鉴别器表达式。

我们接下来定义 EngineerManagerEmployee 子类。每个类包含代表其所代表的子类的唯一属性的列。每个表还必须包含一个主键列(或列),以及对父表的外键引用:

代码语言:javascript复制
class Engineer(Employee):
    __tablename__ = "engineer"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    engineer_name: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

在上面的示例中,每个映射都在其映射器参数中指定了Mapper.polymorphic_identity参数。此值填充了基础映射器上建立的Mapper.polymorphic_on参数指定的列。每个映射类的Mapper.polymorphic_identity参数应在整个层次结构中是唯一的,并且每个映射类应只有一个“标识”;如上所述,“级联”标识不支持一些子类引入第二个标识的情况。

ORM 使用由Mapper.polymorphic_identity设置的值来确定加载行的多态时行属于哪个类。在上面的示例中,每个代表Employee的行将在其type列中具有值'employee';类似地,每个Engineer将获得值'engineer',每个Manager将获得值'manager'。无论继承映射是否为子类使用不同的连接表(如连接表继承)或所有一个表(如单表继承),这个值都应该被持久化并在查询时对 ORM 可用。Mapper.polymorphic_identity参数也适用于具体表继承,但实际上并没有持久化;有关详细信息,请参阅后面的具体表继承部分。

在多态设置中,最常见的是外键约束建立在与主键本身相同的列或列上,但这并非必需;也可以使与主键不同的列引用到父级的外键。从基表到子类的 JOIN 的构建方式也是可直接自定义的,但这很少是必要的。

使用连接继承映射完成后,针对Employee的查询将返回EmployeeEngineerManager对象的组合。新保存的EngineerManagerEmployee对象在这种情况下将自动填充employee.type列中的正确“识别器”值,如"engineer""manager""employee"

使用连接继承的关系

使用连接表继承完全支持关系。涉及连接继承类的关系应该针对在层次结构中也对应于外键约束的类;在下面的示例中,由于employee表有一个回到company表的外键约束,因此关系被设置在CompanyEmployee之间:

代码语言:javascript复制
from __future__ import annotations

from sqlalchemy.orm import relationship

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee): ...

class Engineer(Employee): ...

如果外键约束在对应于子类的表上,关系应该指向该子类。在下面的示例中,有一个从managercompany的外键约束,因此关系建立在ManagerCompany类之间:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee): ...

在上面的例子中,Manager类将有一个Manager.company属性;Company将有一个Company.managers属性,总是对employeemanager表一起加载。

加载连接继承映射

请参阅编写继承映射的 SELECT 语句部分,了解关于继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。## 单表继承

单表继承在单个表中表示所有子类的所有属性。具有唯一于该类的属性的特定子类将在表中的列中保留它们,如果行引用了不同类型的对象,则这些列将为空。

在层次结构中查询特定子类将呈现为针对基表的 SELECT 查询,其中将包括一个 WHERE 子句,该子句限制行为具有鉴别器列或表达式中存在的特定值或值的行。

单表继承相对于联接表继承具有简单性的优势;查询要高效得多,因为只需要涉及一个表来加载每个表示类的对象。

单表继承配置看起来很像联接表继承,除了只有基类指定了__tablename__。还需要在基表上有一个鉴别器列,以便类可以彼此区分。

即使子类共享所有属性的基表,在使用声明性时,仍然可以在子类上指定mapped_column对象,指示该列仅映射到该子类;mapped_column将应用于相同的基本Table对象:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

请注意,派生类 Manager 和 Engineer 的映射器省略了__tablename__,这表明它们没有自己的映射表。另外,包含了一个带有nullable=Truemapped_column()指令;由于为这些类声明的 Python 类型不包括Optional[],因此该列通常被映射为NOT NULL,这对于该列只期望被填充为那些对应于该特定子类的行并不合适。

使用use_existing_column解决列冲突

请注意,在上一节中,manager_nameengineer_info列被“上移”以应用于Employee.__table__,因为它们在没有自己的表的子类上声明。当两个子类想要指定相同的列时,就会出现一个棘手的情况,如下所示:

代码语言:javascript复制
from datetime import datetime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

上面,在EngineerManager上声明的start_date列将导致错误:

代码语言:javascript复制
sqlalchemy.exc.ArgumentError: Column 'start_date' on class Manager conflicts
with existing column 'employee.start_date'.  If using Declarative,
consider using the use_existing_column parameter of mapped_column() to
resolve conflicts.

上述场景对声明式映射系统存在一种模糊性,可以通过在mapped_column()上使用mapped_column.use_existing_column参数来解决,该参数指示mapped_column()在存在继承的超类时查找并使用已经映射的列,如果已经存在,则映射一个新列:

代码语言:javascript复制
from sqlalchemy import DateTime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

上面的例子中,当 Manager 被映射时,start_date 列已经存在于 Employee 类中,已经由 Engineer 映射提供。mapped_column.use_existing_column参数指示 mapped_column() 应首先在 Employee 的映射 Table 中查找请求的 Column,如果存在,则保持该现有映射。如果不存在,mapped_column()将正常映射列,将其添加为由 Employee 超类引用的 Table 中的列之一。

版本 2.0.0b4 中新增:- 添加了mapped_column.use_existing_column,提供了一种在继承子类上有条件地映射列的 2.0 兼容方法。之前的方法结合了 declared_attr 和对父类 .__table__ 的查找,仍然可以正常工作,但缺乏PEP 484的类型支持。

可以使用类似的概念来定义特定系列的列和/或其他可重复使用的混合类中的映射属性(请参阅使用混合类组合映射层次结构):

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "employee",
    }

class HasStartDate:
    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Engineer(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
单表继承关系

关系完全支持单表继承。配置方式与连接继承的方式相同;外键属性应该在关系的“外键”一侧的同一类上:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

同样,类似于连接继承的情况,我们可以创建涉及特定子类的关系。在查询时,SELECT 语句将包括一个 WHERE 子句,将类选择限制为该子类或子类:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_name: Mapped[str] = mapped_column(nullable=True)

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

在上面的例子中,Manager类将具有Manager.company属性;Company将具有一个Company.managers属性,该属性始终针对employee加载,并附加一个 WHERE 子句,限制行为具有type = 'manager'的行。

使用polymorphic_abstract构建更深层次的层次结构

在 2.0 版本中新增。

在构建任何类型的继承层次结构时,映射类可以包括设置为TrueMapper.polymorphic_abstract参数,这表明该类应该被正常映射,但不希望直接实例化,并且不包括Mapper.polymorphic_identity。然后可以声明这个映射类的子类,这些子类本身可以包括一个Mapper.polymorphic_identity,因此可以正常使用。这允许一系列子类被一个通用的基类引用,该基类在层次结构中被视为“抽象”,在查询和relationship()声明中都是如此。这种用法与在 Declarative 中使用 abstract 属性不同,后者将目标类完全取消映射,因此无法单独作为映射类使用。Mapper.polymorphic_abstract可以应用于层次结构中的任何类或类,包括同时应用于多个级别的类。

例如,假设ManagerPrincipal都被分类到一个超类Executive,而EngineerSysadmin被分类到一个超类TechnologistExecutiveTechnologist都不会被实例化,因此没有Mapper.polymorphic_identity。可以使用Mapper.polymorphic_abstract来配置这些类,如下所示:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Executive(Employee):
  """An executive of the company"""

    executive_background: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Technologist(Employee):
  """An employee who works with technology"""

    competencies: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Manager(Executive):
  """a manager"""

    __mapper_args__ = {"polymorphic_identity": "manager"}

class Principal(Executive):
  """a principal of the company"""

    __mapper_args__ = {"polymorphic_identity": "principal"}

class Engineer(Technologist):
  """an engineer"""

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class SysAdmin(Technologist):
  """a systems administrator"""

    __mapper_args__ = {"polymorphic_identity": "sysadmin"}

在上面的示例中,新的类 TechnologistExecutive 都是普通的映射类,并且还指示要添加到超类的新列 executive_backgroundcompetencies。然而,它们都缺少对 Mapper.polymorphic_identity 的设置;这是因为不希望直接实例化 TechnologistExecutive;我们总是会有 ManagerPrincipalEngineerSysAdmin 中的一个。然而,我们可以查询 PrincipalTechnologist 角色,并且让它们成为relationship()的目标。下面的示例演示了对 Technologist 对象的 SELECT 语句:

代码语言:javascript复制
session.scalars(select(Technologist)).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.competencies
FROM  employee
WHERE  employee.type  IN  (?,  ?)
[...]  ('engineer',  'sysadmin') 

TechnologistExecutive 的抽象映射类也可以作为relationship()映射的目标,就像任何其他映射类一样。我们可以扩展上面的示例,包括 Company,其中包含单独的集合 Company.technologistsCompany.principals

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id = Column(Integer, primary_key=True)

    executives: Mapped[List[Executive]] = relationship()
    technologists: Mapped[List[Technologist]] = relationship()

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)

    # foreign key to "company.id" is added
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))

    # rest of mapping is the same
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
    }

# Executive, Technologist, Manager, Principal, Engineer, SysAdmin
# classes from previous example would follow here unchanged

使用上述映射,我们可以分别跨 Company.technologistsCompany.executives 使用联接和关系加载技术:

代码语言:javascript复制
session.scalars(
    select(Company)
    .join(Company.technologists)
    .where(Technologist.competency.ilike("%java%"))
    .options(selectinload(Company.executives))
).all()
SELECT  company.id
FROM  company  JOIN  employee  ON  company.id  =  employee.company_id  AND  employee.type  IN  (?,  ?)
WHERE  lower(employee.competencies)  LIKE  lower(?)
[...]  ('engineer',  'sysadmin',  '%java%')

SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.executive_background  AS  employee_executive_background
FROM  employee
WHERE  employee.company_id  IN  (?)  AND  employee.type  IN  (?,  ?)
[...]  (1,  'manager',  'principal') 

另请参阅

abstract - 声明参数,允许在继承层次结构中完全取消映射声明的类,同时仍然继承自映射的超类。

加载单表继承映射

单表继承的加载技术与连接表继承的加载技术大部分相同,并且提供了这两种映射类型之间的高度抽象,因此很容易在它们之间切换,以及在单个继承层次结构中混合使用它们(只需从要单继承的子类中省略 __tablename__)。请参阅编写用于继承映射的 SELECT 语句和单表继承映射的 SELECT 语句章节,了解有关继承加载技术的文档,包括在映射器配置时间和查询时间配置要查询的类。## 具体表继承

具体继承将每个子类映射到其自己的独立表,每个表包含产生该类实例所需的所有列。具体继承配置默认以非多态方式查询;对于特定类的查询将仅查询该类的表,并且仅返回该类的实例。通过在映射器内配置特殊的 SELECT,通常会将所有表的 UNION 作为结果来启用具体类的多态加载。

警告

具体表继承比连接或单表继承复杂得多,在使用关系、急加载和多态加载方面功能受限,尤其是与其一起使用时。当以多态方式使用时,会生成非常大的查询,其中包含不会像简单连接那样执行得好的 UNION。强烈建议如果需要关系加载和多态加载的灵活性,尽量使用连接或单表继承。如果不需要多态加载,则每个类完全引用自己的表时可以使用普通的非继承映射。

相比于连接和单表继承在“多态”加载方面更为流畅,具体继承在这方面更为麻烦。因此,当不需要多态加载时,具体继承更为合适。建立涉及具体继承类的关系也更为麻烦。

要将类标记为使用具体继承,需要在__mapper_args__中添加Mapper.concrete参数。这表示对于声明式和映射来说,超类表不应被视为映射的一部分:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))

class Manager(Employee):
    __tablename__ = "manager"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }

应该注意两个关键点:

  • 我们必须在每个子类上明确定义所有列,即使是同名的列也是如此。例如此处的Employee.name不会被复制到ManagerEngineer映射的表中。
  • 虽然EngineerManager类与Employee之间有映射关系,但它们不包括多态加载。这意味着,如果我们查询Employee对象,managerengineer表根本不会被查询。
具体多态加载配置

具体继承的多态加载要求针对应具有多态加载的每个基类配置一个专门的 SELECT。此 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 助手polymorphic_union()构造的 UNION 语句。

如为继承映射编写 SELECT 语句中所讨论的,任何类型的映射继承配置都可以配置为默认从特殊可选中加载,使用Mapper.with_polymorphic参数。当前的公共 API 要求在首次构造Mapper时设置此参数。

但是,在使用 Declarative 的情况下,映射器和被映射的Table同时创建,一旦定义了映射的类。这意味着由于尚未定义对应于子类的Table对象,因此暂时无法提供Mapper.with_polymorphic参数。

有几种可用的策略来解决这个循环,但是 Declarative 提供了处理此问题的助手类ConcreteBaseAbstractConcreteBase

使用ConcreteBase,我们可以几乎以与其他形式的继承映射相同的方式设置我们的具体映射:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

上面,Declarative 在映射器“初始化”时为Employee类设置了多态可选项;这是解决其他依赖映射器的延迟配置步骤。ConcreteBase助手使用polymorphic_union()函数在设置了所有其他类后创建所有具体映射表的 UNION,并然后使用已经存在的基类映射器配置此语句。

在选择时,多态联合会产生这样的查询:

代码语言:javascript复制
session.scalars(select(Employee)).all()
SELECT
  pjoin.id,
  pjoin.name,
  pjoin.type,
  pjoin.manager_data,
  pjoin.engineer_info
FROM  (
  SELECT
  employee.id  AS  id,
  employee.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'employee'  AS  type
  FROM  employee
  UNION  ALL
  SELECT
  manager.id  AS  id,
  manager.name  AS  name,
  manager.manager_data  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'manager'  AS  type
  FROM  manager
  UNION  ALL
  SELECT
  engineer.id  AS  id,
  engineer.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  engineer.engineer_info  AS  engineer_info,
  'engineer'  AS  type
  FROM  engineer
)  AS  pjoin 

上面的 UNION 查询需要为每个子表制造“NULL”列,以适应那些不是特定子类成员的列。

另请参阅

ConcreteBase ### 抽象具体类

到目前为止,所示的具体映射同时显示了子类和基类分别映射到各自的表中。在具体继承用例中,通常基类在数据库中不会被表示,只有子类。换句话说,基类是“抽象的”。

通常,当想要将两个不同的子类映射到各自的表中,并且将基类保持未映射时,这可以很容易地实现。在使用 Declarative 时,只需使用__abstract__指示符声明基类:

代码语言:javascript复制
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(Base):
    __abstract__ = True

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

上面,我们实际上并没有使用 SQLAlchemy 的继承映射功能;我们可以正常加载和持久化ManagerEngineer的实例。然而,当我们需要多态查询时,情况就会发生变化,也就是说,我们希望发出select(Employee)并返回ManagerEngineer实例的集合。这将我们带回到具体继承的领域,我们必须针对Employee构建一个特殊的映射器才能实现这一点。

要修改我们的具体继承示例,以说明一个能够进行多态加载的“抽象”基类,我们将只有一个engineer和一个manager表,没有employee表,但是Employee映射器将直接映射到“多态联合”,而不是在Mapper.with_polymorphic参数中本地指定它。

为了帮助解决这个问题,Declarative 提供了一种名为AbstractConcreteBaseConcreteBase类的变体,它可以自动实现这一点:

代码语言:javascript复制
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(AbstractConcreteBase, Base):
    strict_attrs = True

    name = mapped_column(String(50))

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

Base.registry.configure()

上面,调用了registry.configure()方法,这将触发实际映射Employee类;在配置步骤之前,类没有映射,因为它将从中查询的子表尚未定义。此过程比ConcreteBase更复杂,因为必须延迟基类的整个映射,直到所有子类都已声明。使用像上面的映射,只能持久化ManagerEngineer的实例;对Employee类进行查询将始终产生ManagerEngineer对象。

使用上述映射,可以根据Employee类和在其上本地声明的任何属性生成查询,例如Employee.name

代码语言:javascript复制
>>> stmt = select(Employee).where(Employee.name == "n1")
>>> print(stmt)
SELECT  pjoin.id,  pjoin.name,  pjoin.type,  pjoin.manager_data,  pjoin.engineer_info
FROM  (
  SELECT  engineer.id  AS  id,  engineer.name  AS  name,  engineer.engineer_info  AS  engineer_info,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,  'engineer'  AS  type
  FROM  engineer
  UNION  ALL
  SELECT  manager.id  AS  id,  manager.name  AS  name,  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  manager.manager_data  AS  manager_data,  'manager'  AS  type
  FROM  manager
)  AS  pjoin
WHERE  pjoin.name  =  :name_1 

AbstractConcreteBase.strict_attrs 参数指示 Employee 类应直接映射仅属于 Employee 类的属性,如本例中的 Employee.name 属性。其他属性,如 Manager.manager_dataEngineer.engineer_info,仅存在于其相应的子类中。当未设置 AbstractConcreteBase.strict_attrs 时,所有子类属性(如 Manager.manager_dataEngineer.engineer_info)都将映射到基类 Employee。这是一种传统的使用模式,可能更方便查询,但其效果是所有子类共享整个层次结构的完整属性集;在上述示例中,不使用 AbstractConcreteBase.strict_attrs 将导致生成非实用的 Engineer.manager_nameManager.engineer_info 属性。

新版 2.0 中:新增了 AbstractConcreteBase.strict_attrs 参数到 AbstractConcreteBase 中,以产生更清晰的映射;默认值为 False,以允许继续使用旧版 1.x 版本中的传统映射。

另请参阅

AbstractConcreteBase

经典和半经典具体多态配置

使用 ConcreteBaseAbstractConcreteBase 说明的声明性配置相当于另外两种使用 polymorphic_union() 显式的配置形式。 这些配置形式明确使用 Table 对象,以便首先创建“多态联合”,然后将其应用于映射。 这些示例旨在澄清 polymorphic_union() 函数在映射中的作用。

例如,半经典映射利用声明性,但分别建立 Table 对象:

代码语言:javascript复制
metadata_obj = Base.metadata

employees_table = Table(
    "employee",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

managers_table = Table(
    "manager",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("manager_data", String(50)),
)

engineers_table = Table(
    "engineer",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("engineer_info", String(50)),
)

接下来,使用 polymorphic_union() 生成 UNION:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "employee": employees_table,
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

使用上述 Table 对象,可以使用“半经典”样式生成映射,在此样式中,我们与 __table__ 参数一起使用声明性;我们上面的多态联合通过 __mapper_args__ 传递给 Mapper.with_polymorphic 参数:

代码语言:javascript复制
class Employee(Base):
    __table__ = employee_table
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": ("*", pjoin),
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

或者,可以完全以“经典”风格使用相同的 Table 对象,而不使用声明性。 构造函数与声明性提供的类似,如下所示:

代码语言:javascript复制
class Employee:
    def __init__(self, **kw):
        for k in kw:
            setattr(self, k, kw[k])

class Manager(Employee):
    pass

class Engineer(Employee):
    pass

employee_mapper = mapper_registry.map_imperatively(
    Employee,
    pjoin,
    with_polymorphic=("*", pjoin),
    polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper_registry.map_imperatively(
    Manager,
    managers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="manager",
)
engineer_mapper = mapper_registry.map_imperatively(
    Engineer,
    engineers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="engineer",
)

“抽象” 示例也可以使用“半经典”或“经典”风格进行映射。 不同之处在于,我们不再将“多态联合”应用于 Mapper.with_polymorphic 参数,而是直接将其作为我们最基本的映射器上的映射选择。 半经典映射如下所示:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

class Employee(Base):
    __table__ = pjoin
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": "*",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

在上面的示例中,我们与以前一样使用 polymorphic_union(),只是省略了employee表。

另请参阅

命令式映射 - 有关命令式或“经典”映射的背景信息

具体继承关系

在具体继承场景中,映射关系是具有挑战性的,因为不同的类不共享一个表。如果关系只涉及特定类,例如我们之前示例中的CompanyManager之间的关系,那么不需要特殊步骤,因为这只是两个相关表。

然而,如果Company要与Employee建立一对多关系,表示集合可能包括EngineerManager对象,这意味着Employee必须具有多态加载能力,并且要关联的每个表都必须有一个外键返回到company表。这种配置的示例如下:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

具体继承和关系的下一个复杂性涉及当我们希望EmployeeManagerEngineer中的一个或全部自身引用Company时。对于这种情况,SQLAlchemy 在 Employee 上放置一个与 Company 相关的 relationship() 时,在实例级别执行时不适用于 ManagerEngineer 类,而必须对每个类应用一个不同的 relationship()。为了实现三个独立关系的双向行为,这些关系作为 Company.employees 的相反关系,使用了 relationship.back_populates 参数:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee", back_populates="company")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

上述限制与当前实现相关,包括具体继承类不共享超类的任何属性,因此需要设置不同的关系。

加载具体继承映射

具体继承加载选项有限;通常,如果在映射器上配置了多态加载,使用其中一个声明性具体混合类,就不能在当前 SQLAlchemy 版本中在查询时修改它。通常,with_polymorphic() 函数应该能够覆盖具体使用的加载样式,但由于当前限制,这还不受支持。 ## 连接表继承

在连接表继承中,类层次结构中的每个类都由一个不同的表表示。在层次结构中查询特定子类将作为 SQL JOIN 渲染其继承路径上的所有表。如果查询的类是基类,则将查询基表,同时可以选择包括其他表或允许特定于子表的属性稍后加载。

在所有情况下,给定行的最终实例化类由基类上定义的鉴别器列或 SQL 表达式确定,该列将产生与特定子类关联的标量值。

连接继承层次结构中的基类将配置具有指示多态鉴别器列的额外参数,以及可选的基类自身的多态标识符:

代码语言:javascript复制
from sqlalchemy import ForeignKey
from sqlalchemy.orm import DeclarativeBase
from sqlalchemy.orm import Mapped
from sqlalchemy.orm import mapped_column

class Base(DeclarativeBase):
    pass

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

    def __repr__(self):
        return f"{self.__class__.__name__}({self.name!r})"

在上面的例子中,鉴别器是type列,可以使用Mapper.polymorphic_on参数进行配置。该参数接受一个基于列的表达式,可以指定为要使用的映射属性的字符串名称,也可以指定为列表达式对象,如Columnmapped_column()构造。

鉴别器列将存储一个值,该值指示行中表示的对象类型。该列可以是任何数据类型,但字符串和整数最常见。为数据库中的特定行应用于此列的实际数据值是使用Mapper.polymorphic_identity参数指定的,如下所述。

虽然多态鉴别器表达式不是严格必需的,但如果需要多态加载,则需要。在基表上建立一个列是实现此目的的最简单方法,但是非常复杂的继承映射可能会使用 SQL 表达式,例如 CASE 表达式,作为多态鉴别器。

注意

目前,整个继承层次结构仅可以配置一个鉴别器列或 SQL 表达式,通常在层次结构中最基本的类上。目前不支持“级联”多态鉴别器表达式。

我们接下来定义EngineerManager作为Employee的子类。每个子类包含代表其所代表子类的唯一属性的列。每个表还必须包含主键列(或列)以及对父表的外键引用:

代码语言:javascript复制
class Engineer(Employee):
    __tablename__ = "engineer"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    engineer_name: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

在上面的示例中,每个映射在其映射器参数中指定了Mapper.polymorphic_identity参数。此值填充了由基本映射器上建立的Mapper.polymorphic_on参数指定的列。Mapper.polymorphic_identity参数应该对整个层次结构中的每个映射类是唯一的,并且每个映射类只应有一个“标识”;如上所述,不支持一些子类引入第二个标识的“级联”标识。

ORM 使用Mapper.polymorphic_identity设置的值来确定加载行时行属于哪个类。在上面的示例中,每个代表Employee的行在其type列中将有值'employee';同样,每个Engineer将获得值'engineer',每个Manager将获得值'manager'。无论继承映射使用不同的联接表作为子类(如联合表继承)还是所有一个表作为单表继承,这个值都应该被持久化并在查询时对 ORM 可用。Mapper.polymorphic_identity参数也适用于具体表继承,但实际上并没有被持久化;有关详细信息,请参阅后面的具体表继承部分。

在多态设置中,最常见的是外键约束建立在与主键本身相同的列或列上,但这并非必需;一个与主键不同的列也可以通过外键指向父类。从基本表到子类构建 JOIN 的方式也是可以直接自定义的,但这很少是必要的。

完成联合继承映射后,针对Employee的查询将返回EmployeeEngineerManager对象的组合。新保存的EngineerManagerEmployee对象将自动填充employee.type列,此时正确的“鉴别器”值为"engineer""manager""employee"

具有联合继承关系

与联合表继承完全支持关系。涉及联合继承类的关系应该针对与外键约束对应的层次结构中的类;在下面的示例中,由于employee表有一个指向company表的外键约束,关系被建立在CompanyEmployee之间:

代码语言:javascript复制
from __future__ import annotations

from sqlalchemy.orm import relationship

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee): ...

class Engineer(Employee): ...

如果外键约束在对应于子类的表上,则关系应该指向该子类。在下面的示例中,从managercompany有一个外键约束,因此建立了ManagerCompany类之间的关系:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee): ...

在上面,Manager类将具有Manager.company属性;Company将具有Company.managers属性,总是针对employeemanager表一起加载的连接进行加载。

加载连接继承映射

请参阅编写用于继承映射的 SELECT 语句部分,了解继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。

具有连接继承的关系

与连接表继承完全支持关系。涉及连接继承类的关系应该指向与外键约束对应的层次结构中的类;在下面的示例中,由于employee表有一个指向company表的外键约束,因此在CompanyEmployee之间建立了关系:

代码语言:javascript复制
from __future__ import annotations

from sqlalchemy.orm import relationship

class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee): ...

class Engineer(Employee): ...

如果外键约束在对应于子类的表上,则关系应该指向该子类。在下面的示例中,从managercompany有一个外键约束,因此建立了ManagerCompany类之间的关系:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    __tablename__ = "manager"
    id: Mapped[int] = mapped_column(ForeignKey("employee.id"), primary_key=True)
    manager_name: Mapped[str]

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee): ...

在上面,Manager类将具有Manager.company属性;Company将具有Company.managers属性,总是针对employeemanager表一起加载的连接进行加载。

加载连接继承映射

请参阅编写用于继承映射的 SELECT 语句部分,了解继承加载技术的背景,包括在映射器配置时间和查询时间配置要查询的表。

单表继承

单表继承将所有子类的所有属性表示为单个表中的内容。具有特定类别属性的特定子类将在表中的列中保留它们,如果行引用不同类型的对象,则列中将为空。

在层次结构中查询特定子类将呈现为针对基表的 SELECT,其中将包括一个 WHERE 子句,该子句将限制行为具有鉴别器列或表达式中存在的特定值或值。

单表继承相对于连接表继承具有简单性的优势;查询效率更高,因为只需要涉及一个表来加载每个表示类的对象。

单表继承配置看起来很像连接表继承,只是基类指定了__tablename__。基表还需要一个鉴别器列,以便类之间可以区分开来。

即使子类共享所有属性的基本表,当使用 Declarative 时,仍然可以在子类上指定 mapped_column 对象,指示该列仅映射到该子类;mapped_column 将应用于相同的基本 Table 对象:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

注意到派生类 Manager 和 Engineer 的映射器省略了 __tablename__,表明它们没有自己的映射表。此外,还包括一个带有 nullable=Truemapped_column() 指令;由于为这些类声明的 Python 类型不包括 Optional[],该列通常会被映射为 NOT NULL,这对于只期望为对应于特定子类的那些行填充的列来说是不合适的。

使用 use_existing_column 解决列冲突

注意在前一节中,manager_nameengineer_info 列被“上移”应用到 Employee.__table__,因为它们在没有自己表的子类上声明。当两个子类想要指定相同列时,就会出现一个棘手的情况,如下所示:

代码语言:javascript复制
from datetime import datetime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

上面,在 EngineerManager 上声明的 start_date 列将导致错误:

代码语言:javascript复制
sqlalchemy.exc.ArgumentError: Column 'start_date' on class Manager conflicts
with existing column 'employee.start_date'.  If using Declarative,
consider using the use_existing_column parameter of mapped_column() to
resolve conflicts.

上述情况对 Declarative 映射系统提出了一个模棱两可的问题,可以通过在 mapped_column() 上使用 mapped_column.use_existing_column 参数来解决,该参数指示 mapped_column() 查找并使用已经映射的继承超类上的列,如果已经存在,否则映射一个新列:

代码语言:javascript复制
from sqlalchemy import DateTime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

在上面的例子中,当Manager被映射时,start_date列已经存在于Employee类上,已经由Engineer映射提供。mapped_column.use_existing_column参数指示mapped_column()应该首先在Employee的映射Table上查找请求的Column,如果存在,则保持该现有映射。如果不存在,mapped_column()将正常映射该列,将其添加为Employee超类引用的Table中的列之一。

2.0.0b4 版本中新增:- 添加了mapped_column.use_existing_column,提供了一种符合 2.0 版本的方式来有条件地映射继承子类上的列。之前的方法结合了declared_attr和对父类.__table__的查找仍然有效,但缺乏PEP 484类型支持。

类似的概念可以与混合类一起使用(参见使用混合类组合映射层次结构)来定义一系列特定的列和/或其他可重用混合类中的映射属性:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "employee",
    }

class HasStartDate:
    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Engineer(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
与单表继承的关系

与单表继承完全支持关系。配置方式与连接继承相同;外键属性应该在与关系的“外键”一侧相同的类上:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

此外,与连接继承的情况类似,我们可以创建涉及特定子类的关系。在查询时,SELECT 语句将包含一个 WHERE 子句,将类选择限制为该子类或子类:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_name: Mapped[str] = mapped_column(nullable=True)

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

在上面的例子中,Manager类将具有Manager.company属性;Company将具有Company.managers属性,始终针对具有额外 WHERE 子句的employee加载,该子句将行限制为type = 'manager'的行。

使用polymorphic_abstract构建更深层次的层次结构

2.0 版本中新增。

在构建任何继承层次结构时,一个映射类可以设置Mapper.polymorphic_abstract参数为True,这表示该类应该被正常映射,但不希望直接实例化,并且不包含Mapper.polymorphic_identity。然后可以声明这个映射类的子类,这些子类可以包含Mapper.polymorphic_identity,因此可以被正常使用。这允许一系列子类被一个公共基类引用,该基类在层次结构中被认为是“抽象的”,无论是在查询中还是在relationship()声明中。这种用法与在 Declarative 中使用 abstract 属性的用法不同,后者将目标类完全未映射,因此不能单独作为映射类使用。Mapper.polymorphic_abstract可以应用于层次结构中的任何类或类,包括同时在多个级别上应用。

举例来说,假设ManagerPrincipal都被分类到一个超类Executive下,而EngineerSysadmin被分类到一个超类Technologist下。ExecutiveTechnologist都不会被实例化,因此没有Mapper.polymorphic_identity。这些类可以通过Mapper.polymorphic_abstract进行配置如下:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Executive(Employee):
  """An executive of the company"""

    executive_background: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Technologist(Employee):
  """An employee who works with technology"""

    competencies: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Manager(Executive):
  """a manager"""

    __mapper_args__ = {"polymorphic_identity": "manager"}

class Principal(Executive):
  """a principal of the company"""

    __mapper_args__ = {"polymorphic_identity": "principal"}

class Engineer(Technologist):
  """an engineer"""

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class SysAdmin(Technologist):
  """a systems administrator"""

    __mapper_args__ = {"polymorphic_identity": "sysadmin"}

在上面的例子中,新的类TechnologistExecutive都是普通的映射类,并且指示要添加到超类中的新列executive_backgroundcompetencies。然而,它们都缺少Mapper.polymorphic_identity的设置;这是因为不希望直接实例化TechnologistExecutive;我们总是会有ManagerPrincipalEngineerSysAdmin中的一个。但是我们可以查询PrincipalTechnologist角色,并且让它们成为relationship()的目标。下面的示例演示了针对Technologist对象的 SELECT 语句:

代码语言:javascript复制
session.scalars(select(Technologist)).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.competencies
FROM  employee
WHERE  employee.type  IN  (?,  ?)
[...]  ('engineer',  'sysadmin') 

TechnologistExecutive抽象映射类也可以成为relationship()映射的目标,就像任何其他映射类一样。我们可以扩展上面的例子,包括Company,有单独的集合Company.technologistsCompany.principals

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id = Column(Integer, primary_key=True)

    executives: Mapped[List[Executive]] = relationship()
    technologists: Mapped[List[Technologist]] = relationship()

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)

    # foreign key to "company.id" is added
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))

    # rest of mapping is the same
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
    }

# Executive, Technologist, Manager, Principal, Engineer, SysAdmin
# classes from previous example would follow here unchanged

使用上述映射,我们可以分别跨Company.technologistsCompany.executives使用连接和关系加载技术:

代码语言:javascript复制
session.scalars(
    select(Company)
    .join(Company.technologists)
    .where(Technologist.competency.ilike("%java%"))
    .options(selectinload(Company.executives))
).all()
SELECT  company.id
FROM  company  JOIN  employee  ON  company.id  =  employee.company_id  AND  employee.type  IN  (?,  ?)
WHERE  lower(employee.competencies)  LIKE  lower(?)
[...]  ('engineer',  'sysadmin',  '%java%')

SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.executive_background  AS  employee_executive_background
FROM  employee
WHERE  employee.company_id  IN  (?)  AND  employee.type  IN  (?,  ?)
[...]  (1,  'manager',  'principal') 

另请参阅

abstract - 声明性参数,允许声明性类在层次结构中完全取消映射,同时仍然从映射的超类扩展。

加载单一继承映射

单表继承的加载技术与联接表继承的加载技术基本相同,并且在这两种映射类型之间提供了高度的抽象,使得很容易在它们之间进行切换,以及在单个层次结构中混合使用它们(只需从要单继承的子类中省略 __tablename__)。请参阅 编写继承映射的 SELECT 语句 和 单一继承映射的 SELECT 语句 部分,了解有关继承加载技术的文档,包括在映射器配置时间和查询时间配置要查询的类。

使用 use_existing_column 解决列冲突

在上一节中注意到,manager_nameengineer_info列被“上移”,应用于Employee.__table__,因为它们在没有自己的表的子类上声明。当两个子类想要指定相同列时会出现棘手的情况,如下所示:

代码语言:javascript复制
from datetime import datetime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
    start_date: Mapped[datetime] = mapped_column(nullable=True)

在上述代码中,同时在EngineerManager上声明的start_date列将导致错误:

代码语言:javascript复制
sqlalchemy.exc.ArgumentError: Column 'start_date' on class Manager conflicts
with existing column 'employee.start_date'.  If using Declarative,
consider using the use_existing_column parameter of mapped_column() to
resolve conflicts.

上述情景对声明性映射系统存在一种模糊性,可以通过在mapped_column()上使用mapped_column.use_existing_column参数来解决,该参数指示mapped_column()查找继承的超类,并使用已经存在的列,如果已经存在,则映射新列:

代码语言:javascript复制
from sqlalchemy import DateTime

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Manager(Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

在上文中,当 Manager 被映射时,start_date 列已经存在于 Employee 类中,已经由之前的 Engineer 映射提供。mapped_column.use_existing_column 参数指示给 mapped_column(),它应该首先查找映射到 Employee 的映射 Table 上的请求的 Column,如果存在,则保持该现有映射。如果不存在,mapped_column() 将正常映射列,将其添加为 Employee 超类引用的 Table 中的列之一。

新版本 2.0.0b4 中新增:- 添加了 mapped_column.use_existing_column,它提供了一个与 2.0 兼容的方法,以条件地映射继承子类上的列。先前的方法结合了 declared_attr 与对父类 .__table__ 的查找,仍然有效,但缺少了 PEP 484 类型支持。

一个类似的概念可以与 mixin 类一起使用(参见 Composing Mapped Hierarchies with Mixins)来定义来自可重用 mixin 类的特定系列列和/或其他映射属性:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": type,
        "polymorphic_identity": "employee",
    }

class HasStartDate:
    start_date: Mapped[datetime] = mapped_column(
        nullable=True, use_existing_column=True
    )

class Engineer(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

class Manager(HasStartDate, Employee):
    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }
单表继承的关系

关系在单表继承中得到充分支持。配置方式与联接继承的方式相同;外键属性应位于与关系的“外部”一侧相同的类上:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    employees: Mapped[List[Employee]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_data: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

类似于联接继承的情况,我们也可以创建涉及特定子类的关系。当查询时,SELECT 语句将包含一个 WHERE 子句,将类的选择限制为该子类或子类:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    managers: Mapped[List[Manager]] = relationship(back_populates="company")

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Manager(Employee):
    manager_name: Mapped[str] = mapped_column(nullable=True)

    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))
    company: Mapped[Company] = relationship(back_populates="managers")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
    }

class Engineer(Employee):
    engineer_info: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
    }

上文中,Manager 类将具有一个 Manager.company 属性;Company 将具有一个 Company.managers 属性,该属性始终加载针对具有附加 WHERE 子句的 employee,限制行为具有 type = 'manager' 的行。

使用 polymorphic_abstract 构建更深层次的层次结构

新版本 2.0 中新增。

在构建任何类型的继承层次结构时,映射类可以包含设置为TrueMapper.polymorphic_abstract参数,表示该类应该正常映射,但不期望直接实例化,并且不包括Mapper.polymorphic_identity。然后可以声明这个映射类的子类,这些子类本身可以包含Mapper.polymorphic_identity,因此可以正常使用。这允许一系列子类被一个被认为是层次结构内“抽象”的公共基类引用,无论是在查询中还是在relationship()声明中。这种用法与在 Declarative 中使用 abstract 属性的用法不同,后者使目标类完全未映射,因此不能作为一个映射类单独使用。Mapper.polymorphic_abstract可以应用于层次结构中的任何类或类,包括一次在多个级别上。

举个例子,假设要将ManagerPrincipal都归类到一个超类Executive下,而EngineerSysadmin则归类到一个超类Technologist下。ExecutiveTechnologist都不会被实例化,因此没有Mapper.polymorphic_identity。可以使用Mapper.polymorphic_abstract来配置这些类,如下所示:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "polymorphic_on": "type",
    }

class Executive(Employee):
  """An executive of the company"""

    executive_background: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Technologist(Employee):
  """An employee who works with technology"""

    competencies: Mapped[str] = mapped_column(nullable=True)

    __mapper_args__ = {"polymorphic_abstract": True}

class Manager(Executive):
  """a manager"""

    __mapper_args__ = {"polymorphic_identity": "manager"}

class Principal(Executive):
  """a principal of the company"""

    __mapper_args__ = {"polymorphic_identity": "principal"}

class Engineer(Technologist):
  """an engineer"""

    __mapper_args__ = {"polymorphic_identity": "engineer"}

class SysAdmin(Technologist):
  """a systems administrator"""

    __mapper_args__ = {"polymorphic_identity": "sysadmin"}

在上面的示例中,新类TechnologistExecutive都是普通的映射类,并指示要添加到超类中的新列executive_backgroundcompetencies。然而,它们都缺少Mapper.polymorphic_identity的设置;这是因为不期望直接实例化TechnologistExecutive;我们总是会有ManagerPrincipalEngineerSysAdmin中的一个。然而,我们可以查询PrincipalTechnologist角色,并使它们成为relationship()的目标。下面的示例演示了用于Technologist对象的 SELECT 语句:

代码语言:javascript复制
session.scalars(select(Technologist)).all()
SELECT  employee.id,  employee.name,  employee.type,  employee.competencies
FROM  employee
WHERE  employee.type  IN  (?,  ?)
[...]  ('engineer',  'sysadmin') 

抽象映射的TechnologistExecutive抽象映射类也可以成为relationship()映射的目标,就像任何其他映射类一样。我们可以扩展上述示例以包括Company,并分别添加Company.technologistsCompany.principals两个集合:

代码语言:javascript复制
class Company(Base):
    __tablename__ = "company"
    id = Column(Integer, primary_key=True)

    executives: Mapped[List[Executive]] = relationship()
    technologists: Mapped[List[Technologist]] = relationship()

class Employee(Base):
    __tablename__ = "employee"
    id: Mapped[int] = mapped_column(primary_key=True)

    # foreign key to "company.id" is added
    company_id: Mapped[int] = mapped_column(ForeignKey("company.id"))

    # rest of mapping is the same
    name: Mapped[str]
    type: Mapped[str]

    __mapper_args__ = {
        "polymorphic_on": "type",
    }

# Executive, Technologist, Manager, Principal, Engineer, SysAdmin
# classes from previous example would follow here unchanged

使用上述映射,我们可以分别在Company.technologistsCompany.executives之间使用连接和关系加载技术:

代码语言:javascript复制
session.scalars(
    select(Company)
    .join(Company.technologists)
    .where(Technologist.competency.ilike("%java%"))
    .options(selectinload(Company.executives))
).all()
SELECT  company.id
FROM  company  JOIN  employee  ON  company.id  =  employee.company_id  AND  employee.type  IN  (?,  ?)
WHERE  lower(employee.competencies)  LIKE  lower(?)
[...]  ('engineer',  'sysadmin',  '%java%')

SELECT  employee.company_id  AS  employee_company_id,  employee.id  AS  employee_id,
employee.name  AS  employee_name,  employee.type  AS  employee_type,
employee.executive_background  AS  employee_executive_background
FROM  employee
WHERE  employee.company_id  IN  (?)  AND  employee.type  IN  (?,  ?)
[...]  (1,  'manager',  'principal') 

另请参见

abstract - 声明性参数,允许在层次结构中完全取消映射 Declarative 类,同时仍从映射的超类扩展。

加载单继承映射

单表继承的加载技术大部分与用于连接表继承的技术相同,并且在这两种映射类型之间提供了很高程度的抽象,因此很容易在它们之间进行切换以及在单个层次结构中混合使用它们(只需从要单继承的子类中省略__tablename__)。请参阅编写继承映射的 SELECT 语句和单继承映射的 SELECT 语句章节,了解有关继承加载技术的文档,包括在映射器配置时间和查询时间配置要查询的类。

具体表继承

具体表继承将每个子类映射到其自己的独立表格,每个表格包含产生该类实例所需的所有列。具体继承配置默认情况下进行非多态查询;对于特定类的查询只会查询该类的表格,并且只返回该类的实例。具体类的多态加载通过在映射器内配置一个特殊的 SELECT 来启用,该 SELECT 通常被生成为所有表的 UNION。

警告

具体表继承比连接或单表继承更加复杂,在功能上更加受限,特别是在使用关系、急加载和多态加载方面。当以多态方式使用时,会产生非常庞大的查询,其中包含的 UNION 操作不会像简单的连接那样执行良好。强烈建议如果需要灵活性的关系加载和多态加载,尽可能使用连接或单表继承。如果不需要多态加载,则可以使用普通的非继承映射,如果每个类都完全引用其自己的表格。

虽然联接和单表继承在“多态”加载方面很流畅,但在具体继承中却是一种更笨拙的事情。因此,当不需要多态加载时,具体继承更为适用。建立涉及具体继承类的关系也更加麻烦。

要将类建立为使用具体继承,请在__mapper_args__中添加Mapper.concrete参数。这既表示对声明式以及映射,超类表不应被视为映射的一部分:

代码语言:javascript复制
class Employee(Base):
    __tablename__ = "employee"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))

class Manager(Employee):
    __tablename__ = "manager"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"

    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(50))

    __mapper_args__ = {
        "concrete": True,
    }

有两个关键点需要注意:

  • 我们必须在每个子类上显式定义所有列,甚至是同名的列。例如,此处的Employee.name不会被复制到由我们映射的ManagerEngineer表中。
  • 虽然EngineerManager类在与Employee的继承关系中被映射,但它们仍然不包括多态加载。也就是说,如果我们查询Employee对象,managerengineer表根本不会被查询。
具体多态加载配置

具有具体继承的多态加载要求针对应该具有多态加载的每个基类配置专门的 SELECT。此 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 辅助程序polymorphic_union()构造的 UNION 语句。

如编写继承映射的 SELECT 语句所述,任何类型的映射器继承配置都可以使用Mapper.with_polymorphic参数默认配置从特殊的可选项中加载。当前公共 API 要求在首次构造Mapper时设置此参数。

但是,在声明式的情况下,映射器和被映射的Table同时创建,即在定义映射类的那一刻。这意味着暂时无法提供Mapper.with_polymorphic参数,因为子类对应的Table对象尚未定义。

有一些可用的策略来解决这个循环,然而声明性提供了帮助类ConcreteBaseAbstractConcreteBase,它们在幕后处理这个问题。

使用ConcreteBase,我们几乎可以以与其他形式的继承映射相同的方式设置我们的具体映射:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

在上述示例中,声明性在映射器“初始化”时为Employee类设置了多态可选择项;这是解析其他依赖映射器的映射器的后配置步骤。ConcreteBase 帮助程序使用polymorphic_union()函数在设置了其他所有类之后创建所有具体映射表的 UNION,然后使用已经存在的基类映射器配置此语句。

在选择时,多态联合产生这样的查询:

代码语言:javascript复制
session.scalars(select(Employee)).all()
SELECT
  pjoin.id,
  pjoin.name,
  pjoin.type,
  pjoin.manager_data,
  pjoin.engineer_info
FROM  (
  SELECT
  employee.id  AS  id,
  employee.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'employee'  AS  type
  FROM  employee
  UNION  ALL
  SELECT
  manager.id  AS  id,
  manager.name  AS  name,
  manager.manager_data  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'manager'  AS  type
  FROM  manager
  UNION  ALL
  SELECT
  engineer.id  AS  id,
  engineer.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  engineer.engineer_info  AS  engineer_info,
  'engineer'  AS  type
  FROM  engineer
)  AS  pjoin 

上述 UNION 查询需要为每个子表制造“NULL”列,以容纳那些不属于特定子类的列。

另请参阅

ConcreteBase ### 抽象具体类

到目前为止,所示的具体映射同时显示了子类和基类映射到各自的表中。在具体继承用例中,常见的是基类在数据库中没有表示,只有子类。换句话说,基类是“抽象的”。

通常,当一个人想要将两个不同的子类映射到各自的表中,并且保留基类未映射时,这可以非常容易地实现。在使用声明性时,只需使用__abstract__指示器声明基类:

代码语言:javascript复制
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(Base):
    __abstract__ = True

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

在上述示例中,我们实际上没有利用 SQLAlchemy 的继承映射功能;我们可以正常加载和持久化ManagerEngineer的实例。然而,当我们需要多态地查询时,也就是说,我们想要发出select(Employee)并返回ManagerEngineer实例的集合时,情况就会发生变化。这让我们重新进入具体继承的领域,我们必须针对Employee构建一个特殊的映射器才能实现这一点。

要修改我们的具体继承示例以说明一个能够进行多态加载的“抽象”基类,我们将只有一个 engineer 和一个 manager 表,没有 employee 表,但是 Employee 映射器将直接映射到“多态联合”,而不是在 Mapper.with_polymorphic 参数中本地指定它。

为了帮助实现这一点,声明性提供了一个名为 AbstractConcreteBaseConcreteBase 类的变体,它可以自动实现这一点:

代码语言:javascript复制
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(AbstractConcreteBase, Base):
    strict_attrs = True

    name = mapped_column(String(50))

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

Base.registry.configure()

在上面的代码中,调用了 registry.configure() 方法,这将触发 Employee 类实际上被映射;在配置步骤之前,由于尚未定义将从中查询的子表,因此该类没有映射。这个过程比 ConcreteBase 更复杂,因为整个基类的映射必须延迟到所有子类都声明完毕。有了上述的映射,只有 ManagerEngineer 的实例才能被持久化;对 Employee 类进行查询将始终产生 ManagerEngineer 对象。

使用上述的映射,可以根据 Employee 类和在其上局部声明的任何属性生成查询,例如 Employee.name

代码语言:javascript复制
>>> stmt = select(Employee).where(Employee.name == "n1")
>>> print(stmt)
SELECT  pjoin.id,  pjoin.name,  pjoin.type,  pjoin.manager_data,  pjoin.engineer_info
FROM  (
  SELECT  engineer.id  AS  id,  engineer.name  AS  name,  engineer.engineer_info  AS  engineer_info,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,  'engineer'  AS  type
  FROM  engineer
  UNION  ALL
  SELECT  manager.id  AS  id,  manager.name  AS  name,  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  manager.manager_data  AS  manager_data,  'manager'  AS  type
  FROM  manager
)  AS  pjoin
WHERE  pjoin.name  =  :name_1 

AbstractConcreteBase.strict_attrs 参数表示 Employee 类应直接映射仅属于 Employee 类本身的属性,例如 Employee.name 属性。其他属性如 Manager.manager_dataEngineer.engineer_info 仅存在于它们各自的子类中。当未设置 AbstractConcreteBase.strict_attrs 时,所有子类的属性如 Manager.manager_dataEngineer.engineer_info 都会映射到基类 Employee 中。这是一种遗留模式,对于查询可能更方便,但会导致所有子类共享整个层次结构的完整属性集;在上述示例中,如果不使用 AbstractConcreteBase.strict_attrs,将会生成无用的 Engineer.manager_nameManager.engineer_info 属性。

新版本 2.0 中:增加了 AbstractConcreteBase.strict_attrs 参数到 AbstractConcreteBase,以生成更清晰的映射;默认值为 False,以允许遗留映射在 1.x 版本中继续正常工作。

另请参阅

AbstractConcreteBase

经典和半经典具体多态配置

通过ConcreteBaseAbstractConcreteBase说明的声明性配置等同于另外两种明确使用polymorphic_union()的配置形式。这些配置形式明确使用Table对象,以便首先创建“多态联合”,然后应用到映射中。这些例子旨在澄清polymorphic_union()函数在映射中的作用。

例如,半经典映射利用了声明性,但是单独建立了Table对象:

代码语言:javascript复制
metadata_obj = Base.metadata

employees_table = Table(
    "employee",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

managers_table = Table(
    "manager",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("manager_data", String(50)),
)

engineers_table = Table(
    "engineer",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("engineer_info", String(50)),
)

接下来,使用polymorphic_union()生成 UNION:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "employee": employees_table,
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

使用上述Table对象,可以使用“半经典”风格生成映射,在这种风格中,我们将声明性与__table__参数结合使用;我们上面的多态联合通过__mapper_args__传递给Mapper.with_polymorphic参数:

代码语言:javascript复制
class Employee(Base):
    __table__ = employee_table
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": ("*", pjoin),
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

或者,可以完全以“经典”风格使用相同的Table对象,而不使用声明性。与声明性提供的类似的构造函数如下所示:

代码语言:javascript复制
class Employee:
    def __init__(self, **kw):
        for k in kw:
            setattr(self, k, kw[k])

class Manager(Employee):
    pass

class Engineer(Employee):
    pass

employee_mapper = mapper_registry.map_imperatively(
    Employee,
    pjoin,
    with_polymorphic=("*", pjoin),
    polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper_registry.map_imperatively(
    Manager,
    managers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="manager",
)
engineer_mapper = mapper_registry.map_imperatively(
    Engineer,
    engineers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="engineer",
)

“抽象”示例也可以使用“半经典”或“经典”风格进行映射。不同之处在于,我们将“多态联合”应用于Mapper.with_polymorphic参数的方式,而是将其直接应用于我们基本映射器上的映射可选项。半经典映射如下所示:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

class Employee(Base):
    __table__ = pjoin
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": "*",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

在上面的例子中,我们像以前一样使用polymorphic_union(),只是省略了employee表。

另请参阅

命令式映射 - 命令式或“经典”映射的背景信息

具体继承的关系

在具体继承的情况下,映射关系是具有挑战性的,因为不同的类不共享表格。如果关系仅涉及特定类,例如在我们先前的示例中CompanyManager之间的关系,那么不需要特殊步骤,因为这只是两个相关的表。

但是,如果Company要对Employee有一对多的关系,表明集合可能包含EngineerManager对象,那么这意味着Employee必须具有多态加载功能,并且要与company表关联的每个表都必须有一个外键。这种配置的示例如下:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

具体继承和关系的下一个复杂性涉及当我们希望EmployeeManagerEngineer中的一个或全部自己参考Company时。对于这种情况,SQLAlchemy 具有特殊行为,即在Employee上放置到Companyrelationship()在实例级别时不适用ManagerEngineer类。相反,必须对每个类应用不同的relationship()。为了在三个单独的关系中实现与Company.employees相反的双向行为,使用了relationship.back_populates参数:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee", back_populates="company")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

上述限制与当前的实现相关,其中具体继承类不共享超类的任何属性,因此需要设置不同的关系。

加载具体继承映射

具体继承的加载选项有限;一般来说,如果使用声明性具体混合类型之一在映射器上配置多态加载,那么在当前 SQLAlchemy 版本中无法在查询时修改它。通常,with_polymorphic()函数应该能够覆盖具体使用的加载方式,但由于当前的限制,这尚不受支持。

具体多态加载配置

具有具体继承的多态加载要求针对应该具有多态加载的每个基类配置一个专用的 SELECT。这个 SELECT 需要能够单独访问所有映射的表,并且通常是使用 SQLAlchemy 助手polymorphic_union()构造的 UNION 语句。

如为继承映射编写 SELECT 语句所讨论的,任何类型的映射器继承配置都可以使用Mapper.with_polymorphic参数默认配置从特殊的可选项加载。当前的公共 API 要求在首次构造Mapper时设置此参数。

但是,在声明式编程中,映射器和被映射的Table同时创建,即在定义映射类的时候。这意味着Mapper.with_polymorphic参数还不能提供,因为对应于子类的Table对象尚未定义。

有几种策略可用于解决这种循环,然而,声明式提供了处理此问题的助手类ConcreteBaseAbstractConcreteBase

使用ConcreteBase,我们可以几乎与其他形式的继承映射方式相同地设置我们的具体映射:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

在上述情况下,声明式在映射器“初始化”时为Employee类设置多态可选项;这是为解析其他依赖映射器而进行的映射器的后期配置步骤。ConcreteBase助手使用polymorphic_union()函数在设置完所有其他类之后创建所有具体映射表的联合,然后使用已经存在的基类映射器配置此语句。

在选择时,多态联合产生类似这样的查询:

代码语言:javascript复制
session.scalars(select(Employee)).all()
SELECT
  pjoin.id,
  pjoin.name,
  pjoin.type,
  pjoin.manager_data,
  pjoin.engineer_info
FROM  (
  SELECT
  employee.id  AS  id,
  employee.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'employee'  AS  type
  FROM  employee
  UNION  ALL
  SELECT
  manager.id  AS  id,
  manager.name  AS  name,
  manager.manager_data  AS  manager_data,
  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  'manager'  AS  type
  FROM  manager
  UNION  ALL
  SELECT
  engineer.id  AS  id,
  engineer.name  AS  name,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,
  engineer.engineer_info  AS  engineer_info,
  'engineer'  AS  type
  FROM  engineer
)  AS  pjoin 

上述的 UNION 查询需要为每个子表制造“NULL”列,以适应那些不是特定子类成员的列。

另请参见

ConcreteBase

抽象具体类

到目前为止,所示的具体映射显示了子类以及基类映射到单独的表中。在具体继承用例中,常见的情况是基类在数据库中不表示,只有子类。换句话说,基类是“抽象的”。

通常,当一个人想要将两个不同的子类映射到单独的表中,并且保留基类未映射时,这可以非常容易地实现。当使用声明式时,只需使用__abstract__指示符声明基类:

代码语言:javascript复制
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(Base):
    __abstract__ = True

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

在上述代码中,我们实际上没有使用 SQLAlchemy 的继承映射功能;我们可以正常加载和持久化ManagerEngineer的实例。然而,当我们需要进行多态查询时,情况就会发生变化,也就是说,我们希望发出select(Employee)并返回一组ManagerEngineer实例。这将我们带回到具体继承的领域,我们必须构建一个针对Employee的特殊映射器才能实现这一点。

要修改我们的具体继承示例,以说明能够进行多态加载的“抽象”基类,我们将只有一个engineer和一个manager表,而没有employee表,但Employee映射器将直接映射到“多态联合”,而不是将其局部指定给Mapper.with_polymorphic参数。

为了帮助解决这个问题,Declarative 提供了一种名为AbstractConcreteBaseConcreteBase类的变体,可以自动实现这一点:

代码语言:javascript复制
from sqlalchemy.ext.declarative import AbstractConcreteBase
from sqlalchemy.orm import DeclarativeBase

class Base(DeclarativeBase):
    pass

class Employee(AbstractConcreteBase, Base):
    strict_attrs = True

    name = mapped_column(String(50))

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

Base.registry.configure()

在上面的代码中,调用了registry.configure()方法,这将触发Employee类实际映射;在配置步骤之前,该类没有映射,因为它将从中查询的子表尚未定义。这个过程比ConcreteBase的过程更复杂,因为必须延迟基类的整个映射,直到所有的子类都已声明。通过像上面这样的映射,只能持久化ManagerEngineer的实例;对Employee类进行查询将始终生成ManagerEngineer对象。

使用上述映射,可以按照Employee类和在其上本地声明的任何属性生成查询,例如Employee.name

代码语言:javascript复制
>>> stmt = select(Employee).where(Employee.name == "n1")
>>> print(stmt)
SELECT  pjoin.id,  pjoin.name,  pjoin.type,  pjoin.manager_data,  pjoin.engineer_info
FROM  (
  SELECT  engineer.id  AS  id,  engineer.name  AS  name,  engineer.engineer_info  AS  engineer_info,
  CAST(NULL  AS  VARCHAR(40))  AS  manager_data,  'engineer'  AS  type
  FROM  engineer
  UNION  ALL
  SELECT  manager.id  AS  id,  manager.name  AS  name,  CAST(NULL  AS  VARCHAR(40))  AS  engineer_info,
  manager.manager_data  AS  manager_data,  'manager'  AS  type
  FROM  manager
)  AS  pjoin
WHERE  pjoin.name  =  :name_1 

AbstractConcreteBase.strict_attrs 参数指示 Employee 类应直接映射仅属于 Employee 类的本地属性,即 Employee.name 属性。其他属性,如 Manager.manager_dataEngineer.engineer_info,仅存在于其对应的子类中。当未设置 AbstractConcreteBase.strict_attrs 时,所有子类属性,如 Manager.manager_dataEngineer.engineer_info,都映射到基类 Employee。这是一种传统的使用模式,可能更方便查询,但其效果是所有子类共享整个层次结构的完整属性集;在上述示例中,不使用 AbstractConcreteBase.strict_attrs 将导致生成不必要的 Engineer.manager_nameManager.engineer_info 属性。

2.0 版本新增:增加了 AbstractConcreteBase.strict_attrs 参数到 AbstractConcreteBase 中,以产生更清晰的映射;默认值为 False,以允许传统映射继续像 1.x 版本中那样工作。

另请参阅

AbstractConcreteBase

经典和半经典具有多态性的具体配置

ConcreteBaseAbstractConcreteBase说明的声明性配置等同于另外两种明确使用polymorphic_union()的配置形式。这些配置形式明确使用Table对象,以便首先创建“多态联合”,然后应用于映射。这里举例说明了这些配置形式,以阐明polymorphic_union()函数在映射方面的作用。

一个半经典映射的例子利用了声明性,但是单独建立了Table对象:

代码语言:javascript复制
metadata_obj = Base.metadata

employees_table = Table(
    "employee",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
)

managers_table = Table(
    "manager",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("manager_data", String(50)),
)

engineers_table = Table(
    "engineer",
    metadata_obj,
    Column("id", Integer, primary_key=True),
    Column("name", String(50)),
    Column("engineer_info", String(50)),
)

接下来,使用polymorphic_union()生成 UNION:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "employee": employees_table,
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

使用上述Table对象,可以使用“半经典”风格生成映射,其中我们与__table__参数一起使用声明性;我们上面的多态联合通过__mapper_args__传递给Mapper.with_polymorphic参数:

代码语言:javascript复制
class Employee(Base):
    __table__ = employee_table
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": ("*", pjoin),
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

或者,完全可以使用完全“经典”风格,而根本不使用声明性,使用与声明性提供的类似的构造函数:

代码语言:javascript复制
class Employee:
    def __init__(self, **kw):
        for k in kw:
            setattr(self, k, kw[k])

class Manager(Employee):
    pass

class Engineer(Employee):
    pass

employee_mapper = mapper_registry.map_imperatively(
    Employee,
    pjoin,
    with_polymorphic=("*", pjoin),
    polymorphic_on=pjoin.c.type,
)
manager_mapper = mapper_registry.map_imperatively(
    Manager,
    managers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="manager",
)
engineer_mapper = mapper_registry.map_imperatively(
    Engineer,
    engineers_table,
    inherits=employee_mapper,
    concrete=True,
    polymorphic_identity="engineer",
)

“抽象”示例也可以使用“半经典”或“经典”风格进行映射。不同之处在于,我们不是将“多态联合”应用于Mapper.with_polymorphic参数,而是直接将其应用为我们最基本的映射器上的映射选择器。下面是半经典映射的示例:

代码语言:javascript复制
from sqlalchemy.orm import polymorphic_union

pjoin = polymorphic_union(
    {
        "manager": managers_table,
        "engineer": engineers_table,
    },
    "type",
    "pjoin",
)

class Employee(Base):
    __table__ = pjoin
    __mapper_args__ = {
        "polymorphic_on": pjoin.c.type,
        "with_polymorphic": "*",
        "polymorphic_identity": "employee",
    }

class Engineer(Employee):
    __table__ = engineer_table
    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

class Manager(Employee):
    __table__ = manager_table
    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

在上面的示例中,我们像以前一样使用polymorphic_union(),只是省略了employee表。

另请参见

命令式映射 - 关于命令式或“经典”映射的背景信息

具体继承关系

在具体继承的情况下,映射关系是具有挑战性的,因为不同的类不共享一个表。如果关系只涉及特定的类,比如我们之前示例中的 CompanyManager 之间的关系,那么不需要特殊步骤,因为这只是两个相关的表。

然而,如果 Company 要与 Employee 有一对多的关系,表明集合可能包括 EngineerManager 对象,这意味着 Employee 必须具有多态加载能力,并且每个相关的表都必须有一个外键返回到 company 表。这样的配置示例如下:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

具体继承和关系的下一个复杂性涉及当我们希望 EmployeeManagerEngineer 中的一个或全部自身参考 Company 时。对于这种情况,SQLAlchemy 在 Employee 上有特殊的行为,即一个链接到 Companyrelationship() 放置在 Employee 上,当在实例级别执行时,不适用ManagerEngineer 类。相反,必须对每个类应用不同的 relationship()。为了实现作为 Company.employees 的相反的三个单独关系的双向行为,使用了 relationship.back_populates 参数:

代码语言:javascript复制
from sqlalchemy.ext.declarative import ConcreteBase

class Company(Base):
    __tablename__ = "company"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    employees = relationship("Employee", back_populates="company")

class Employee(ConcreteBase, Base):
    __tablename__ = "employee"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "employee",
        "concrete": True,
    }

class Manager(Employee):
    __tablename__ = "manager"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    manager_data = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "manager",
        "concrete": True,
    }

class Engineer(Employee):
    __tablename__ = "engineer"
    id = mapped_column(Integer, primary_key=True)
    name = mapped_column(String(50))
    engineer_info = mapped_column(String(40))
    company_id = mapped_column(ForeignKey("company.id"))
    company = relationship("Company", back_populates="employees")

    __mapper_args__ = {
        "polymorphic_identity": "engineer",
        "concrete": True,
    }

上述限制与当前实现相关,包括具体继承类不共享超类的任何属性,因此需要设置不同的关系。

加载具体继承映射

具体继承加载选项有限;一般来说,如果在映射器上配置了多态加载,使用其中一个声明性的具体混合类,那么在当前的 SQLAlchemy 版本中它就不能在查询时进行修改。通常,with_polymorphic() 函数应该能够覆盖具体加载使用的样式,然而由于当前的限制,这还不被支持。

0 人点赞