【发布时间】:2021-07-27 06:40:00
【问题描述】:
上下文
我有一个用 SQLAlchemy 编写的简单 MySQL 数据库。以下是我的两个模型,Subreddit 和 Keyword,它们具有多对多关系,以及它们的关联表:
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
作为测试数据,我创建了以下数据集:
Subreddit:
test_subreddit
Keywords:
test_keyword1
test_keyword2
test_keyword3
换句话说,test_subreddit.keywords 应该返回 [test_keyword1, test_keyword2, test_keyword3]。
问题
当我删除 test_subreddit 时,test_keyword1、test_keyword2、test_keyword3 仍然保留在数据库中。
我知道,对于多对多关系,技术上没有父级,因此根据这篇文章,级联在技术上不起作用: https://stackoverflow.com/a/803584/10426919.
我试过的
我点击了这个链接:https://github.com/sqlalchemy/sqlalchemy/wiki/ManyToManyOrphan。
此链接提供了一个库函数,可以解决我的确切问题。
但是,当通过以下方式集成到我的模型文件中时,该功能不起作用:
方法#1:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans <------ # library
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
auto_delete_orphans(Subreddit.keywords) <------ # Library function
但是,这个函数似乎没有做任何事情。没有输出任何错误来帮助引导我朝着正确的方向前进。当我在 MySQL 工作台中检查我的数据库时,Subreddit test_subreddit 被删除了,但关键字 [test_keyword1, test_keyword2, test_keyword3] 仍在数据库中的关键字表下。
方法#2:
我尝试将库函数所基于的实际函数也集成到我的代码中:
from app.extensions import db
from werkzeug.security import generate_password_hash, check_password_hash
from sqlalchemy.inspection import inspect
from sqlalchemy_utils import auto_delete_orphans
# for deleting many-to-many "orphans".
from sqlalchemy import event, create_engine
from sqlalchemy.orm import attributes, sessionmaker
subreddits_keywords = db.Table('subreddits_keywords', db.Model.metadata,
db.Column('subreddit_id', db.Integer, db.ForeignKey('subreddits.id', ondelete='CASCADE')),
db.Column('keyword_id', db.Integer, db.ForeignKey('keywords.id', ondelete='CASCADE')),
)
class Subreddit(db.Model, JsonSerializer):
__tablename__ = 'subreddits'
id = db.Column(db.Integer, primary_key=True)
subreddit_name = db.Column(db.String(128), index=True)
# Establish a parent-children relationship (subreddit -> keywords).
keywords = db.relationship('Keyword', secondary=subreddits_keywords, backref='subreddits', cascade='all, delete', passive_deletes=True, lazy='dynamic')
// ...
class Keyword(db.Model, JsonSerializer):
__tablename__ = 'keywords'
id = db.Column(db.Integer, primary_key=True)
keyword = db.Column(db.String(128), index=True)
// ...
engine = create_engine("mysql://", echo=True)
Session = sessionmaker(bind=engine)
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
# optional: look through Session state to see if we want
# to emit a DELETE for orphan Tags
flag = False
for instance in session.dirty:
if isinstance(instance, Subreddit) and \
attributes.get_history(instance, 'keywords').deleted:
flag = True
break
for instance in session.deleted:
if isinstance(instance, Subreddit):
flag = True
break
# emit a DELETE for all orphan Tags. This is safe to emit
# regardless of "flag", if a less verbose approach is
# desired.
if flag:
session.query(Keyword).\
filter(~Keyword.subreddits.any()).\
delete(synchronize_session=False)
同样,尽管没有附加到任何父级,但关键字仍然存在。
我想要完成的事情
当数据库中的孩子不再有父母时,我希望将他们从数据库中删除。我做错了什么?
【问题讨论】:
-
我无法重现您的问题。 This code 对我来说很好。
-
@Gord,这很有趣。我想知道我使用位于 RDS 中的数据库,而不是文件的本地数据库,是否会导致
auto_delete_orphans出现问题。其他差异可能是您如何使用声明性基础,而我使用 SQLAlchemy 的 db.Model 来创建我的表。无论如何,我有一个绕过auto_delete_orphans的解决方案。
标签: sql sqlalchemy many-to-many parent-child