映射类继承层次结构

优质
小牛编辑
128浏览
2023-12-01

SQLAlchemy支持三种继承形式: 单表继承 ,其中几种类型的类由一个表表示, 具体的表继承 ,其中每种类型的类都由独立的表表示,并且 联接表继承 ,其中类层次结构在依赖表中被分解,每个类都由其自己的表表示,该表只包含该类的本地属性。

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

在继承关系中配置映射器时,SQLAlchemy可以加载元素 polymorphically ,表示单个查询可以返回多个类型的对象。

参见

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

联接表继承

在联合表继承中,沿着类层次结构的每个类都由一个不同的表表示。查询层次结构中的特定子类将沿其继承路径中的所有表呈现为SQL联接。如果查询的类是基类,则 默认行为是只包括基表 在select语句中。在所有情况下,为给定行实例化的最终类都由鉴别器列或对基表有效的表达式确定。加载子类时 only 对于基表,结果对象将首先填充基属性;子类的本地属性将 lazy load 当它们被访问时。另外,还有一些选项可以更改默认行为,允许查询前面包含与多个表/子类对应的列。

联合继承层次结构中的基类配置了其他参数,这些参数将引用多态鉴别器列以及基类的标识符:

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

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

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

虽然多态性鉴别器表达不是严格必要的,但如果需要多态性加载,则需要它。在基表上建立一个简单的列是实现这一点的最简单方法,但是非常复杂的继承映射甚至可以将SQL表达式(如case语句)配置为多态鉴别器。

注解

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

我们下一个定义 EngineerManager 亚类 Employee . 每个列都包含表示它们所表示的子类唯一属性的列。每个表还必须包含主键列(或列),以及对父表的外键引用::

class Engineer(Employee):
    __tablename__ = 'engineer'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    engineer_name = Column(String(30))

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

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

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

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

ORM使用由设置的值 mapper.polymorphic_identity 以便在多态加载行时确定行属于哪个类。在上面的示例中,表示 Employee 会有价值的 'employee' 在其 type 同样,每行;每行 Engineer 会得到价值 'engineer' ,每一个 Manager 会得到价值 'manager' . 不管继承映射是在联接表继承中为子类使用不同的联接表,还是像在单表继承中那样使用全部一个表,这个值都应该在查询时被持久化并可供ORM使用。这个 mapper.polymorphic_identity 参数也适用于具体的表继承,但实际上并不是持久化的;请参见 具体的表继承 有关详细信息。

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

联接的继承主键

联合表继承配置的一个自然效果是,任何映射对象的标识都可以完全由基表中的行来确定。这有明显的优点,因此sqlAlchemy总是将联接继承类的主键列视为仅基表的主键列。也就是说, id 两列 engineermanager 表不用于定位 EngineerManager 对象-仅限中的值 employee.id 被认为是。 engineer.idmanager.id 当然,在语句中确定父行后,它们仍然对模式整体的正确操作至关重要,因为它们用于定位联接行。

连接继承映射完成后,查询 Employee 将返回 EmployeeEngineerManager 物体。新保存的 EngineerManagerEmployee 对象将自动填充 employee.type 在这种情况下具有正确“discriminator”值的列 "engineer""manager""employee" ,视情况而定。

与联合继承的关系

联接表继承完全支持关系。涉及已联接继承类的关系应以层次结构中也对应于外键约束的类为目标;在下面,作为 employee 表具有返回到的外键约束 company 表中,在 CompanyEmployee ::

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

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

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

class Manager(Employee):
    # ...

class Engineer(Employee):
    # ...

如果外键约束位于对应于子类的表上,那么关系应该以该子类为目标。在下面的示例中,有一个外键约束来自 managercompany ,因此在 ManagerCompany 班级:

class Company(Base):
    __tablename__ = 'company'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    managers = relationship("Manager", back_populates="company")

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

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

class Manager(Employee):
    __tablename__ = 'manager'
    id = Column(Integer, ForeignKey('employee.id'), primary_key=True)
    manager_name = Column(String(30))

    company_id = Column(ForeignKey('company.id'))
    company = relationship("Company", back_populates="managers")

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

class Engineer(Employee):
    # ...

上面, Manager 班级将有一个 Manager.company 属性; Company 将有一个 Company.managers 总是针对 employeemanager 桌子放在一起。

正在加载联接的继承映射

参见章节 正在加载继承层次结构正在加载具有联接表继承的对象 对于继承加载技术的背景,包括要在映射器配置时和查询时查询的表的配置。

单表继承

单表继承表示单表中所有子类的所有属性。具有该类独有属性的特定子类将在表中的列中持久化这些属性,否则,如果该行引用不同类型的对象,这些列将为空。

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

与联接表继承相比,单表继承具有简单的优点;查询效率更高,因为只有一个表需要参与,才能加载每个表示类的对象。

单表继承配置与联接表继承非常相似,只是基类指定了 __tablename__ . 基表上还需要一个鉴别器列,这样类就可以彼此区分。

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

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(20))

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

class Manager(Employee):
    manager_data = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'manager'
    }

class Engineer(Employee):
    engineer_info = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'engineer'
    }

注意,派生类管理器和工程师的映射器省略了 __tablename__ ,指示它们没有自己的映射表。

解决列冲突

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

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(20))

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

class Engineer(Employee):
    __mapper_args__ = {'polymorphic_identity': 'engineer'}
    start_date = Column(DateTime)

class Manager(Employee):
    __mapper_args__ = {'polymorphic_identity': 'manager'}
    start_date = Column(DateTime)

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

sqlalchemy.exc.ArgumentError: Column 'start_date' on class
<class '__main__.Manager'> conflicts with existing
column 'employee.start_date'

上面的场景为声明性映射系统提供了一个模糊性,可以通过使用 declared_attr 定义 Column 有条件地,注意归还 既有线 经由父母 __table__ 如果它已经存在:

from sqlalchemy.orm import declared_attr

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(20))

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

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

    @declared_attr
    def start_date(cls):
        "Start date column, if not present already."
        return Employee.__table__.c.get('start_date', Column(DateTime))

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

    @declared_attr
    def start_date(cls):
        "Start date column, if not present already."
        return Employee.__table__.c.get('start_date', Column(DateTime))

以上,何时 Manager 映射到 start_date 列已存在于 Employee 类;通过返回现有的 Column 对象时,声明性系统识别出这是要分别映射到两个不同子类的同一列。

mixin类也可以使用类似的概念(请参见 使用mixin组合映射层次 )要从可重用mixin类定义一系列特定的列和/或其他映射属性:

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(20))

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

class HasStartDate:
    @declared_attr
    def start_date(cls):
        return cls.__table__.c.get('start_date', Column(DateTime))

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

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

与单表继承的关系

单表继承完全支持关系。配置的方式与联接继承的方式相同;外键属性应位于关系的“外部”端的同一类上:

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

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

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


class Manager(Employee):
    manager_data = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'manager'
    }

class Engineer(Employee):
    engineer_info = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'engineer'
    }

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

class Company(Base):
    __tablename__ = 'company'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    managers = relationship("Manager", back_populates="company")

class Employee(Base):
    __tablename__ = 'employee'
    id = Column(Integer, primary_key=True)
    name = Column(String(50))
    type = Column(String(50))

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


class Manager(Employee):
    manager_name = Column(String(30))

    company_id = Column(ForeignKey('company.id'))
    company = relationship("Company", back_populates="managers")

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


class Engineer(Employee):
    engineer_info = Column(String(50))

    __mapper_args__ = {
        'polymorphic_identity':'engineer'
    }

上面, Manager 班级将有一个 Manager.company 属性; Company 将有一个 Company.managers 总是针对 employee 使用附加的WHERE子句将行限制为 type = 'manager' .

加载单个继承映射

单表继承的加载技术与用于连接表继承的加载技术基本相同,并且这两种映射类型之间提供了高度的抽象性,以便在它们之间切换以及在单个层次结构中混合它们(只需省略 __tablename__ 从属于单一继承的子类)。参见章节 正在加载继承层次结构加载具有单表继承的对象 有关继承加载技术的文档,包括要在映射器配置时和查询时查询的类的配置。

具体的表继承

具体继承将每个子类映射到它自己的不同表,每个表包含生成该类实例所需的所有列。默认情况下,具体的继承配置以非多态方式查询;对特定类的查询将只查询该类的表,并且只返回该类的实例。具体类的多态加载是通过在映射器中配置一个特殊的选择来实现的,该选择通常作为所有表的联合产生。

警告

具体的表继承是 更复杂的是 而不是联合的或单表继承,并且 功能更加有限 特别是关于将其用于关系、渴望加载和多态加载。当使用多态性时,它产生 非常大的查询 使用的联合不如简单的联接效果好。强烈建议,如果需要关系加载和多态加载的灵活性,则尽可能使用联接表或单表继承。如果不需要多态加载,那么如果每个类完全引用自己的表,就可以使用普通的非继承映射。

尽管连接表继承和单表继承在“多态”加载方面非常流利,但在具体继承中,这是一个更为棘手的问题。因此,当 不需要多态加载 . 建立涉及具体继承类的关系也更加困难。

要使用具体继承建立类,请添加 mapper.concrete 中的参数 __mapper_args__ . 这向声明性和映射指示不应将超类表视为映射的一部分:

class Employee(Base):
    __tablename__ = 'employee'

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

class Manager(Employee):
    __tablename__ = 'manager'

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

    __mapper_args__ = {
        'concrete': True
    }

class Engineer(Employee):
    __tablename__ = 'engineer'

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

    __mapper_args__ = {
        'concrete': True
    }

应注意两个关键点:

  • 我们必须 显式定义所有列 在每个子类上,甚至是相同名称的子类上。一列,如 Employee.name 这里是 not 复制到由映射的表 ManagerEngineer 对我们来说。

  • EngineerManager 类在继承关系中与映射 Employee 他们仍然 不包括多态加载 . 意思是,如果我们查询 Employee 对象 managerengineer 根本不查询表。

混凝土多晶加载形态

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

正如在 正在加载继承层次结构 ,可以将任何类型的映射器继承配置配置为默认情况下使用 mapper.with_polymorphic 争论。当前公共API要求在 Mapper 第一次施工时。

但是,在声明性的情况下,映射器和 Table 一旦定义了映射类,就会立即创建映射类。这意味着 mapper.with_polymorphic 还无法提供参数,因为 Table 与子类相对应的对象尚未定义。

有几个策略可以解决这个循环,但是声明性提供了助手类。 ConcreteBaseAbstractConcreteBase 在幕后处理这个问题。

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

from sqlalchemy.ext.declarative import ConcreteBase

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

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

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

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

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

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

上面,声明性设置了 Employee 在映射器“初始化”时初始化;这是用于解析其他依赖映射器的映射器的最新配置步骤。这个 ConcreteBase 助手使用 polymorphic_union() 函数在设置所有其他类之后创建所有具体映射表的联合,然后使用现有的基类映射器配置此语句。

选择后,多态联合生成如下查询:

session.query(Employee).all()
SELECT
    pjoin.id AS pjoin_id,
    pjoin.name AS pjoin_name,
    pjoin.type AS pjoin_type,
    pjoin.manager_data AS pjoin_manager_data,
    pjoin.engineer_info AS pjoin_engineer_info
FROM (
    SELECT
        employee.id AS id,
        employee.name AS name,
        CAST(NULL AS VARCHAR(50)) AS manager_data,
        CAST(NULL AS VARCHAR(50)) 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(50)) AS engineer_info,
        'manager' AS type
    FROM manager
    UNION ALL
    SELECT
        engineer.id AS id,
        engineer.name AS name,
        CAST(NULL AS VARCHAR(50)) AS manager_data,
        engineer.engineer_info AS engineer_info,
        'engineer' AS type
    FROM engineer
) AS pjoin

上面的联合查询需要为每个子表生成“空”列,以便容纳那些不是该特定子类成员的列。

抽象的具体类

到目前为止,所示的具体映射既显示了子类,也显示了映射到各个表的基类。在具体的继承用例中,通常不在数据库中表示基类,只表示子类。换句话说,基类是“抽象的”。

通常,当您希望将两个不同的子类映射到单个表,并且不映射基类时,这很容易实现。使用声明性时,只需使用 __abstract__ 指标:

class Employee(Base):
    __abstract__ = True

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

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

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

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

上面,我们实际上并没有使用sqlachemy的继承映射工具;我们可以加载和持久化 ManagerEngineer 通常情况下。但是当我们需要的时候情况会改变 多态查询 也就是说,我们想发射 session.query(Employee) 并取回一系列 ManagerEngineer 实例。这使我们回到具体继承领域,我们必须针对 Employee 为了达到这个目的。

映射器始终可以选择

在sqlAlchemy中,类的映射器总是需要引用一些“可选的”,通常是 Table 但也可以指 select() 对象也一样。虽然看起来“单表继承”映射器没有映射到表,但实际上,这些映射器隐式地引用了由超类映射的表。

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

为了帮助实现这一点,声明性提供了 ConcreteBase 类称为 AbstractConcreteBase 自动实现:

from sqlalchemy.ext.declarative import AbstractConcreteBase

class Employee(AbstractConcreteBase, Base):
    pass

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

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

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

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

这个 AbstractConcreteBase helper类的内部进程比 ConcreteBase ,其中必须延迟基类的整个映射,直到所有子类都已声明。对于上面这样的映射,只有 ManagerEngineer 可能被持久化;查询 Employee 类将始终生成 ManagerEngineer 物体。

经典和半经典混凝土多晶型构形

说明的声明性配置 ConcreteBaseAbstractConcreteBase 相当于使用 polymorphic_union() 明确地。这些构型利用 Table 对象,以便首先创建“多态联合”,然后将其应用于映射。这里对这些进行了说明,以阐明 polymorphic_union() 在映射方面的函数。

A semi-classical mapping 例如,使用声明性,但建立 Table 单独对象:

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() ::

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 参数::

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 对象可以完全“经典”样式使用,根本不使用声明性。类似于声明性提供的构造函数的说明如下:

class Employee(object):
    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 参数,我们直接将其应用于我们最基本的映射器上的映射可选项。半经典映射如下图所示:

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 桌子。

参见

命令(又称经典)映射 -“经典”映射的背景信息

与具体继承的关系

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

然而,如果 Company 是一对多的关系 Employee ,指示集合可以同时包含 EngineerManager 对象,这意味着 Employee 必须具有多态加载功能,并且每个要关联的表都必须具有返回到 company 表。这种配置的示例如下:

from sqlalchemy.ext.declarative import ConcreteBase


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


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

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


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

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


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

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

The next complexity with concrete inheritance and relationships involves when we'd like one or all of Employee, Manager and Engineer to themselves refer back to Company. For this case, SQLAlchemy has special behavior in that a relationship() placed on Employee which links to Company does not work against the Manager and Engineer classes, when exercised at the instance level. Instead, a distinct relationship() must be applied to each class. In order to achieve bi-directional behavior in terms of three separate relationships which serve as the opposite of Company.employees, the relationship.back_populates parameter is used between each of the relationships:

from sqlalchemy.ext.declarative import ConcreteBase


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


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

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


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

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


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

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

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

加载具体的继承映射

使用具体继承进行加载的选项是有限的;通常,如果在映射器上使用一个声明性具体混合配置了多态加载,则在当前的sqlAlchemy版本中,不能在查询时对其进行修改。通常情况下, with_polymorphic() 函数可以覆盖混凝土使用的加载样式,但是由于当前的限制,目前还不支持这种方式。