版本:1.1.0b2 |发布日期:2016年7月1日

SQLAlchemy 1.1文档

将关系链接到Backref

backref关键字参数最初是在Object Relational Tutorial中引入的,在这里的许多示例中都提到过。它究竟做了什么?我们从规范的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'))

上面的配置在User上建立了一个名为User.addressesAddress对象的集合。它还在Address上建立一个.user属性,它将引用父对象User

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

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")

上面,我们明确地向Address添加一个.user关系。On both relationships, the back_populates directive tells each relationship about the other one, indicating that they should establish “bidirectional” behavior between each other. 这种配置的主要作用是,这种关系将事件处理程序添加到两个属性中,这两个属性的行为是“当发生追加或设置事件时,使用这个特定属性名称将自己设置为传入属性”。行为如下所示。UserAddress实例开始。.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时,Address对象将从反向集合中移除:

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

.addresses集合和.user属性的操作完全是在Python中进行的,没有与SQL数据库进行任何交互。如果没有这种行为,那么一旦数据被刷新到数据库中,双方就会看到正确的状态,并在提交或到期操作发生后重新加载。The backref/back_populates behavior has the advantage that common bidirectional operations can reflect the correct state without requiring a database round trip.

请记住,当在单个关系上使用backref关键字时,就好像上面的两个关系是分别使用back_populates创建的。

Backref参数

我们已经确定,backref关键字仅仅是构建彼此相关的两个单独的relationship()结构的快捷方式。这个快捷方式的一部分行为是,应用于relationship()的某些配置参数也将应用于另一个方向 - 即描述模式级关系的参数,而不可能在相反的方向上是不同的。The usual case here is a many-to-many relationship() that has a secondary argument, or a one-to-many or many-to-one which has a primaryjoin argument (the primaryjoin argument is discussed in Specifying Alternate Join Conditions). 例如,如果我们将Address对象的列表限制为以“tony”开头的列表:

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 || '%%'
>>>

This reuse of arguments should pretty much do the “right thing” - it uses only arguments that are applicable, and in the case of a many-to- many relationship, will reverse the usage of primaryjoin and secondaryjoin to correspond to the other direction (see the example in Self-Referential Many-to-Many Relationship for this).

然而,我们经常会指定参数,而这些参数只针对我们碰巧放置“backref”的那一边。This includes relationship() arguments like lazy, remote_side, cascade and 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"))

在上面,我们只在Address.user一边放置了一个lazy="joined"指令,表明当对Address ,应自动创建一个到User实体的连接,以填充每个返回的Address.user属性。backref()函数将我们给它的参数格式化为一个由接收relationship()解释的形式,作为应用于它创建的新关系的附加参数。

单程Backrefs

一个不寻常的情况是“单程后退”。这是backref的“back-populating”行为只在一个方向上需要的地方。一个例子是包含一个过滤primaryjoin条件的集合。我们希望根据需要将项追加到此集合中,并让它们在传入对象上填充“父”对象。但是,我们也想拥有不属于集合的项目,但仍然具有相同的“父”关联 - 这些项目不应该在集合中。

以我们前面的例子为例,我们建立了一个primaryjoin,它将收藏集限制在Address对象的电子邮件地址以tony backref行为是所有项目都在两个方向上填充。我们不希望这种情况下,像下面这样的情况:

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

Above, the Address object that doesn’t match the criterion of “starts with ‘tony’” is present in the addresses collection of u1. 在这些对象被刷新后,事务被提交并且它们的属性在重载期间过期,所以addresses集合将在下一次访问时触发数据库并且不再具有Address对象目前,由于过滤条件。但是,我们可以通过使用两个单独的relationship()结构,仅在一侧放置back_populates来消除Python端“backref”行为的这个不需要的一方:

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对象附加到User.addresses集合将始终建立.user >属性在Address上:

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

但是,将User应用于Address.user属性,不会将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试图将这些保持在最低水平。