链接与backref的关系¶

这个 backref 关键字参数首次引入于 对象关系教程 在这里的许多例子中都提到过。它实际上是做什么的?让我们从规范开始 UserAddress 脚本::

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

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", backref="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

上述配置建立了 Address 对象上 User 调用 User.addresses . 它还建立了一个 .user 属性对 Address 指的是父母 User 对象。

事实上, backref 关键字只是放置第二个关键字的常用快捷方式 relationship()Address 映射,包括在两侧建立一个事件侦听器,它将在两个方向上镜像属性操作。上述配置相当于:

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

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address", back_populates="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

    user = relationship("User", back_populates="addresses")

以上,我们添加了 .user 关系到 Address 明确地。在两种关系中, back_populates 指令告诉每个关系关于另一个,指示它们应该在彼此之间建立“双向”行为。此配置的主要效果是,关系向两个属性添加事件处理程序,这些属性的行为为“当在此处发生追加或设置事件时,使用此特定属性名将自己设置到传入属性”。行为说明如下。从A开始 User 和一个 Address 实例。这个 .addresses 集合为空,并且 .user 属性是 None ::

>>> u1 = User()
>>> a1 = Address()
>>> u1.addresses
[]
>>> print(a1.user)
None

然而,一旦 Address 附加到 u1.addresses 集合,集合和标量属性都已填充::

>>> u1.addresses.append(a1)
>>> u1.addresses
[<__main__.Address object at 0x12a6ed0>]
>>> a1.user
<__main__.User object at 0x12a6590>

当然,这种行为在移除操作以及两侧的等效操作中也起反作用。比如什么时候 .user 再次设置为 None , the Address 对象已从反向集合中删除::

>>> a1.user = None
>>> u1.addresses
[]

操纵 .addresses 收藏和 .user 属性完全发生在Python中,不与SQL数据库进行任何交互。如果没有这种行为,在将数据刷新到数据库之后,双方都会看到正确的状态,然后在提交或到期操作发生后重新加载。这个 backref/back_populates 行为的优点是,普通的双向操作可以在不需要数据库往返的情况下反映正确的状态。

记住,当 backref 关键字用于单个关系,它与上面两个关系是使用 back_populates 在每一个。

backref参数

我们已经确定 backref 关键字只是构建两个人的快捷方式 relationship() 相互引用的构造。这种快捷方式的一部分行为是,某些配置参数应用于 relationship() 也将应用于另一个方向——即那些描述模式级别关系的参数,并且在相反的方向上不太可能不同。这里的通常情况是多对多 relationship() 那有 secondary 或一对多或多对一的论点 primaryjoin 论证(论点) primaryjoin 论点讨论于 指定备用联接条件 )比如我们限制了 Address 反对那些以“托尼”开头的人:

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

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address",
                    primaryjoin="and_(User.id==Address.user_id, "
                        "Address.email.startswith('tony'))",
                    backref="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))

通过检查结果属性,我们可以观察到关系的两边都应用了这个连接条件:

>>> print(User.addresses.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>
>>> print(Address.user.property.primaryjoin)
"user".id = address.user_id AND address.email LIKE :email_1 || '%%'
>>>

这种对参数的重用几乎应该做“正确的事情”——它只使用适用的参数,并且在多对多关系的情况下,将逆转 primaryjoinsecondaryjoin 与另一个方向相对应(参见中的示例 自指多对多关系 为此)。

然而,通常情况下,我们希望指定特定于我们放置“backref”的那一方的参数。这包括 relationship() 类似论点 lazyremote_sidecascadecascade_backrefs . 对于这种情况,我们使用 backref() 函数代替字符串:

# <other imports>
from sqlalchemy.orm import backref

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)

    addresses = relationship("Address",
                    backref=backref("user", lazy="joined"))

在上面的位置,我们放置了一个 lazy="joined" 仅对 Address.user 侧边,指示当查询 Address 加入 User 应自动生成实体,该实体将填充 .user 每个返回的属性 Address . 这个 backref() 函数将我们给它的参数格式化为一种由接收端解释的形式。 relationship() 作为附加参数应用于它创建的新关系。

单向回退

一个不寻常的情况是“单向回传”。这就是backref的“后填充”行为只在一个方向上可取的地方。例如,包含筛选的集合 primaryjoin 条件。我们希望根据需要将项附加到此集合,并让它们填充传入对象上的“父”对象。但是,我们还希望有不属于集合的项,但仍具有相同的“父”关联-这些项不应在集合中。

以我们前面的例子为例,我们建立了一个 primaryjoin 仅限于 Address 其电子邮件地址以单词开头的对象 tony ,通常的backref行为是所有项都向两个方向填充。我们不希望这样的情况发生:

>>> u1 = User()
>>> a1 = Address(email='mary')
>>> a1.user = u1
>>> u1.addresses
[<__main__.Address object at 0x1411910>]

上面, Address 不符合“以“tony”开头”标准的对象出现在 addresses 收藏 u1 . 刷新这些对象之后,提交的事务及其属性将因重新加载而过期, addresses 集合将在下次访问时命中数据库,并且不再具有此 Address 由于筛选条件,对象存在。但是我们可以通过使用两个单独的 relationship() 构造,放置 back_populates 仅一侧:

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

Base = declarative_base()

class User(Base):
    __tablename__ = 'user'
    id = Column(Integer, primary_key=True)
    name = Column(String)
    addresses = relationship("Address",
                    primaryjoin="and_(User.id==Address.user_id, "
                        "Address.email.startswith('tony'))",
                    back_populates="user")

class Address(Base):
    __tablename__ = 'address'
    id = Column(Integer, primary_key=True)
    email = Column(String)
    user_id = Column(Integer, ForeignKey('user.id'))
    user = relationship("User")

在上面的场景中,附加一个 Address 对象到 .addresses A的集合 User 将始终建立 .user 在那上面的属性 Address ::

>>> u1 = User()
>>> a1 = Address(email='tony')
>>> u1.addresses.append(a1)
>>> a1.user
<__main__.User object at 0x1411850>

但是,应用 User.user AN属性 Address ,不会附加 Address 集合的对象:

>>> a2 = Address(email='mary')
>>> a2.user = u1
>>> a2 in u1.addresses
False

当然,我们已经失去了 backref 在这里,当我们附加一个 Address 这符合 email.startswith('tony') 它不会出现在 User.addresses 直到刷新会话,并在提交或过期操作后重新加载属性。虽然我们可以考虑在python中检查这个标准的属性事件,但这开始跨越了在python中复制过多SQL行为的界限。backref行为本身只是对这一哲学的轻微违反——SQLAlchemy试图将这些限制在最低限度。