[英]python SQLAlchemy many to many unique constraint only working one way
[英]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.