简体   繁体   English

SQLAlchemy 多对多 - 唯一约束

[英]SQLAlchemy Many to Many - Unique Constraint

I am using FastAPI and SQLAlchemy to save a many to many relationship.我正在使用 FastAPI 和 SQLAlchemy 来保存多对多关系。 I am getting an issue where the pre-existing child element is being inserted by SQLAlchemy as a new item rather than updating (or preferably leaving it alone).我遇到了一个问题,即 SQLAlchemy 将预先存在的子元素作为新项目插入而不是更新(或者最好不理会它)。

I have put together the below code which replicates the issue.我已经把下面的代码放在一起复制了这个问题。

The first time you run it, it works and creates the parent, the child and the relationship in the relationship table.第一次运行它时,它会工作并在关系表中创建父、子和关系。

The second time your run it (which replicates my issue) it attempts to create the child again with the provided ID, which obviously causes an error as the ID already exists and is the primary key.第二次运行它(复制了我的问题)它尝试使用提供的 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

Yeah, that might be confusing.是的,这可能会令人困惑。 When adding a m2m object with its related object, you either need that child object to be a result of a query or a new object of its own.当添加一个 m2m object 及其相关的 object 时,您要么需要该子 object 作为查询的结果,要么需要一个新的 ZA8CFDE6331BD59EB6666A9它自己的结果。 SQLAlchemy (unfortunately) doesn't work like you want it to work; SQLAlchemy(不幸的是)不像你想要的那样工作; creating a new Child object from scratch does not perform an 'upsert' but will try an insert.从头开始创建一个新的 Child object 不会执行“upsert”,但会尝试插入。 An easy way to circumvent this is to see if the (Child) object already exists and if not then only create a new instance.避免这种情况的一种简单方法是查看(子)object 是否已经存在,如果不存在,则仅创建一个新实例。 Like so:像这样:

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)

(I simplified your model as well if that is not to your liking; it has nothing to do with the question at hand so ignore it if you will:)) (如果您不喜欢,我也简化了您的 model;它与手头的问题无关,所以如果您愿意,请忽略它:))

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM