如何从数据库中删除 SQLAlchemy 多对多孤儿?

How to remove SQLAlchemy Many-To-Many Orphans from database?

上下文

我有一个用 SQLAlchemy 编写的简单 MySQL 数据库。以下是我的两个模型,Subreddit 和 Keyword,它们具有多对多关系,以及它们的关联 table:

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应该return[test_keyword1,test_keyword2,test_keyword3].

问题

当我删除 test_subreddit、test_keyword1、test_keyword2 时,test_keyword3 仍然保留在数据库中。

我知道对于多对多关系,从技术上讲没有父级关系,因此从技术上讲级联不会根据此 post 工作: .

我试过的

我关注了这个link:https://github.com/sqlalchemy/sqlalchemy/wiki/ManyToManyOrphan

这个 link 提供了一个库函数,应该可以解决我的确切问题。

但是,当通过以下方式集成到我的模型文件中时,该功能不起作用:

方法一:

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 workbench 中检查我的数据库时,Subreddit test_subreddit 被删除,但是关键字 [test_keyword1、test_keyword2、test_keyword3 ] 仍在关键字 table.

下的数据库中

方法二:

我尝试将库函数所基于的实际函数也集成到我的代码中:

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)

同样,尽管关键字没有附加到父级,但仍然存在。

我想要完成的事情

当数据库中的子项不再有父项时,我希望将它们从数据库中删除。我做错了什么?

我没有使用 auto_delete_orphans,而是创建了一个可以在我想删除 children 时调用的方法。此方法检查有问题的 child,并查看它是否有任何 parent。如果它确实有 parent,我们保留它,但如果它没有 parent,我们然后删除 children.

下面是我实现这个方法的方法,假设 Subreddit 是 parent 而 Keyword 是 Subreddit 的 child。

def check_for_keyword_orphans(keyword):
    # check if each keyword has an associated subreddit
    if len(keyword.subreddits) == 0:
        db.session.delete(keyword)
        return True # keyword deleted
    else:
        return False # keyword has an associated subreddit

下面是我在 API 路线中使用该方法的方式:

keywords = subreddit.keywords
  for keyword in keywords:
    check_for_keyword_orphans(keyword)
        
db.session.commit()