簡體   English   中英

SQLAlchemy 多對多 - 唯一約束

[英]SQLAlchemy Many to Many - Unique Constraint

我正在使用 FastAPI 和 SQLAlchemy 來保存多對多關系。 我遇到了一個問題,即 SQLAlchemy 將預先存在的子元素作為新項目插入而不是更新(或者最好不理會它)。

我已經把下面的代碼放在一起復制了這個問題。

第一次運行它時,它會工作並在關系表中創建父、子和關系。

第二次運行它(復制了我的問題)它嘗試使用提供的 ID 再次創建孩子,這顯然會導致錯誤,因為 ID 已經存在並且是主鍵。

from fastapi import Depends, FastAPI
from sqlalchemy import Column, ForeignKey, Integer, create_engine, String, Integer, Table
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
 
SQLALCHEMY_DATABASE_URL = "sqlite:///./sql_app.db"
 
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
 
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
 
Base = declarative_base()
 
 
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
 
 
children_parents = Table(
    "children_parents",
    Base.metadata,
    Column("child_id", ForeignKey(
        "children.id"), primary_key=True),
    Column("parent_id", ForeignKey(
        "parents.id"), primary_key=True)
)
 
 
class Parent(Base):
    __tablename__ = "parents"
 
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
 
    children = relationship(
        "Child", secondary=children_parents, back_populates="parents")
 
 
class Child(Base):
    __tablename__ = "children"
 
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
 
    parents = relationship(
        "Parent", secondary=children_parents, back_populates="children")
 
 
app = FastAPI()
 
Base.metadata.create_all(bind=engine)
 
# Run route "/" twice and get:
# ----------------------------------------
 
# sqlalchemy.exc.IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: children.id
# [SQL: INSERT INTO children (id, name) VALUES (?, ?)]
# [parameters: (1, 'Child name')]
# (Background on this error at: https://sqlalche.me/e/14/gkpj)
 
 
@app.get("/")
async def root(db: SessionLocal = Depends(get_db)):
 
    parent = Parent(name="Parent name", children=[
        Child(id=1, name="Child name")])
 
    db.add(parent)
    db.commit()
    db.refresh(parent)
    return parent

是的,這可能會令人困惑。 當添加一個 m2m object 及其相關的 object 時,您要么需要該子 object 作為查詢的結果,要么需要一個新的 ZA8CFDE6331BD59EB6666A9它自己的結果。 SQLAlchemy(不幸的是)不像你想要的那樣工作; 從頭開始創建一個新的 Child object 不會執行“upsert”,但會嘗試插入。 避免這種情況的一種簡單方法是查看(子)object 是否已經存在,如果不存在,則僅創建一個新實例。 像這樣:

from fastapi import Depends, FastAPI
from sqlalchemy import Column, ForeignKey, Integer, create_engine, String, Integer, Table
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.ext.declarative import declarative_base
 
SQLALCHEMY_DATABASE_URL = "sqlite:///sql_app.db"
 
engine = create_engine(
    SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False}
)
 
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
 
Base = declarative_base()
 
def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()
 
 
children_parents = Table("children_parents", Base.metadata,
    Column("child_id", ForeignKey("children.id"), primary_key=True),
    Column("parent_id", ForeignKey("parents.id"), primary_key=True)
)
 
 
class Parent(Base):
    __tablename__ = "parents"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    children = relationship("Child", secondary=children_parents, backref="parents")
 
class Child(Base):
    __tablename__ = "children"
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
 
 
app = FastAPI()
 
Base.metadata.create_all(bind=engine)

 
@app.get("/")
async def root(db: SessionLocal = Depends(get_db)):
    child_1 = db.query(Child).filter(Child.id==1).first()
    if not child_1:
        child_1 = Child(id=1, name="Child name")

    parent = Parent(name="Parent name", children=[
        child_1])
 
    db.add(parent)
    db.commit()
    db.refresh(parent)
    return parent

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)

(如果您不喜歡,我也簡化了您的 model;它與手頭的問題無關,所以如果您願意,請忽略它:))

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM