链接与 backref 的关系
这个 relationship.backref
关键字参数首次引入于 对象关系教程(1.x API) 在这里的许多例子中都提到过。它实际上是做什么的?让我们从规范开始 User
和 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", 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.primaryjoin
和 relationship.secondaryjoin
与另一个方向相对应(参见中的示例 自指多对多关系 为此)。
然而,通常情况下,我们希望指定特定于我们放置“backref”的那一方的参数。这包括 relationship()
类似论点 relationship.lazy
, relationship.remote_side
, relationship.cascade
和 relationship.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()
以上 User
是 persistent 在 Session
. 如果我们创建一个 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上控制级联 更多信息。
参见
单向回退
一个不寻常的情况是“单向回传”。这就是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试图将这些限制在最低限度。