SQLAlchemy FK ondelete 不限制

SQLAlchemy FK ondelete does not RESTRICT

我建立了自我参照关系。一个人可以有一个parent(或None),一个人可以有多个child仁(或None)。

所以允许 NULL 作为 FK:

class Person(db.Model):
    id        = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('person.id', ondelete='RESTRICT'))
    parent    = db.relationship('Person', remote_side=[id], back_populates='children')
    children  = db.relationship('Person', back_populates='parent')

但是,我希望禁止 删除 parent 的人物。所以我包含了 ondelete='RESTRICT' 子句,但它没有任何效果。删除 parent 后,parent_id 列仍设置为 NULL。

(注意我的 SQLite 连接已将 pragma 外键约束切换为 ON)

为什么当 parent 被删除时数据库不抛出错误,因此 child 列以它作为外键限制了这个?

在数据库有机会评估外键约束之前,Sqlalchemy 正在清空子行。如果您将 passive_deletes=True 添加到关系中,sqlalchemy 将不会尝试管理相关实体的删除,而只是让数据库根据您的配置方式执行操作。 不会先发出 select 来填充关系,然后再删除父级。设置为 True 仍会导致已在会话中的子对象将其 FK 列设置为 NULL.

此配置:

class Person(db.Model):
    id        = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('person.id', ondelete='RESTRICT'))
    parent    = db.relationship('Person', remote_side=[id], back_populates='children')
    children  = db.relationship('Person', back_populates='parent', passive_deletes=True)


if __name__ == '__main__':
    with app.app_context():
        db.drop_all()
        db.create_all()
        parent = Person()
        db.session.add(parent)
        child = Person(parent=parent)
        db.session.commit()
        db.session.delete(parent)
        db.session.commit()

加薪:

sqlalchemy.exc.IntegrityError: (mysql.connector.errors.IntegrityError) 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (test.person, CONSTRAINT person_ibfk_1 FOREIGN KEY (parent_id) REFERENCES person (id))

if __name__ == '__main__':
    with app.app_context():
        db.drop_all()
        db.create_all()
        parent = Person()
        db.session.add(parent)
        child = Person(parent=parent)
        db.session.commit()
        db.session.query(Person).all()  # reload the people into the session before deleting parent
        db.session.delete(parent)
        db.session.commit()

... 仍然会使子项的 parent_id 字段为空,即使 passive_deletes=True 也是如此。所以 passive_deletes='all' 是正确的选择。

您的外键约束设置看起来是正确的,但是您的 ORM 关系没有明确的级联配置,因此它们使用默认值 save-update and merge. In this default configuration the children relationship de-associates orphaned children when deleting the parent by setting their foreign key to NULL. I think you should use passive_deletes='all' (see the note on delete 级联)在这种情况下删除父级时禁用任何 ORM 级级联,以便数据库在刷新时可以防止删除:

class Person(db.Model):
    id        = db.Column(db.Integer, primary_key=True)
    parent_id = db.Column(db.Integer, db.ForeignKey('person.id', ondelete='RESTRICT'))
    parent    = db.relationship('Person', remote_side=[id], back_populates='children')
    children  = db.relationship('Person', back_populates='parent', passive_deletes='all')