好的,在这种情况下,您需要更仔细地查看,尽管这里有一个警告可能应该成为例外,我会调查一下。这是您的示例的工作版本:
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('role_id', Integer, ForeignKey('role.id', ondelete='cascade'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
cascade='all,delete-orphan',
backref=backref('roles', cascade='all'))
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Role()
r1.tag_names.extend(["t1", "t2", "t3"])
s.add(r1)
s.commit()
现在让我们运行:
... creates tables
/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/properties.py:918: SAWarning: On Role.tags, delete-orphan cascade is not supported on a many-to-many or many-to-one relationship when single_parent is not set. Set single_parent=True on the relationship().
self._determine_direction()
Traceback (most recent call last):
... stacktrace ...
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 349, in hasparent
assert self.trackparent, "This AttributeImpl is not configured to track parents."
AssertionError: This AttributeImpl is not configured to track parents.
所以这是重要的部分:SAWarning:在 Role.tags 上,当未设置 single_parent 时,多对多或多对一关系不支持删除孤儿级联。在 relationship() 上设置 single_parent=True。
所以错误是固定的,如果你这样说:
tags = relationship('Tag',
secondary=tagging,
cascade='all,delete-orphan',
single_parent=True,
backref=backref('roles', cascade='all'))
但是,您可能会注意到,这并不是您真正想要的:
r1 = Role()
r2 = Role()
t1, t2 = Tag("t1"), Tag("t2")
r1.tags.extend([t1, t2])
r2.tags.append(t1)
输出:
sqlalchemy.exc.InvalidRequestError: Instance <Tag at 0x101503a10> is already associated with an instance of <class '__main__.Role'> via its Role.tags attribute, and is only allowed a single parent.
这就是你的“单亲”——“删除-孤儿”功能仅适用于所谓的生命周期关系,其中孩子完全存在于其单亲的范围内。因此,将多对多与“孤儿”一起使用几乎没有意义,而且它之所以受到支持,只是因为有些人真的非常想通过关联表来获得这种行为(也许是遗留数据库的东西)。
继承人的文档:
删除孤儿级联意味着每个子对象一次只能有一个父对象,因此在绝大多数情况下配置为一对多关系。将其设置在多对一或多对多关系上会更尴尬;对于这个用例,SQLAlchemy 要求使用 single_parent=True 函数配置 relationship(),该函数建立 Python 端验证,确保对象一次仅与一个父对象关联。
当您说“我要它清除孤儿”时暗示了什么?这里的意思是,如果你说r1.tags.remove(t1)
,那么你说的是“同花顺”。SQLAlchemy 会看到,“r1.tags, t1 已被删除,如果它是孤立的,我们需要删除!好吧,让我们出去“标记”,然后扫描整个表对于任何剩余的条目。” 一次天真地为每个标签执行此操作显然是非常低效的 - 如果您在一个会话中影响了数百个标签集合,那么将有数百个这些潜在的巨大查询。这样做不是天真地这样做将是添加了相当复杂的功能,因为工作单元倾向于一次考虑一个集合 - 它仍然会增加人们可能并不真正想要的明显查询开销。工作单元确实做得很好,但它试图避开不寻常的边缘情况,这会增加很多复杂性和惊喜。实际上,“删除孤儿”系统只有在对象 B 与内存中的对象 A 分离时才会发挥作用 - 没有扫描数据库或类似的东西,它'
因此,您在这里使用“删除孤儿”所做的事情是正确的,但让我们将其粘贴到事件中并使用更有效的查询,并一次性删除我们不需要的所有内容:
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('role_id', Integer, ForeignKey('role.id', ondelete='cascade'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
class Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
backref='roles')
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
session.query(Tag).\
filter(~Tag.roles.any()).\
delete(synchronize_session=False)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Role()
r2 = Role()
r3 = Role()
t1, t2, t3, t4 = Tag("t1"), Tag("t2"), Tag("t3"), Tag("t4")
r1.tags.extend([t1, t2])
r2.tags.extend([t2, t3])
r3.tags.extend([t4])
s.add_all([r1, r2, r3])
assert s.query(Tag).count() == 4
r2.tags.remove(t2)
assert s.query(Tag).count() == 4
r1.tags.remove(t2)
assert s.query(Tag).count() == 3
r1.tags.remove(t1)
assert s.query(Tag).count() == 2
现在每次刷新我们最后都会得到这个查询:
DELETE FROM tag WHERE NOT (EXISTS (SELECT 1
FROM tagging, role
WHERE tag.id = tagging.tag_id AND role.id = tagging.role_id))
所以我们不需要为了删除它们而将对象拉入内存,当我们可以根据一个简单的 SQL 标准进行删除时(依赖于在数据库可以更有效地执行操作时将行拉入内存已通过痛苦的行编程称为行)。与在计划器中往往更昂贵的 OUTER JOIN 相比,“不存在”在搜索相关行的缺失时也非常有效。