链接与 backref 的关系

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

这个 relationship.backref 关键字参数首次引入于 对象关系教程(1.x API) 在这里的许多例子中都提到过。它实际上是做什么的?让我们从规范开始 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 对象。

事实上, relationship.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 明确地。在两种关系中, relationship.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数据库进行任何交互。如果没有这种行为,在将数据刷新到数据库之后,双方都会看到正确的状态,然后在提交或到期操作发生后重新加载。这个 relationship.backref/relationship.back_populates 行为的优点是,普通的双向操作可以在不需要数据库往返的情况下反映正确的状态。

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

backref参数

我们已经确定 relationship.backref 关键字只是构建两个人的快捷方式 relationship() 相互引用的构造。这种快捷方式的一部分行为是,某些配置参数应用于 relationship() 也将应用于另一个方向——即那些描述模式级别关系的参数,并且在相反的方向上不太可能不同。这里的通常情况是多对多 relationship() 那有 relationship.secondary 或一对多或多对一的论点 relationship.primaryjoin 论证(论点) relationship.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 || '%%'
>>>

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

然而,通常情况下,我们希望指定特定于我们放置“backref”的那一方的参数。这包括 relationship() 类似论点 relationship.lazyrelationship.remote_siderelationship.cascaderelationship.cascade_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() 作为附加参数应用于它创建的新关系。

设置反向引用的层叠

SQLAlchemy 1.x系列中关于backref的一个关键行为是 cascades 默认情况下会双向发生。这基本上意味着,如果一个人以 User 对象中持久化的 Session ::

user = session.query(User).filter(User.id == 1).first()

以上 UserpersistentSession . 如果我们创建一个 Address 对象并附加到 User.addresses 集合,它将自动添加到 Session 如下例所示:

user = session.query(User).filter(User.id == 1).first()
address = Address(email_address='foo')
user.addresses.append(address)

上述行为称为“保存更新级联”,将在本节中进行描述 级联 .

但是,如果我们创建一个新的 Address 对象,并将 User 对象与 Address 如下:

address = Address(email_address='foo', user=user)

在上面的例子中,它是 not 凭直觉 Address 将自动添加到 Session . 但是 Address.user 表示 Address 对象也附加到 User.addresses 收藏。这又会引发 叶栅 操作表明 Address 应该放在 Session 作为一个 pending 对象。

由于大多数人认为这种行为违反直觉,所以可以通过设置 relationship.cascade_backrefs 假,如:

class User(Base):
    # ...

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

参见中的示例 在backrefs上控制级联 更多信息。

参见

在backrefs上控制级联 .

单向回退

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

以我们前面的例子为例,我们建立了一个 relationship.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() 构造,放置 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

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