ORM 配置


如何映射没有主键的表?


SQLAlchemy ORM 为了映射到特定表,至少需要有一列表示为主键列;多列(即复合主键)当然也是完全可行的。这些列实际上不需要被数据库知道为主键 列,尽管它们是个好主意。 只需要将列 行为与 primary key 一样,例如作为 row 的唯一且不可为 null 的标识符。


大多数 ORM 要求对象定义某种主键,因为内存中的对象必须与数据库表中唯一可识别的行相对应;至少,这允许对象可以作为 UPDATE 和 DELETE 语句的目标,这些语句将仅影响该对象的行,而不会影响其他行。但是,主键的重要性远不止于此。在 SQLAlchemy 中,所有 ORM 映射的对象在 Session 中始终是唯一链接的 使用一种称为身份映射的模式添加到它们的特定数据库行中,该模式是 SQLAlchemy 采用的工作单元系统的核心,也是最常见(和不太常见)的 ORM 使用模式的关键。


注意


需要注意的是,我们只在谈论 SQLAlchemy ORM;一个基于 Core 构建并仅处理 Table 对象的应用程序, select() 结构等不需要任何主键出现在表上或以任何方式与表关联(尽管同样,在 SQL 中,所有表都应该有某种主键,以免您实际需要更新或删除特定行)。


在几乎所有情况下,表都有一个所谓的候选键,它是唯一标识一行的一列或一系列列。如果一个表真的没有这个,并且有实际的完全重复的行,则该表不对应于第一范式,并且无法映射。否则,构成最佳候选键的任何列都可以直接应用于 mapper:

class SomeClass(Base):
    __table__ = some_table_with_no_pk
    __mapper_args__ = {
        "primary_key": [some_table_with_no_pk.c.uid, some_table_with_no_pk.c.bar]
    }


更好的是,当使用完全声明的表元数据时,使用 primary_key=True 标志:

class SomeClass(Base):
    __tablename__ = "some_table_with_no_pk"

    uid = Column(Integer, primary_key=True)
    bar = Column(String, primary_key=True)


关系数据库中的所有表都应该有主键。即使是多对多关联表 - 主键也是两个关联列的组合:

CREATE TABLE my_association (
  user_id INTEGER REFERENCES user(id),
  account_id INTEGER REFERENCES account(id),
  PRIMARY KEY (user_id, account_id)
)


如何配置作为 Python 保留字或类似字的 Column?


可以为基于列的属性指定映射中所需的任何名称。看 显式命名声明性映射列


如何获取给定映射类的所有列、关系、映射属性等的列表?


此信息都可以从 Mapper 对象中获得。


要获取特定映射类的 Mapper,请调用 inspect() 函数:

from sqlalchemy import inspect

mapper = inspect(MyClass)


从那里,可以通过以下属性访问有关类的所有信息:


我收到关于 “Implicitically combining column X under attribute Y” 的警告或错误


这种情况是指映射包含两列,由于它们的名称,这两个列被映射在相同的属性名称下,但没有迹象表明这是有意为之。映射类需要为要存储独立值的每个属性具有显式名称;当两列具有相同的名称且未消除歧义时,它们属于同一属性,其效果是,根据首先分配给属性的列,将一列中的值复制到另一列中。


这种行为通常是可取的,并且在这种情况下是允许的,恕不另行通知 其中,两列通过外键关系链接在一起 在继承映射中。 当出现警告或异常时, 可以通过将列分配给 Different-Named 来解决 属性,或者如果需要将它们组合在一起,请使用 column_property() 使其明确。


给出示例如下:

from sqlalchemy import Integer, Column, ForeignKey
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()


class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(A):
    __tablename__ = "b"

    id = Column(Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey("a.id"))


从 SQLAlchemy 版本 0.9.5 开始,检测到上述情况,并会警告 ABid 列正在合并在同名属性 id 下,这上面是一个严重的问题,因为这意味着 B 对象的主键将始终镜像其 A 的主键。


解决此问题的映射如下所示:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(A):
    __tablename__ = "b"

    b_id = Column("id", Integer, primary_key=True)
    a_id = Column(Integer, ForeignKey("a.id"))


假设我们确实希望 A.idB.id 成为彼此的镜像,尽管 B.a_idA.id 相关的地方。我们可以使用 column_property() 将它们组合在一起:

class A(Base):
    __tablename__ = "a"

    id = Column(Integer, primary_key=True)


class B(A):
    __tablename__ = "b"

    # probably not what you want, but this is a demonstration
    id = column_property(Column(Integer, primary_key=True), A.id)
    a_id = Column(Integer, ForeignKey("a.id"))


我正在使用 Declarative 并使用 and_()or_() 设置 primaryjoin/secondaryjoin,并且收到有关外键的错误消息。


您正在执行此作吗?

class MyClass(Base):
    # ....

    foo = relationship(
        "Dest", primaryjoin=and_("MyClass.id==Dest.foo_id", "MyClass.foo==Dest.bar")
    )


这是两个字符串表达式的 and_(),SQLAlchemy 无法对其应用任何映射。Declarative 允许将 relationship() 参数指定为字符串,这些字符串使用 eval() 转换为表达式对象。但这不会发生在 and_() 表达式中 - 这是一个特殊的作,声明性仅适用于作为字符串传递给 primaryjoin 或其他参数的全部内容:

class MyClass(Base):
    # ....

    foo = relationship(
        "Dest", primaryjoin="and_(MyClass.id==Dest.foo_id, MyClass.foo==Dest.bar)"
    )


或者,如果您需要的对象已经可用,请跳过字符串:

class MyClass(Base):
    # ....

    foo = relationship(
        Dest, primaryjoin=and_(MyClass.id == Dest.foo_id, MyClass.foo == Dest.bar)
    )


同样的想法适用于所有其他参数,例如 foreign_keys

# wrong !
foo = relationship(Dest, foreign_keys=["Dest.foo_id", "Dest.bar_id"])

# correct !
foo = relationship(Dest, foreign_keys="[Dest.foo_id, Dest.bar_id]")

# also correct !
foo = relationship(Dest, foreign_keys=[Dest.foo_id, Dest.bar_id])


# if you're using columns from the class that you're inside of, just use the column objects !
class MyClass(Base):
    foo_id = Column(...)
    bar_id = Column(...)
    # ...

    foo = relationship(Dest, foreign_keys=[foo_id, bar_id])


什么是 defaultdefault_factoryinsert_default,我应该使用什么?


由于添加了 PEP-681 数据类转换,SQLAlchemy 的 API 在这里存在一些冲突,这对其命名约定非常严格。如果你使用的是 MappedAsDataclass,PEP-681 就会发挥作用,如 声明式数据类映射 中所示。如果您未使用 MappedAsDataclass,则它不适用。


第一部分 - 不使用数据类的经典 SQLAlchemy


不使用 MappedAsDataclass 时,就像 SQLAlchemy 中多年来的情况一样,mapped_column()(Column)构造支持参数 mapped_column.default。这表示在发出 INSERT 语句时将发生 Python 端默认值(而不是服务器端默认值,服务器端默认值将成为数据库架构定义的一部分)。此默认值可以是静态 Python 值(如字符串)、Python 可调用函数 SQLAlchemy SQL 构造中的任何一个。mapped_column.default 的完整文档位于 客户端调用的 SQL 表达式


mapped_column.default 使用 MappedAsData类时,此默认值 /callable 不会显示 在首次构造对象时将其置于对象上。仅当 SQLAlchemy 为您的对象执行 INSERT 语句时,才会发生此事件。


需要注意的非常重要的一点是,在使用 mapped_column() 时 (和 Column),则经典的 mapped_column.default parameter 也以一个名为 mapped_column.insert_default 中。如果您构建了一个 mapped_column() 并且您没有使用 MappedAsData类,则 mapped_column.defaultmapped_column.insert_default parameters 是同义词


第二部分 - 使用 MappedAsDataclass 的 Dataclasses 支持


当您使用 MappedAsDataclass 时,即 Declarative Dataclass Mapping 中使用的特定映射形式,则 mapped_column.default 关键字更改。我们认识到,这个名称改变其行为并不理想,但是没有其他选择,因为 PEP-681 需要 mapped_column.default 才能具有此含义。


使用数据类时,mapped_column.default 参数必须 按照 Python 数据类 - 它引用字符串或数字等常量值,并应用于您的对象 在构建时立即。目前,它也应用于 mapped_column.default 参数,即使不存在,它也会自动在 INSERT 语句中使用 在对象上。如果您想为数据类使用可调用对象,则 ,它将在构造时应用于对象,您将使用 mapped_column.default_factory 中。


访问 mapped_column.default 的仅 INSERT 行为 如上面的第一部分所述,您将使用 mapped_column.insert_default 参数。 mapped_column.insert_default(当使用数据类时)仍然是到核心级“default”进程的直接路由,其中参数可以是静态值或可调用的。


摘要图表


构建


是否适用于数据类?


在没有数据类的情况下工作?


接受标量?


接受 callable?


立即填充对象?

mapped_column.default


仅当没有数据类时


仅当数据类

mapped_column.insert_default

mapped_column.default_factory


仅当数据类