SQLAlchemy FK ondelete не ОГРАНИЧЕНИЕ - PullRequest
1 голос
/ 03 мая 2019

У меня установлены отношения со ссылками на себя. У человека может быть один родитель (или никто), а у человека может быть много детей (или ни одного).

Так что 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')

Однако я хочу, чтобы запретил удаления лиц, если они являются родителями. Поэтому я включил предложение ondelete='RESTRICT', но оно не имеет никакого эффекта. Столбец parent_id по-прежнему имеет значение NULL, когда родитель удаляется.

(обратите внимание, что мое соединение SQLite переключило ограничения внешнего ключа прагмы в положение ON)

Почему база данных не выдает ошибку, когда родитель удаляется, и, следовательно, дочерний столбец с его внешним ключом ограничивает это?

Ответы [ 2 ]

2 голосов
/ 03 мая 2019

Настройка ограничения внешнего ключа выглядит правильно, но ваши отношения ORM не имеют явной каскадной конфигурации, поэтому они используют значения по умолчанию save-update и merge .В этой конфигурации по умолчанию отношение children отменяет связь осиротевших потомков при удалении родителя путем установки их внешнего ключа равным NULL.Я думаю, что вы должны использовать passive_deletes='all' (см. Примечание по delete cascades) в этом случае, чтобы отключить любые каскады уровня 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')
2 голосов
/ 03 мая 2019

Sqlalchemy обнуляет дочерние строки, прежде чем база данных сможет оценить ограничение внешнего ключа. Если вы добавите passive_deletes=True к отношению, sqlalchemy не будет пытаться управлять удалением связанных сущностей, а просто позволит базе данных сделать свое дело в зависимости от того, как вы ее настроили. не выдаст сначала выберите, чтобы заполнить отношения перед удалением родителя. Установка 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): невозможно удалить или обновить родительскую строку: внешний ключ ограничение не выполняется (test. person, CONSTRAINT person_ibfk_1 FOREIGN КЛЮЧ (parent_id) ССЫЛКИ 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' - это путь.

...