如何使用 SQLAlchemy 建立多对多关系:一个很好的例子
Posted
技术标签:
【中文标题】如何使用 SQLAlchemy 建立多对多关系:一个很好的例子【英文标题】:How to build many-to-many relations using SQLAlchemy: a good example 【发布时间】:2011-08-11 00:34:19 【问题描述】:我已阅读有关构建多对多关系的 SQLAlchemy 文档和教程,但是当关联表包含超过 2 个外键时,我无法弄清楚如何正确执行此操作。
我有一个项目表,每个项目都有很多细节。很多物品的细节可以相同,所以物品和细节之间是多对多的关系
我有以下:
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
我的关联表是(在代码中其他2之前定义的):
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
在文档中,据说我需要使用“关联对象”。我无法弄清楚如何正确使用它,因为它混合了声明性与映射器表单,并且示例似乎不完整。我添加了一行:
details = relation(ItemDetail)
作为 Item 类和行的成员:
itemDetail = relation('Detail')
作为关联表的成员,如文档中所述。
当我执行 item = session.query(Item).first() 时,item.details 不是 Detail 对象的列表,而是 ItemDetail 对象的列表。
如何在 Item 对象中正确获取详细信息,即 item.details 应该是 Detail 对象的列表?
【问题讨论】:
sqlalchemy.org/docs/orm/extensions/associationproxy.html 非常冗长并且有很多例子。不知道这里还需要什么。 关联代理不是我想要的。我用表一替换了 ItemDetial 类声明,并在关系函数中使用了辅助参数 【参考方案1】:我看到你已经从 cmets 中找到了答案。但是对于一个“新用户”来说,SQLAlchemy 文档是相当难以接受的,而我也在为同样的问题而苦苦挣扎。所以供以后参考:
ItemDetail = Table('ItemDetail',
Column('id', Integer, primary_key=True),
Column('itemId', Integer, ForeignKey('Item.id')),
Column('detailId', Integer, ForeignKey('Detail.id')),
Column('endDate', Date))
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
【讨论】:
不知道为什么,但就我而言,我必须将Base.metadata
添加到ItemDetail
表中,如here 所述。
@Kerma 我还有一个问题。如何将关系放在同一个类而不是把它放在项目类和细节类中??
就我而言,我实际上必须删除 Base.metadata
才能删除“与列关联的 FK...”
嗯,那么您如何创建新的详细信息并将它们与正确的项目相关联?【参考方案2】:
上一个答案对我有用,但我对表 ItemDetail 使用了类基方法。这是示例代码:
class ItemDetail(Base):
__tablename__ = 'ItemDetail'
id = Column(Integer, primary_key=True, index=True)
itemId = Column(Integer, ForeignKey('Item.id'))
detailId = Column(Integer, ForeignKey('Detail.id'))
endDate = Column(Date)
class Item(Base):
__tablename__ = 'Item'
id = Column(Integer, primary_key=True)
name = Column(String(255))
description = Column(Text)
details = relationship('Detail', secondary=ItemDetail, backref='Item')
class Detail(Base):
__tablename__ = 'Detail'
id = Column(Integer, primary_key=True)
name = Column(String)
value = Column(String)
items = relationship('Item', secondary=ItemDetail, backref='Detail')
【讨论】:
使用这种方法我得到以下错误:sqlalchemy.exc.ArgumentError: secondary argument <class> passed to to relationship() Activity.participants must be a Table object or other FROM clause; can't send a mapped class directly as rows in 'secondary' are persisted independently of a class that is mapped to that same table.
这让我相信映射的类不能用作辅助属性
应该是secondary=ItemDetail.__table__
【参考方案3】:
与 Miguel 一样,我也在为我的连接表使用声明式方法。但是,我一直遇到诸如
之类的错误sqlalchemy.exc.ArgumentError: 次要参数
main.ProjectUser'> 传递给 relationship() User.projects 必须是 Table 对象或其他 FROM 子句;无法直接发送映射类,因为“辅助”中的行独立于映射到同一个表的类进行持久化。
通过一些摆弄,我能够想出以下内容。 (请注意,我的课程与 OP 的课程不同,但概念相同。)
示例
这是一个完整的工作示例
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 User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True)
name = Column(String)
projects = relationship('Project', secondary='project_users', back_populates='users')
class Project(Base):
__tablename__ = "projects"
id = Column(Integer, primary_key=True)
name = Column(String)
users = relationship('User', secondary='project_users', back_populates='projects')
class ProjectUser(Base):
__tablename__ = "project_users"
id = Column(Integer, primary_key=True)
notes = Column(String, nullable=True)
user_id = Column(Integer, ForeignKey('users.id'))
project_id = Column(Integer, ForeignKey('projects.id'))
# Create the tables in the database
Base.metadata.create_all(engine)
# Test it
with Session(bind=engine) as session:
# add users
usr1 = User(name="bob")
session.add(usr1)
usr2 = User(name="alice")
session.add(usr2)
session.commit()
# add projects
prj1 = Project(name="Project 1")
session.add(prj1)
prj2 = Project(name="Project 2")
session.add(prj2)
session.commit()
# map users to projects
prj1.users = [usr1, usr2]
prj2.users = [usr2]
session.commit()
with Session(bind=engine) as session:
print(session.query(User).where(User.id == 1).one().projects)
print(session.query(Project).where(Project.id == 1).one().users)
注意事项
-
在
secondary
参数中引用表名,例如secondary='project_users'
,而不是secondary=ProjectUser
使用back_populates
而不是backref
我对此here进行了详细的撰写。
【讨论】:
你试过了吗:secondary=ItemDetail.__table__
or secondary=ItemDetail.__tablename__
以上是关于如何使用 SQLAlchemy 建立多对多关系:一个很好的例子的主要内容,如果未能解决你的问题,请参考以下文章
如何避免在 SQLAlchemy - python 的多对多关系表中添加重复项?