我正在尝试在 SQLAlchemy 和 PostgreSQL 中使用多对多关系。 我想利用
on_conflict_do_update
,所以我必须使用 insert
语句,而不是创建 ORM 对象,将其添加到会话中,然后提交。
我发现,当以这种方式创建对象时,使用
thing.relationship.append
添加多对多关系不起作用。 IE。当您提交时,关联表中没有新行。
我用表 B 和 C(以及 BC 将它们连接起来)重现了下面的问题。 我注意到了使这种关系发挥作用的方法,但是您不能运行该代码两次,因为这会导致 UniqueConstraint 错误。
from typing import List
from sqlalchemy import Column, create_engine, ForeignKey, String, Table
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship, Session
from sqlalchemy.dialects.postgresql import insert
class Base(DeclarativeBase):
pass
bc_table = Table(
"bc",
Base.metadata,
Column("b_id", ForeignKey("b.id", ondelete="CASCADE"), index=True, primary_key=True),
Column("c_id", ForeignKey("c.id", ondelete="CASCADE"), index=True, primary_key=True)
)
class B(Base):
__tablename__ = 'b'
id: Mapped[int] = mapped_column(primary_key=True)
foo: Mapped[str] = mapped_column(String, unique=True)
cs: Mapped[List["C"]] = relationship(back_populates="bs", secondary=bc_table)
class C(Base):
__tablename__ = 'c'
id: Mapped[int] = mapped_column(primary_key=True)
bar: Mapped[str] = mapped_column(String, unique=True)
bs: Mapped[List["B"]] = relationship(back_populates="cs", secondary=bc_table)
engine = create_engine('postgresql://postgres:hunter2@localhost:5432/replica')
Base.metadata.create_all(engine)
with Session(engine) as session, session.begin():
# I have to use an insert to take advantage of `on_conflict_do_update`
set_ = {'foo': 1}
b_stmt = insert(B).values(set_)
b_stmt = b_stmt.on_conflict_do_update(index_elements=[B.foo], set_=set_)
cro = session.execute(b_stmt)
b = B(id=cro.inserted_primary_key[0], **set_)
set_ = {'bar': 1}
c_stmt = insert(C).values(set_)
c_stmt = c_stmt.on_conflict_do_update(index_elements=[C.bar], set_=set_)
cro = session.execute(c_stmt)
c = C(id=cro.inserted_primary_key[0], **set_)
# Try to create a many-to-many relationship entry between b and c.
c.bs.append(b)
## The easy way would be to do this, but I can't because I need to use on_conflict_do_update
# b = B(foo=1)
# c = C(bar=1)
# c.bs.append(b)
# session.add_all([b, c])
session.commit()
我怎样才能让
c.bs.append
做出承诺? 解决方案是否只是检查 b
或 c
是否存在并仅通过 b = ...; c = ...; session.add_all([b,c])
创建它? 我更喜欢 on_conflict_do_update
的原子性。
在
insert
语句之后,您需要将插入或更新的对象添加到会话中。您可以使用 session.merge()
或 session.add()
来完成此操作。这样,ORM 就可以识别对象并可以管理它们的关系。
这是更新后的代码:
with Session(engine) as session, session.begin():
# Insert or update B and add to session
set_ = {'foo': 1}
b_stmt = insert(B).values(set_)
b_stmt = b_stmt.on_conflict_do_update(index_elements=[B.foo], set_=set_).returning(B.id)
result = session.execute(b_stmt)
b_id = result.scalar_one()
b = session.get(B, b_id)
# Insert or update C and add to session
set_ = {'bar': 1}
c_stmt = insert(C).values(set_)
c_stmt = c_stmt.on_conflict_do_update(index_elements=[C.bar], set_=set_).returning(C.id)
result = session.execute(c_stmt)
c_id = result.scalar_one()
c = session.get(C, c_id)
# Add the relationship
c.bs.append(b)
# Commit the changes
session.commit()