![](/img/trans.png)
[英]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.