使用 PostgreSQL 的 on_conflict_do_update 的 SQLAlchemy 多对多未提交

问题描述 投票:0回答:1

我正在尝试在 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
的原子性。

python postgresql sqlalchemy
1个回答
0
投票

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()
© www.soinside.com 2019 - 2024. All rights reserved.