基本关系模式¶

快速浏览基本关系模式。

以下各部分使用的导入如下:

from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

一对多

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

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))

要在一对多中建立双向关系,“反向”端是多对一,请指定一个附加的 relationship() 然后用 relationship.back_populates 参数::

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", back_populates="children")

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

或者, backref 选项可用于单个 relationship() 而不是使用 back_populates ::

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

多对一

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

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)

双向行为是通过增加一秒钟来实现的。 relationship() 并应用 relationship.back_populates 双向参数:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", back_populates="parents")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parents = relationship("Parent", back_populates="child")

或者, backref 参数可以应用于单个 relationship() ,如 Parent.child ::

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref="parents")

一对一

一对一本质上是一种双向关系,两边都有一个标量属性。为了实现这一点, uselist 标志指示在关系的“多”端放置标量属性而不是集合。将一对多转换为一对一:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child = relationship("Child", uselist=False, back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey('parent.id'))
    parent = relationship("Parent", back_populates="child")

或多对一:

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", back_populates="parent")

class Child(Base):
    __tablename__ = 'child'
    id = Column(Integer, primary_key=True)
    parent = relationship("Parent", back_populates="child", uselist=False)

一如既往, relationship.backrefbackref() 功能可代替 relationship.back_populates 接近;说明 uselist 在backref上,使用 backref() 功能:

from sqlalchemy.orm import backref

class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    child_id = Column(Integer, ForeignKey('child.id'))
    child = relationship("Child", backref=backref("parent", uselist=False))

多对多

多对多在两个类之间添加一个关联表。关联表由 secondary 参数 relationship() . 通常, Table 使用 MetaData 与声明性基类关联的对象,以便 ForeignKey 指令可以定位要链接的远程表:

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=association_table)

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

对于双向关系,关系的两边都包含一个集合。指定使用 relationship.back_populates 为每个 relationship() 指定公共关联表::

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship(
        "Child",
        secondary=association_table,
        back_populates="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship(
        "Parent",
        secondary=association_table,
        back_populates="children")

当使用 backref 参数而不是 relationship.back_populates ,backref将自动使用相同的 secondary 反向关系的参数:

association_table = Table('association', Base.metadata,
    Column('left_id', Integer, ForeignKey('left.id')),
    Column('right_id', Integer, ForeignKey('right.id'))
)

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=association_table,
                    backref="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

这个 secondary 的参数 relationship() 还接受一个返回最终参数的可调用文件,该参数仅在首次使用映射器时计算。使用这个,我们可以定义 association_table 稍后,只要在所有模块初始化完成后可调用文件可用,则:

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary=lambda: association_table,
                    backref="parents")

使用声明性扩展时,也接受传统的“表的字符串名称”,与存储在 Base.metadata.tables ::

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child",
                    secondary="association",
                    backref="parents")

从多对多表中删除行

一种独特的行为 secondary 参数 relationship() 那是 Table 当对象从集合中添加或删除时,此处指定的对象将自动服从INSERT和DELETE语句。有 无需手动从此表中删除 . 从集合中删除记录的操作将具有在刷新时删除的行的效果:

# row will be deleted from the "secondary" table
# automatically
myparent.children.remove(somechild)

经常出现的一个问题是,当直接将子对象交给“辅助”表时,如何删除“辅助”表中的行 Session.delete() ::

session.delete(somechild)

这里有几种可能性:

  • relationship()ParentChild 但是有 not 一种连接特定事物的反向关系。 Child 对每个 Parent ,SQLAlchemy将不知道删除此特定项时 Child 对象,它需要维护将其链接到 Parent . 不会删除“辅助”表。

  • 如果有一个关系链接到一个特定的 Child 对每个 Parent ,假设它被称为 Child.parents ,默认情况下,SQLAlchemy将加载到 Child.parents 集合以定位所有 Parent 对象,并从建立此链接的“辅助”表中删除每一行。注意,这种关系不需要是双向的;sqlAlchemy严格地查看 relationship()Child 正在删除的对象。

  • 这里的一个更高性能的选项是使用数据库使用的外键来删除CASCADE指令。假设数据库支持此功能,则可以使数据库本身自动删除“辅助”表中的行,因为“子”中的引用行将被删除。可以指示SQLAlchemy放弃在 Child.parents 在本例中,使用 passive_deletes 指令 relationship()使用被动删除 有关此的详细信息。

再次注意,这些行为是 only 有关的 secondary 与一起使用的选项 relationship() . 如果处理显式映射的关联表, not 存在于 secondary 相关选项 relationship() ,可以使用级联规则来自动删除实体,以响应被删除的相关实体-请参见 级联 有关此功能的信息。

关联对象

关联对象模式是多对多的变体:当关联表包含的列超出了左表和右表的外键之外的其他列时,就会使用它。而不是使用 secondary 参数,将新类直接映射到关联表。关系的左侧通过一对多引用关联对象,关联类通过多对一引用右侧。下面我们演示了一个映射到 Association 类,其中包含一个名为 extra_data ,它是一个字符串值,与 ParentChild ::

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Association")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

和往常一样,双向版本使用 relationship.back_populatesrelationship.backref ::

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child", back_populates="parents")
    parent = relationship("Parent", back_populates="children")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Association", back_populates="parent")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Association", back_populates="child")

使用其直接形式的关联模式要求子对象在附加到父对象之前与关联实例关联;同样,父对象到子对象的访问通过关联对象进行:

# create parent, append a child via association
p = Parent()
a = Association(extra_data="some data")
a.child = Child()
p.children.append(a)

# iterate through child objects via association, including association
# attributes
for assoc in p.children:
    print(assoc.extra_data)
    print(assoc.child)

增强关联对象模式,以便直接访问 Association 对象是可选的,SQLAlchemy提供 关联代理 延伸。此扩展允许配置属性,这些属性将通过一次访问访问两个"hop",一个"hop"访问关联的对象,另一个"hop"访问目标属性。

警告

关联对象模式 不与将关联表映射为“辅助”的单独关系协调更改 .

下面,对 Parent.children 不会与对 Parent.child_associationsChild.parent_associations 在python中;虽然所有这些关系都将继续正常运行,但是在 Session 过期,通常在 Session.commit() ::

class Association(Base):
    __tablename__ = 'association'

    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))

    child = relationship("Child", backref="parent_associations")
    parent = relationship("Parent", backref="child_associations")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)

    children = relationship("Child", secondary="association")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)

此外,正如对一个关系的更改不会自动反映在另一个关系中一样,向两个关系中写入相同的数据也会导致插入或删除语句冲突,例如下面我们在 ParentChild 对象两次:

p1 = Parent()
c1 = Child()
p1.children.append(c1)

# redundant, will cause a duplicate INSERT on Association
p1.parent_associations.append(Association(child=c1))

如果你知道自己在做什么,可以使用上面这样的映射,尽管应用 viewonly=True 参数设置为“次要”关系,以避免记录冗余更改的问题。然而,为了得到一个简单的两个对象的万无一失的模式 Parent->Child 关系在仍然使用关联对象模式时,使用关联代理扩展,如中所述 关联代理 .