好的,在這種情況下,您需要仔細觀察,儘管這裏有一個警告,可能應該成爲一個例外,我會研究這一點。這裏是你的榜樣的工作版本:
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時,可以使用多對多或多對一的關係。在關係()上設置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.
這就是你的「單親」 - 「刪除孤兒」功能只適用於所謂的生命週期關係,其中孩子完全存在於其單親父母的範圍內。因此,與「孤兒」一起使用多對多實際上沒有意義,而且它僅受支持,因爲有些人真的很想用關聯表來獲得這種行爲(無論是傳統數據庫的東西,也許)。
繼承人the doc爲:
delete-orphan cascade implies that each child object can only have one parent at a time, so is configured in the vast majority of cases on a one-to-many relationship. Setting it on a many-to-one or many-to-many relationship is more awkward; for this use case, SQLAlchemy requires that the relationship() be configured with the single_parent=True function, which establishes Python-side validation that ensures the object is associated with only one parent at a time.
當你說,「我希望它清理掉孤兒」是什麼暗示?這意味着在這裏,如果你說r1.tags.remove(t1)
,那麼你說「沖洗」。 SQLAlchemy會看到「r1.tags,t1已被刪除,如果它是一個孤兒,我們需要刪除!OK,那麼讓我們去」標記「,然後掃描整個表以獲取所有剩餘的條目。」To一次只爲每個標籤做這個事情顯然會非常低效 - 如果您在會話中影響了幾百個標籤集合,那麼這些潛在的巨大查詢就會有幾百個。要做到這一點不會太天真,將會是一個非常複雜的功能添加,因爲工作單元傾向於一次考慮一個集合 - 它仍然會增加人們可能不想要的明顯查詢開銷。工作單位確實做得很好,但它試圖擺脫不尋常邊緣案例的業務,增加了許多複雜性和驚喜。實際上,「刪除孤兒」系統只有在對象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標準(當數據庫可以更高效地執行操作時,依靠將行拉入內存) en編號爲row by agonizing row)。與搜索沒有相關行的「外部連接」相比,「外部連接」在計劃者中往往更昂貴。
很好的答案。感謝您提供詳細而詳細的解釋,並感謝您的工作代碼。我很高興通過工作實例學習和了解更多SQLAlchemy,因爲在文檔中很難從理論上理解和理解。 – 2012-03-17 20:33:56
真是一個很好的答案! – NobRuked 2012-07-25 21:56:26
夢幻般的答案 - 謝謝 – 2012-09-05 12:58:23