SQLAlchemy ORM - Excluindo Objetos Relacionados
É fácil realizar a operação de exclusão em uma única tabela. Tudo que você precisa fazer é excluir um objeto da classe mapeada de uma sessão e confirmar a ação. No entanto, a operação de exclusão em várias tabelas relacionadas é um pouco complicada.
Em nosso banco de dados sales.db, as classes Cliente e Fatura são mapeadas para o cliente e a tabela de fatura com um para vários tipos de relacionamento. Tentaremos excluir o objeto Cliente e ver o resultado.
Como uma referência rápida, abaixo estão as definições das classes Cliente e Fatura -
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")
Configuramos uma sessão e obtemos um objeto Cliente, consultando-o com o ID primário usando o programa abaixo -
from sqlalchemy.orm import sessionmaker
Session = sessionmaker(bind=engine)
session = Session()
x = session.query(Customer).get(2)
Em nossa tabela de exemplo, x.name é 'Gopal Krishna'. Vamos deletar esse x da sessão e contar a ocorrência desse nome.
session.delete(x)
session.query(Customer).filter_by(name = 'Gopal Krishna').count()
A expressão SQL resultante retornará 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
No entanto, os objetos de fatura relacionados de x ainda estão lá. Pode ser verificado pelo seguinte código -
session.query(Invoice).filter(Invoice.invno.in_([10,14])).count()
Aqui, 10 e 14 são os números das faturas pertencentes ao cliente Gopal Krishna. O resultado da consulta acima é 2, o que significa que os objetos relacionados não foram excluídos.
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
Isso ocorre porque SQLAlchemy não assume a exclusão da cascata; temos que dar um comando para excluí-lo.
Para alterar o comportamento, configuramos opções em cascata no relacionamento User.addresses. Vamos fechar a sessão em andamento, usar new declarative_base () e declarar novamente a classe User, adicionando o relacionamento de endereços, incluindo a configuração em cascata.
O atributo cascade na função de relacionamento é uma lista separada por vírgulas de regras em cascata que determina como as operações da Sessão devem ser “cascateadas” de pai para filho. Por padrão, é False, o que significa que é "salvar-atualizar, mesclar".
As cascatas disponíveis são as seguintes -
- save-update
- merge
- expunge
- delete
- delete-orphan
- refresh-expire
A opção frequentemente usada é "todos, excluir órfãos" para indicar que os objetos relacionados devem seguir junto com o objeto pai em todos os casos e ser excluídos quando desassociados.
Portanto, a classe Cliente reafirmada é mostrada abaixo -
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"
)
Vamos excluir o Cliente com o nome Gopal Krishna usando o programa abaixo e ver a contagem de seus objetos de fatura relacionados -
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()
A contagem agora é 0 com o seguinte SQL emitido pelo script acima -
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