170 votes

sqlachemy: cascade delete

Je doit manquer quelque chose de trivial avec SQLAlchemy de la cascade d'options que je ne peux pas obtenir un simple supprimer en cascade pour fonctionner correctement-si un parent de l'élément est supprimé, les enfants persistent, avec null clés étrangères.

J'ai mis un concis de cas de test ici:

from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Parent(Base):
    __tablename__ = "parent"
    id = Column(Integer, primary_key = True)

class Child(Base):
    __tablename__ = "child"
    id = Column(Integer, primary_key = True)
    parentid = Column(Integer, ForeignKey(Parent.id))
    parent = relationship(Parent, cascade = "all,delete", backref = "children")

engine = create_engine("sqlite:///:memory:")
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)

session = Session()

parent = Parent()
parent.children.append(Child())
parent.children.append(Child())
parent.children.append(Child())

session.add(parent)
session.commit()

print "Before delete, children = {0}".format(session.query(Child).count())
print "Before delete, parent = {0}".format(session.query(Parent).count())

session.delete(parent)
session.commit()

print "After delete, children = {0}".format(session.query(Child).count())
print "After delete parent = {0}".format(session.query(Parent).count())

session.close()

Sortie:

Before delete, children = 3
Before delete, parent = 1
After delete, children = 3
After delete parent = 0

Il est un simple, un-à-plusieurs relation entre le Parent et l'Enfant. Le script crée un parent, ajoute 3 enfants, puis s'engage. Ensuite, il supprime les parents, mais les enfants persistent. Pourquoi? Comment puis-je rendre les enfants effacer en cascade?

263voto

Steven Points 10243

Le problème est que sqlalchemy considère Child en tant que parent, parce que c'est là que vous avez défini votre relation (il ne se soucie pas que vous l'avez appelé "Enfant", bien entendu).

Si vous définissez la relation sur l' Parent de la classe au lieu de cela, il va fonctionner:

children = relationship("Child", cascade="all,delete", backref="parent")

(note "Child" comme une chaîne de caractères: ce qui est permis lors de l'utilisation de l'ensemble des déclarations de style, de sorte que vous êtes en mesure de se référer à une classe qui n'est pas encore défini)

Vous pouvez ajouter delete-orphan (endelete incite les enfants à être supprimée lorsque le parent est supprimé, delete-orphan supprime également tous les enfants qui ont été "enlevés" par les parents, même si le parent n'est pas supprimé)

EDIT: viens de découvrir: si vous vraiment voulez définir la relation sur l' Child classe, vous pouvez le faire, mais vous aurez à définir la cascade sur la backref (par la création de la backref explicitement), comme ceci:

parent = relationship(Parent, backref=backref("children", cascade="all,delete"))

(ce qui implique from sqlalchemy.orm import backref)

164voto

Alex Okrushko Points 1711

@ La réponse de Steven est bonne lorsque vous effectuez une suppression via session.delete() ce qui ne se produit jamais dans mon cas. J'ai remarqué que la plupart du temps, je supprime via session.query().filter().delete() (ce qui ne met pas d'éléments dans la mémoire et supprime directement de la base de données). En utilisant cette méthode, les cascade='all, delete' sqlalchemy ne fonctionnent pas. Il existe cependant une solution: ON DELETE CASCADE par db (remarque: toutes les bases de données ne la prennent pas en charge).

 class Child(Base):
    __tablename__ = "children"

    id = Column(Integer, primary_key=True)
    parent_id = Column(Integer, ForeignKey("parents.id", ondelete='CASCADE'))

class Parent(Base):
    __tablename__ = "parents"

    id = Column(Integer, primary_key=True)
    child = relationship(Child, backref="parent", passive_deletes=True)
 

7voto

Larry Weya Points 31

Steven est correct en ce que vous avez besoin de créer explicitement la backref, cette résultats dans la cascade d'être appliqué sur la mère (contrairement à ce qu'elle soit appliquée à l'enfant, comme dans le scénario de test).

Toutefois, la définition de la relation de l'Enfant ne fait PAS de sqlalchemy considérer l'Enfant de la mère. Il n'a pas d'importance où la relation est définie (enfant ou parent), c'est la clé étrangère qui relie les deux tableaux qui détermine qui est le parent et qui est l'enfant.

Il est logique de s'en tenir à une convention, bien, et sur la base de Steven réponse, je suis à la définition de l'ensemble de mon enfant relations sur le parent.

6voto

Profane Points 840

J'ai du mal avec la documentation ainsi, mais a constaté que les docstrings eux-mêmes ont tendance à être plus facile que le manuel. Par exemple, si vous importez une relation à partir de sqlalchemy.l'orm et le faire aider(relation), il vous donnera toutes les options que vous pouvez spécifier pour cascade. La balle pour "delete-orphan" dit, "si un élément de l'enfant, du type avec aucun parent n'est détecté, le marquer pour suppression. Notez que cette option empêche un élément en attente de la classe de leur enfant d'être persisté sans leurs parents."

Je me rends compte votre est plus un problème avec la façon dont la documentation pour la définition des relations parent-enfant. Mais il semble que vous pourriez aussi avoir un problème avec la cascade d'options, parce que "tout le" comprend "supprimer". "supprimer orphelin" est la seule option qui n'est pas inclus dans "tous".

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X