SQLAlchemy ORM - 具有联结表属性的多对多关系查询

Ben*_*Ben 5 python many-to-many sqlalchemy

我有一种类似于此处描述的多对多关系。请注意,我的关联表包含一个extra_data字段..

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(ForeignKey('left.id'), primary_key=True)
    right_id = Column(ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary="association", back_populates="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Parent", secondary="association", back_populates="children")
Run Code Online (Sandbox Code Playgroud)

如果我想获取特定的父对象及其子对象,我可以这样做

db_parent = db.query(Parent).where(Parent.id == 1).first()
print(db_parent.children[0].id)  # works fine
Run Code Online (Sandbox Code Playgroud)

但是,该extra_data字段不作为子项的属性包含在内。

print(db_parent.children[0].extra_data)
Run Code Online (Sandbox Code Playgroud)

AttributeError:“Child”对象没有属性“extra_data”

我如何编写获取父级的子级,以便将其extra_data作为属性包含在内?


完全工作示例

from sqlalchemy import create_engine, Column, Integer, String, ForeignKey
from sqlalchemy.orm import declarative_base, relationship, Session

# Make the engine
engine = create_engine("sqlite+pysqlite:///:memory:", future=True, echo=False)

# Make the DeclarativeMeta
Base = declarative_base()


class Association(Base):
    __tablename__ = 'association'
    left_id = Column(ForeignKey('left.id'), primary_key=True)
    right_id = Column(ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)
    children = relationship("Child", secondary="association", back_populates="parents")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)
    parents = relationship("Parent", secondary="association", back_populates="children")


# Create the tables in the database
Base.metadata.create_all(engine)

# Test it
with Session(bind=engine) as session:

    # add parents
    p1 = Parent()
    session.add(p1)

    p2 = Parent()
    session.add(p2)

    session.commit()

    # add children
    c1 = Child()
    session.add(c1)

    c2 = Child()
    session.add(c2)

    session.commit()

    # map children to parents
    a1 = Association(left_id=p1.id, right_id=c1.id, extra_data='foo')
    a2 = Association(left_id=p1.id, right_id=c2.id, extra_data='bar')
    a3 = Association(left_id=p2.id, right_id=c2.id, extra_data='baz')

    session.add(a1)
    session.add(a2)
    session.add(a3)

    session.commit()


with Session(bind=engine) as session:
    db_parent = session.query(Parent).where(Parent.id == 1).first()
    print(db_parent.children[0].id)
    print(db_parent.children[0].extra_data)
Run Code Online (Sandbox Code Playgroud)

Syl*_*NFF 2

使用 SQLAlchemy 无法完全按照您想要的方式完成您所要求的操作。事实上,who 中的项目Parent.children应该是Child类的实例。如果您的子类具有extra_data从关联表加载的属性,那么它将引用其父类中的哪个属性?

我试图解释的是,您希望在 Child 中拥有的对“extra_data”的隐式引用只有在从父对象引用 Child 对象时才有意义。

例如,想象以下场景

session.add_all(
   Association(left=parent_a.id, right=child.id, extra_data="hello")
   Association(left=parent_b.id, right=child.id, extra_data="world")
)
Run Code Online (Sandbox Code Playgroud)

您期望在哪个父级元数据中child.extra_data?

而且大多数时候,如果需要一个对象作为关联表,就意味着这个对象本身就有意义。所以你不应该试图隐藏它。看看下面的具体例子

class Account(Base):
    __tablename__ = "accounts"
    id = Column(Integer, primary_key=True)
    username = Column(String(10), nullable=False)
    groups = relationship("Membership", back_populates="account")


class Group(Base):
    __tablename__ = "groups"
    id = Column(Integer, primary_key=True)
    name = Column(String(10), nullable=False)
    members = relationship("Membership", back_populates="group")


class Membership(Base):
    """Membership is our association table here"""
    __tablename__ = "memberships"
    id = Column(Integer, primary_key=True)

    account_id = Column(Integer, ForeignKey("accounts.id"))
    account = relationship("Account", back_populates="groups")

    group_id = Column(Integer, ForeignKey("groups.id"))
    group = relationship("Group", back_populates="members")

    # extra data embed in association table
    role = Column(String(10), nullable=False)

Base.metadata.create_all()

# create user "toto" that belongs to group "Funny people" with role "joker"
toto = Account(username="toto")
funny_people = Group(name="Funny people")
session.add(Membership(account=toto, group=funny_people, role="joker"))
session.commit()
Run Code Online (Sandbox Code Playgroud)

请注意两种方法之间的差异。在这里,Account.groups 包含成员身份,而不是直接的 Group 对象。然后你可以这样使用它:

toto = session.query(Account).first()

toto.username
toto.groups[0].group.name
toto.groups[0].role
Run Code Online (Sandbox Code Playgroud)

我知道这并不完全是您所要求的,但这可能是您可以拥有的最接近的结果,而不会引入会干扰应用程序正常运行的奇怪逻辑