SQLAlchemy ORM - Suppression d'objets associés

Il est facile d'effectuer une opération de suppression sur une seule table. Tout ce que vous avez à faire est de supprimer un objet de la classe mappée d'une session et de valider l'action. Cependant, l'opération de suppression sur plusieurs tables liées est peu délicate.

Dans notre base de données sales.db, les classes Client et Facture sont mappées à la table client et facture avec un à plusieurs types de relation. Nous allons essayer de supprimer l'objet Client et voir le résultat.

À titre de référence rapide, vous trouverez ci-dessous les définitions des classes de client et de facture -

from sqlalchemy import create_engine, ForeignKey, Column, Integer, String
engine = create_engine('sqlite:///sales.db', echo = True)
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
from sqlalchemy.orm import relationship
class Customer(Base):
   __tablename__ = 'customers'

   id = Column(Integer, primary_key = True)
   name = Column(String)
   address = Column(String)
   email = Column(String)
   
class Invoice(Base):
   __tablename__ = 'invoices'

   id = Column(Integer, primary_key = True)
   custid = Column(Integer, ForeignKey('customers.id'))
   invno = Column(Integer)
   amount = Column(Integer)
   customer = relationship("Customer", back_populates = "invoices")
   
Customer.invoices = relationship("Invoice", order_by = Invoice.id, back_populates = "customer")

Nous configurons une session et obtenons un objet Client en l'interrogeant avec l'ID principal à l'aide du programme ci-dessous -

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
x = session.query(Customer).get(2)

Dans notre exemple de tableau, x.name se trouve être «Gopal Krishna». Supprimons ce x de la session et comptons l'occurrence de ce nom.

session.delete(x)
session.query(Customer).filter_by(name = 'Gopal Krishna').count()

L'expression SQL résultante renverra 0.

SELECT count(*) 
AS count_1
FROM (
   SELECT customers.id 
   AS customers_id, customers.name 
   AS customers_name, customers.address 
   AS customers_address, customers.email 
   AS customers_email
   FROM customers
   WHERE customers.name = ?) 
AS anon_1('Gopal Krishna',) 0

Cependant, les objets Facture associés de x sont toujours là. Il peut être vérifié par le code suivant -

session.query(Invoice).filter(Invoice.invno.in_([10,14])).count()

Ici, 10 et 14 sont des numéros de facture appartenant au client Gopal Krishna. Le résultat de la requête ci-dessus est 2, ce qui signifie que les objets associés n'ont pas été supprimés.

SELECT count(*) 
AS count_1
FROM (
   SELECT invoices.id 
   AS invoices_id, invoices.custid 
   AS invoices_custid, invoices.invno 
   AS invoices_invno, invoices.amount 
   AS invoices_amount
   FROM invoices
   WHERE invoices.invno IN (?, ?)) 
AS anon_1(10, 14) 2

C'est parce que SQLAlchemy ne suppose pas la suppression de cascade; nous devons donner une commande pour le supprimer.

Pour modifier le comportement, nous configurons des options en cascade sur la relation User.addresses. Fermons la session en cours, utilisons new declarative_base () et redéclarons la classe User, en ajoutant la relation d'adresses, y compris la configuration en cascade.

L'attribut cascade dans la fonction de relation est une liste de règles de cascade séparées par des virgules qui détermine comment les opérations de session doivent être «cascadées» du parent à l'enfant. Par défaut, il est False, ce qui signifie qu'il s'agit de "save-update, merge".

Les cascades disponibles sont les suivantes -

  • save-update
  • merge
  • expunge
  • delete
  • delete-orphan
  • refresh-expire

L'option souvent utilisée est "tout, supprimer-orphelin" pour indiquer que les objets associés doivent suivre dans tous les cas l'objet parent et être supprimés lorsqu'ils sont désassociés.

Par conséquent, la classe de client redéclarée est indiquée ci-dessous -

class Customer(Base): 
   __tablename__ = 'customers'
   
   id = Column(Integer, primary_key = True) 
   name = Column(String) 
   address = Column(String) 
   email = Column(String) 
   invoices = relationship(
      "Invoice", 
      order_by = Invoice.id, 
      back_populates = "customer",
      cascade = "all, 
      delete, delete-orphan" 
   )

Supprimons le client avec le nom Gopal Krishna en utilisant le programme ci-dessous et voyons le nombre de ses objets Facture associés -

from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind = engine)
session = Session()
x = session.query(Customer).get(2)
session.delete(x)
session.query(Customer).filter_by(name = 'Gopal Krishna').count()
session.query(Invoice).filter(Invoice.invno.in_([10,14])).count()

Le nombre est maintenant de 0 avec le SQL suivant émis par le script ci-dessus -

SELECT customers.id 
AS customers_id, customers.name 
AS customers_name, customers.address 
AS customers_address, customers.email 
AS customers_email
FROM customers
WHERE customers.id = ?
(2,)
SELECT invoices.id 
AS invoices_id, invoices.custid 
AS invoices_custid, invoices.invno 
AS invoices_invno, invoices.amount
AS invoices_amount
FROM invoices
WHERE ? = invoices.custid 
ORDER BY invoices.id (2,)
DELETE FROM invoices 
WHERE invoices.id = ? ((1,), (2,))
DELETE FROM customers 
WHERE customers.id = ? (2,)
SELECT count(*) 
AS count_1
FROM (
   SELECT customers.id 
   AS customers_id, customers.name 
   AS customers_name, customers.address 
   AS customers_address, customers.email 
   AS customers_email
   FROM customers
   WHERE customers.name = ?) 
AS anon_1('Gopal Krishna',)
SELECT count(*) 
AS count_1
FROM (
   SELECT invoices.id 
   AS invoices_id, invoices.custid 
   AS invoices_custid, invoices.invno 
   AS invoices_invno, invoices.amount 
   AS invoices_amount
   FROM invoices
   WHERE invoices.invno IN (?, ?)) 
AS anon_1(10, 14)
0