何时需要使用SQLAlchemy的back_populates?

138
当我按照这篇指南尝试使用SQLAlchemy Relation Example时:Basic Relationship Patterns,我有以下代码。
#!/usr/bin/env python
# encoding: utf-8
from sqlalchemy import create_engine
from sqlalchemy import Table, Column, Integer, ForeignKey
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

engine = create_engine('sqlite:///:memory:', echo=True)
Session = sessionmaker(bind=engine)
session = Session()
Base = declarative_base(bind=engine)

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'))
    parent = relationship("Parent")

Base.metadata.create_all()

p = Parent()
session.add(p)
session.commit()
c = Child(parent_id=p.id)
session.add(c)
session.commit()
print "children: {}".format(p.children[0].id)
print "parent: {}".format(c.parent.id)

这个功能很好用,但是指南中说模型应该是:

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

为什么我的例子中不需要使用back_populates或者backref? 何时应该使用其中一个?

1个回答

257
如果您使用 backref,则不需要在第二个表中声明关系。
class Parent(Base):
    __tablename__ = 'parent'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", backref="parent")

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

如果你没有使用backref,并且单独定义了relationship,那么如果你不使用back_populates,sqlalchemy就不会知道如何连接这些关系,因此修改一个关系也不会自动更新另一个关系。

所以,在你的例子中,你已经单独定义了relationship,但是没有提供back_populates参数,修改一个字段不会自动更新事务中的另一个字段。

>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[]

看到了吗,它没有自动填写children字段?

现在,如果您提供一个back_populates参数,sqlalchemy将连接这些字段。

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

所以现在我们得到

>>> parent = Parent()
>>> child = Child()
>>> child.parent = parent
>>> print(parent.children)
[Child(...)]

现在Sqlalchemy知道这两个字段之间的关系,并且会在一个字段更新时同时更新另一个字段。值得注意的是,使用backref也可以实现这一点。但是,如果你想在每个类中定义关系,那么使用back_populates就很好,因为它可以轻松地查看模型类中所有字段,而不必查看通过backref定义字段的其他类。


104
关于 back_populatesbackref 的说明:backref 更加简洁,因为你不需要在两个类中都声明关系,但实际上我认为这并不值得一行的省略。我认为 back_populates 更好,不仅因为在 Python 文化中 "显式优于隐式" (Python 之禅),而且当你有许多模型时,通过快速查看其声明,你可以看到所有关系及其名称,而无需查看相关的所有模型。此外,back_populates 的一个好处是,在大多数 IDE 中,你可以自动完成双向关系。 - Fabiano
在Child下,parent_id是否真的必要?还有,如文档中所示,辅助表又该怎么处理呢?(https://flask-sqlalchemy.palletsprojects.com/en/2.x/models/#many-to-many-relationships) - Luiz Tauffer
2
@LuizTauffer parent_id 是用于存储父子关系的实际外键字段,它是必需的。辅助表用于定义多对多关系(例如,如果一个 Child 可以有多个 Parent)。上述 fkey 示例是经典的一对多示例,其中每个 Child 都有一个且仅有一个 Parent,而一个 Parent 可以有多个 Children。 - Brendan Abel
如果您只需要从一侧检索信息,则可以仅在给定侧上添加back_populates。例如,您可以在子级上使用back_populates来提供有关父级的信息,并使父类不带有对子级引用的“back_populated”。 - natbusa
2
这里有很多好的答案。只是想补充一些我没有看到提到的内容;随着项目的发展,使用back_populates时我开始遇到循环依赖问题。因此,在只需要在一个端点使用实体时,使用backref有助于减少遇到依赖性的可能性。 - omufeed
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接