1. ホーム
  2. python

[解決済み] sqlalchemyのback_populatesはいつ使う必要があるのでしょうか?

2022-10-13 18:16:29

質問

このガイドに従ってSQLAlchemyのRelation Exampleを試すと、次のようになります。 基本的なリレーションシップのパターン

私はこのようなコードを持っています。

#!/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 のどちらを使うべきでしょうか? どのような場合にどちらかを使うべきでしょうか?

どのように解決するのですか?

もし backref を使えば、2つ目のテーブルでリレーションシップを宣言する必要はありません。

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 はこの2つのフィールドが関連していることを認識し、もう片方が更新されると、それぞれを更新します。 注目すべきは backref を使っても同じことができます。 使用方法 back_populates を使うのは、すべてのクラスでリレーションシップを定義したい場合に便利です。そうすれば、バックリファレンスを通じてフィールドを定義する他のクラスを見る代わりに、モデルクラスをちらっと見るだけですべてのフィールドを簡単に見ることができます。