SqlAlchemy 2.0 中文文档(九)(5)

简介: SqlAlchemy 2.0 中文文档(九)

SqlAlchemy 2.0 中文文档(九)(4)https://developer.aliyun.com/article/1560677


具体继承的关系

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

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

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

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,我们可以几乎与其他形式的继承映射方式相同地设置我们的具体映射:

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()函数在设置完所有其他类之后创建所有具体映射表的联合,然后使用已经存在的基类映射器配置此语句。

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

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__指示符声明基类:

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类的变体,可以自动实现这一点:

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

>>> 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对象:

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:

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,
    }

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

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参数,而是直接将其应用为我们最基本的映射器上的映射选择器。下面是半经典映射的示例:

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 表。这样的配置示例如下:

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

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() 函数应该能够覆盖具体加载使用的样式,然而由于当前的限制,这还不被支持。

相关文章
|
6月前
|
SQL 前端开发 数据库
SqlAlchemy 2.0 中文文档(六)(1)
SqlAlchemy 2.0 中文文档(六)
70 0
|
6月前
|
存储 SQL API
SqlAlchemy 2.0 中文文档(四)(5)
SqlAlchemy 2.0 中文文档(四)
47 3
|
6月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(一)(3)
SqlAlchemy 2.0 中文文档(一)
118 1
|
6月前
|
SQL API 数据库
SqlAlchemy 2.0 中文文档(四)(1)
SqlAlchemy 2.0 中文文档(四)
49 1
|
6月前
|
SQL 存储 API
SqlAlchemy 2.0 中文文档(十)(5)
SqlAlchemy 2.0 中文文档(十)
40 1
|
6月前
|
SQL JSON 关系型数据库
SqlAlchemy 2.0 中文文档(二)(2)
SqlAlchemy 2.0 中文文档(二)
139 2
|
6月前
|
存储 Python
SqlAlchemy 2.0 中文文档(七)(5)
SqlAlchemy 2.0 中文文档(七)
36 1
|
6月前
|
SQL 存储 数据库
SqlAlchemy 2.0 中文文档(三)(3)
SqlAlchemy 2.0 中文文档(三)
79 0
|
6月前
|
SQL 关系型数据库 数据库
SqlAlchemy 2.0 中文文档(十)(2)
SqlAlchemy 2.0 中文文档(十)
37 0
|
6月前
|
SQL 安全 数据库连接
SqlAlchemy 2.0 中文文档(五)(2)
SqlAlchemy 2.0 中文文档(五)
76 0