繁体   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